Using Asymmetric Cryptography in your ColdFusion Application - Security Series #16.10

A reader emailed me and asked:

I have a question re asymmetric encryption and the best way to achieve it....

I need to encrypt a CreditCard number on one server and store the encrypted string in a db and then 5 minutes later another server takes the card number off that DB and then needs to decrypt it. Any suggestions gratefully received :)

After an e-mail exchange we determined that we were NOT just talking about using SSL between ColdFusion and the DB and we determined that using a symmetric algorithm would not be acceptable to the credit card service. So it seems that this user really did need asymmetric encryption in his application.

Now this is not something I have dealt with before, so I have no experience with this. What I am about to outline is based on reading and research. I will try to point out the details that I am unclear on, both in the Java programming and in the cryptography aspect. If I make errors or do not explain something adequately, please let me know.

I have not yet discussed asymmetric cryptography in my security series, but I felt it was reasonable to skip ahead for this reader. Eventually I will go back and discuss asymmetric crypto in another post, so for now I will only very briefly discuss what asymmetric crypto is and why you would want to use it.

What is Asymmetric Cryptography?

Asymmetric cryptography, also known as Public Key Cryptography, is different from its counterpart, Symmetric Cryptography, in that it uses different keys for the encryption and decryption processes. Encryption is done with one key and decryption is done with another, whereas with symmetric encryption the same key is used for both encryption and decryption.

You may wonder why we would want to use asymmetric cryptography over symmetric cryptography. There are A LOT of reasons. But the reason we are looking at here is because we do not want to share a symmetric key with another machine. Whether it is because we do not trust the administrators of that other machine, or because we do not want the compromise of one machine to make the other machine vulnerable, or even if it is just because we are a control freak in a turtleneck sweater, it doesn't matter.

For the sake of having a reason, we'll say that we are doing it because we want to mitigate the risk of credit card information theft if our database server (the one holding the card data) is hacked or otherwise compromised.

Hypothetical situations are fun

Consider this situation:

We have an e-commerce site that stores and processes credit cards. We have two servers.

  1. Database/Web Server
  2. Credit Card Processing Server

The database server is where we store the encrypted credit card information.

If we are using symmetric encryption, then the credit cards are encrypted using the same key that we would use to decrypt them. Since the database server must have the key in order to perform the encryption, it means that if that machine is compromised, the key could be compromised, and that key could be used to decrypt the credit card information.

When the credit card processing server wants a credit card for processing, it either needs to request the unencrypted card from the database server, or it needs to have its own copy of the key to perform the decryption. There are a number of reasons we may not want this.

Hypothetical 2

Now consider the same situation, but this time, we'll use asymmetric cryptography.

When the database/web server receives the credit card information from the end user, it encrypts the data, just as it would have before, but this time it encrypts it using asymmetric cryptography and the PUBLIC KEY of the credit card processing server.

