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.
- Database/Web Server
- 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.



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.
thanks for letting me know. Looks one of my backup applications was not clearing its temp directory. Hopefully this is resolved now.
issue.
Thanks for going the extra mile with me to get it working Jason - much appreciated!
When I get a chance, I will blog about what I learned.
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
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.
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?
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?
The actually we gonna generate the public/private key one time and then use that to decrypt / encrypt on both ends.
<cfset privateKey = kp.getPrivate() />
How do you get the private key STRING.
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.
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,
Unknown key spec: Could not decode public key from BER.
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?
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 :(
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.
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?
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.