With asymmetric encryption, any data encrypted with the public key can only be decrypted with the private key (with some algorithms, it works the opposite way too, we'll talk about that someday when we discuss digital signatures). So in this situation, our database/web server will encrypt with the public key, but it will not have, or need, a copy of the private key.

Since the database/web server should never need the credit card information again, it does not matter that it will be unable to decrypt the data. And in the event that the database server is compromised, the credit card information will be safe (provided a well-designed, strong algorithm is in place) because the key will be safely tucked away on the credit card processing machine (or somewhere else). And the fact that the public key will have been compromised is of little consequence (hence the reason it is called the public key, it can be made public without fear of revealing the private key).

Now, how do we do it?

ColdFusion has some awesome built in cryptography functions. Encrypt(), Decrypt(), EncryptBinary() and DecryptBinary() are great tools that we should all learn how to use. But ColdFusion's cryptography functions only support symmetric cryptography. If we want to use asymmetric crypto, we need to turn elsewhere. In my experimentation, I turned to Java, of course, and the Java Cryptography Extensions(JCE) to do the asymmetric encryption, but JCE does not include a provider to generate asymmetric cryptographic keys, so for that I turned to the Bouncy Castle Crypto API.

According to the Bouncy Castle website:

The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms, it was developed by the Legion of the Bouncy Castle - with a little help!

The package is organised so that it contains a light-weight API suitable for use in any environment with the additional infrastructure to conform the algorithms to the JCE framework.

With the help of some Googling, Java examples, and experimentation, I came up with the following working example in ColdFusion. Let's walk through it.

The Code

First, we need to add the Bouncy Castle jar file to our ColdFusion installation. You can do this by placing the Bouncy Castle .jar file (I used bcprov-jdk16-145.jar) into your ColdFusion's lib directory. Then restart ColdFusion.

NOTE: You could also use Mark's JavaLoader instead of placing the jar into the lib directly.

Once you have Bouncy Castle in place, we need to get at it using ColdFusion's fantastic ability to work with Java.

NOTE: My skillz with Java are "meh". Please feel free to correct my usage. I also did not architect a great solution here. This is just a bunch of procedural code from a single CFM file.


<!--- Get the Bouncy Castle Asymmetric Key Generator --->
    <cfset kpgo = createObject('java', 'org.bouncycastle.jce.provider.asymmetric.ec.KeyPairGenerator') />
    
    <!--- Get an instance of the provider for the RSA algorithm. --->
    <cfset kpg = kpgo.getInstance("RSA") />
    
    <!--- Get an instance of secureRandom, we'll need this to initialize the key generator --->
    <cfset sr = createObject('java', 'java.security.SecureRandom').init() />
    
    <!--- Initialize the generator by passing in the size of key we want, and a strong pseudo-random number generator (PRNG) --->
    <cfset kpg.initialize(2048, createObject('java', 'java.security.SecureRandom')) />
    
    <!--- This will create two keys, one public, and one private --->
    <cfset kp = kpg.generateKeyPair() />
    
    <!--- Get the two keys. --->
    <cfset privateKey = kp.getPrivate() />
    <cfset publicKey = kp.getPublic() />

In the above code, we first get an instance of the KeyPairGenerator provider from Bouncy Castle, then we request an instance of the generator for the RSA algorithm. RSA is a common encryption algorithm that you can read more about here.

Once we have an instance of the KeyPairGenerator we need to initialize it by telling it how large a key we want and we need to pass in a Pseudo-Random Number Generator (PRNG). Since ColdFusion has SecureRandom built in, I used that.

I chose a key length of 2048 bits. This is probably the minimum you should consider using. Typical key lengths are 1024, 2048, and 4096. 1024 is still an acceptable size for now, but many experts agree that it will not be acceptable for too much longer and recommend going with at least 2048.

You might ask, "why not just do 4096 and call it awesome?", and I would answer you with some charts. This chart shows the execution time of generating a public and private key and then performing a single encryption and decryption operation of a 117 character string on my development machine (Intel Dual-Core with WinXP 32-bit).

Key Length Speed in ms
1024 500-600
2048 1500-2500
4096 8000-20000

As you can see, the computation time with the larger key sizes can get ridiculous. Asymmetric encryption is EXTREMELY processor intense. Keep that in mind before you decide to implement it as a solution. Symmetric encryption is SMOKIN' fast compared to it.

Note that the times above do include creation of the Public and Private keys. That seems to be the most processor intensive part. If you use pre-generated keys, then the times for JUST encryption and decryption are much shorter.

Key Length Speed in ms
1024 16
2048 30-32
4096 230-250

The 4096-bit key is still considerably slower than the other two. Consider the number of concurrent operations before you make your decision. If the encryption/decryption process does not happen very often, then perhaps 4096-bit key lengths will work for you. Also keep in mind how much data you are encrypting/decrypting.

After initializing my generator, I then requested that it generate a key pair. A key pair consists of a public key and a private key. These two keys are Java objects of type com.rsa.jsafe.provider.JSA_RSAPublicKey and com.rsa.jsafe.provider.JSA_RSAPrivateKey respectively.

Now, one thing I have not yet looked into is storing these keys. Once they are generated they need to be stored in a persistent (and safe way) for their entire cryptoperiod. I am not going to get heavily into key management here, but somehow any one who implements this needs to figure out how to store these keys and make them available for use on the respective machines. From a brief search it looks like you should be able to store them as Base64 encoded strings, if that is the case, you should be able to put them where ever you need to (database, text file, etc) keeping in mind that the PRIVATE KEY MUST BE SECURELY STORED.

Remember, ONLY the public key should be stored on the machine that is doing the encryption. In the example above, that would be our database/web server. The credit card processing server can have both keys, but the important one for it to have is the private key.

Also, remember that the safety of the key storage is imperative. Not only do you need to safeguard the private key from being stolen, but you also need to safeguard it from being lost. If you lose that key, you'll NEVER get your data back out.

One last thing on key management. Look into proper key management procedures, like key rotation, cryptoperiods, destruction, etc. etc. It's complicated and difficult, but important.

Using the public and private keys

Once you have generated and stored the keys you can now use them. Using them is actually quite simple. In the examples below, I am assuming that the keys have been loaded as the Java objects mention above into the ColdFusion application's APPLICATION scope.

Encrypting Data

Here is a possible function you could use to encrypt a string:


<cffunction name="encryptString" access="public" returntype="Any" output="false">
    <!--- Take in the string to encrypt and the key to encrypt with --->
    <cfargument name="inputString" type="string" />
    <cfargument name="key" type="any" />

    <!--- Create a Java Cipher object and get a mode --->
    <cfset var cipher = createObject('java', 'javax.crypto.Cipher').getInstance("RSA") />
    
    <!--- The mode tells the Cipher whether is will be encrypting or decrypting --->
    <cfset var encMode = cipher.ENCRYPT_MODE />
        
    <cfset var encryptedValue = "" /> <!--- Return variable --->
    
    <!--- Initialize the Cipher with the mode and the key --->
    <cfset cipher.init(encMode, key) />
    
    <!--- Convert the string to bytes --->
    <cfset stringBytes = inputString.getBytes("UTF8") />
    
    <!--- Perform encryption --->
    <cfset encryptedValue = cipher.doFinal(stringBytes, 0, len(inputString)) />
    
    <cfreturn encryptedValue />
</cffunction>

Now in theory, you should be able to encrypt anything. A string, number, object, file, etc. But they need to be converted to bytes first.

Also keep in mind that, at least with the RSA algorithm (I am not sure with other Asymmetric algorithms), that you can only encrypt messages that are N bytes smaller than the key length. N changes based on the padding used. For example, with the 2048-bit key that we used (using the default padding) we can only encrypt 245 bytes of data (2048 bits = 256 bytes and then 11 bytes are used for padding leaving 245 bytes). For a 1024 bit key you can encode 117 bytes and with a 4096-bit key you can encrypt 501 bytes.

If you need to encrypt more data than you have room for, you'll need to break up the data into smaller blocks and encrypt them separately. I do not have time to provide a sample of that right now. Keep in mind the slower speed of encryption and decryption when dealing with asymmetric crypto. They will get much slower if you must iterate over blocks of data and perform multiple encryption and decryption processes.

Decrypting Data

Decryption of the data is essentially a reversal of the encryption.


<cffunction name="decryptString" access="public" returntype="Any" output="false">
    <!--- takes in the encrypted bytes and the decryption key --->
    <cfargument name="inputBytes" type="any" />
    <cfargument name="key" type="any" />
    
    <!--- Create a Java Cipher object and get a mode --->
    <cfset var cipher = createObject('java', 'javax.crypto.Cipher').getInstance("RSA") />
    
    <!--- The mode tells the Cipher whether is will be encrypting or decrypting --->
    <cfset var decMode = cipher.DECRYPT_MODE />
    
    <!--- Return variable --->
    <cfset var returnString = "" />
        
    <!--- Initialize the cipher with the mode and the key --->    
    <cfset cipher.init(decMode, key) />
    
    <!--- Perofrm the decryption --->
    <cfset returnString = cipher.doFinal(arguments.inputBytes, 0, len(arguments.inputBytes)) />

    <!--- Convert the bytes back to a string and return it --->
    <cfreturn toString(returnString, "UTF8") />
    
</cffunction>

In this case, we pass in the encrypted bytes and the private key and set a Cipher object to DECRYPT mode. The decryption process then returns decrypted bytes that we can convert back to a string using ColdFusion's toString() function.

Conclusion

This experiment was a lot of fun, and I learned a lot by doing it. I hope it can benefit some people out there that may have needed this functionality. Again, if you have corrections or can add to this post in anyway, please speak up.

Comments
Adam Presley's Gravatar A good read, and nice into to asymmetric crypto. Haven't played with the asymmetric stuff myself before.
# Posted By Adam Presley | 7/19/10 9:54 AM
Justin's Gravatar Jason,

I'm getting an error with categories and sending mail. it looks like your DB instance is out of storage space. I doubt this comment will work.
# Posted By Justin | 7/19/10 12:31 PM
Jason Dean's Gravatar It looks like it worked. I'll look into the problem. We'll see if I get an error with this comment.
# Posted By Jason Dean | 7/19/10 1:13 PM
Jason Dean's Gravatar @justin,

thanks for letting me know. Looks one of my backup applications was not clearing its temp directory. Hopefully this is resolved now.
# Posted By Jason Dean | 7/19/10 1:19 PM
Rob Dudley's Gravatar This is a great intro to a pretty heavy duty topic - thanks! Ran across Bouncy castle a few years ago and struggled with a pure Java implementation. Am definitely going to revisit with CF to do the heavy lifting!
# Posted By Rob Dudley | 7/19/10 5:32 PM
Paul Hopkinson's Gravatar Just wanted to place on record my thanks to Jason for getting this working with me - it was my initial question that raised the
issue.

Thanks for going the extra mile with me to get it working Jason - much appreciated!
# Posted By Paul Hopkinson | 7/26/10 4:10 PM
Jason Dean's Gravatar For those interest, Paul and I spent a lot of time on e-mail and in IM working out getting this working and figuring out how to deflate, store, and reinflate the keys. It was a real learning experience for both of us.

When I get a chance, I will blog about what I learned.
# Posted By Jason Dean | 7/26/10 9:01 PM
asim's Gravatar how you call the encryptString()?

What I need is to encryp my text and send it along with the public key. how do I use this above example in my CF code...

thanks
# Posted By asim | 11/23/10 11:50 AM
Jason Dean's Gravatar @Asim,

I am not sure I understand the question.

Why would you need to send the public key along with the encrypted string? The public key is worthless at that point.

As for how you call the encryptString function, it would be something like:

<cfset encrypted = encryptString(stringtoEncrypt, key) />

That would return a byteArray of the encrypted value that you could then store by converting it to base64 or some other encoded version of the ByteArray.

It can be confusing, I know. I hope that helps. I really need to finish these posts.
# Posted By Jason Dean | 11/23/10 12:29 PM
asim's Gravatar thanks for your quick reply.

The caller call my webservice with some random text. I will add that text into my sessionsid and encrypt with my private key and return that to the caller.

Then the caller will decrypt this to the public key and match the random text he sent. if that matches the handshake process complete.

I did all the way to the encryption part but how do I send my public key to the caller?
# Posted By asim | 11/24/10 5:54 AM
Jason Dean's Gravatar Well, since the Public Key is a Java object, you'll need to find a way to serialize it and the other side will need to figure out a way to deserialize it.

Although, I have to admit, I feel leery about you providing the PK with the response. If someone could get into the middle of that scenario, what would prevent them from creating a random number of their own, signing it with their own private key, and returning their own public key?
# Posted By Jason Dean | 11/24/10 8:19 AM
asim's Gravatar You are right.
The actually we gonna generate the public/private key one time and then use that to decrypt / encrypt on both ends.
# Posted By asim | 11/24/10 2:00 PM
Brook's Gravatar After doing this:

<cfset privateKey = kp.getPrivate() />

How do you get the private key STRING.
# Posted By Brook | 2/10/11 5:20 PM
Jason Dean's Gravatar Brook,

That is a post I was planning to do for a while and I forget. Thanks for the reminder. In the meantime, here is what I came up with to help Paul (the original emailer).

This snippet applies to the public key, but you should be able to do the same with the private key (except you would use PKCS8EncodedKeySpec instead of the X509EncodedKeySpec and you would use the generatePrivate() method of the KeyFactory instead of generatePublic()).

<!--- Retreive a Base64 Encoded version of the key. You can store this string --->
<cfset byteArrayString = toBase64(application.publicKey.getEncoded()) />

<!--- When you retreive the string fromthe DB, convert it to a byteArray --->
<cfset byteArray = toBinary(byteArrayString) />

<!--- Using these Java classes and methods, reinflate the public key object --->
<cfset specs = createObject("java","java.security.spec.X509EncodedKeySpec").init(byteArray) />
<cfset application.newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePublic(specs) />

Hope this helps.
# Posted By Jason Dean | 2/11/11 10:20 AM
jdB's Gravatar Thanks Jason,

your post is very helpful.

One issue I am running in to :
Statement:
<cfset application.newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePublic(specs) />

causes:
Unknown key spec: Could not decode public key from B

After googling there were some hints that the specs variable
has a type mismatch but this is where my comfort zone ends.

Not sure if the following question is outside your comfort zone:
after transfering the public key to a client-windows app
(through a web-service call); is the public key information
enough to re-build the Key-Object in .net?

thanks in advance,
# Posted By jdB | 2/15/11 6:04 PM
jdb's Gravatar oops, that error message got truncated; it should be

Unknown key spec: Could not decode public key from BER.
# Posted By jdb | 2/15/11 6:05 PM
Jason Dean's Gravatar jdb,

I'm not sure, I have not come across that one before. Are you ensuring that you are using the encoded public key along with the X509EncodedKeySpec and the getPublic() method? And not accidentally using the encoded private key?
# Posted By Jason Dean | 3/1/11 11:54 AM
Guillermo's Gravatar When I run the cfc I just get "The init method was not found"
making reference to this line <cfset cipher.init(encMode, key) />
How can I solve this? I don't have much experience mixing java
with coldfusion :(
# Posted By Guillermo | 5/25/11 2:38 AM
Jason Dean's Gravatar Typically, I have found, that when you get that error message when calling Java methods it is because you are not passing in the correct method signature. In other words, one of your arguments it wrong. Perhaps your key argument is of the wrong type.

Check to make sure you are passing in the key as an unmodified Java object. It cannot be a string or Base64 encoded object.

I've been meaning to do a post on how to store the key object as a string and then retrieve it and re-inflate the object. If you need that code, send me an email using my contact form and I will send it to you.
# Posted By Jason Dean | 5/25/11 12:14 PM
James Morrow's Gravatar Jason,

When using this and running your example.cfm on a Mac (10.7) and Railo 3.x, I'm receiving an error during the sample decrypt:

javax.crypto.BadPaddingException
"Data must start with zero"

Other than ensuring there was a valid instance of javaLoader, I haven't modified your example at all. I did some googling, but it was relatively inconclusive - I think most of the common instances of this don't match this circumstance.

Any ideas? Seen something like this before?
# Posted By James Morrow | 8/4/11 8:07 AM
Jason Dean's Gravatar At what point are you getting the error? Is it during the key generation (Where JavaLoader is being used) or during the encryption or decryption process?

Does it happen on a particular line?

I have not used OSX 10.7 or Railo 3.x for anything, so I am not sure if either of them could be the problem or not.
# Posted By Jason Dean | 8/4/11 10:08 AM
Charlie's Gravatar This is really great. I would love to see a way to sign a String and have a client use openssl to verify it with the public key.
# Posted By Charlie | 9/5/11 1:35 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner