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.

Working with Keys

The keys that you are working with in the above code are not strings, like we are use to dealing with in CF crypto, they are full-blown Java objects with methods and properties of their own, so we cannot simply store them to the database or to a text file as is. We must first encode them into something we can work with.


<!--- Retreive a Base64 Encoded version of the key. You can store this string where ever you need, like a DB --->
<cfset byteArrayString = toBase64(publicKey.getEncoded()) />

Now that we have a Base64 encoded version of the public key, we can do the same for the private key.


<!--- Retreive a Base64 Encoded version of the key. You can store this string where ever you need, like a DB --->
<cfset byteArrayString = toBase64(privateKey.getEncoded()) />

Those to strings can then be stored where ever it is you feel you should store them. Remember to keep your private key safe.

Later, when we need to reinflate those encoded objects into the Java objects that we need to work with we can do the following:


<!--- Later, when you need to recreate that key object fromt he stored string --->
<!--- 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) />

The above example is for the public key. To work with the private key I believe the only difference is the last line will use the generatePrivate() method instead.

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
Paul's Gravatar If using the javaloader, where do you recommend storing the JAR file? And do we instantiate using the same dot notation as for a cfc?
# Posted By Paul | 6/8/12 3:50 AM
Paul's Gravatar Hey Jason,

For the Xero API, we obtain consumer and secret keys on registration.
We need to sign requests with RSA-SHA1. So, rather than generating a random key pair, I guess we need to use the provided keys for the signature:

consumer_key = 'XXXXXX8K0JU2RLC1KWP152QPQBLJXK';
consumer_secret = XXXXXXPLMY7GAVMF5N8A2VR45DDVVP';
provider_url = 'https://api.xero.com/api.xro/2.0/Contacts';

How would this be accomplished?
# Posted By Paul | 6/8/12 5:05 AM
Jason's Gravatar @paul

If using JavaLoader you can put the jar anywhere you want. That's the nice part about JavaLoader. As for instantiation, you would use the same dot notation as I've shown above. It's not a CFC though, it is still java package and class notation.

As for your question about recreating keys from those encoded strings, that is another blog post I have been planning since I wrote this one. Feel free to contact me through my contact for and I can try to dig up the code and send it to you.
# Posted By Jason | 6/8/12 11:07 AM
setare's Gravatar Hello,
Could you please send to me the code on how to store the key object as a string and then retrieve it and re-inflate the object?I get the error "The init method was not found" when I use <cfset cipher.init(encMode, key) />.
your help would be greatly appreciated!
# Posted By setare | 2/4/13 4:35 AM
Jason's Gravatar @setare and anyone else that asked,

I have updated my blog post to include a final section on working with keys that should address your needs.
# Posted By Jason | 2/4/13 9:47 AM
setare's Gravatar Thank you Jason,
As I have a private key which is something like this : "<RSAKeyValue><Modulus>7YOvT+8/q8............." which is given us by bank;I dont need to create public and private key.I should just encrypt data with this key and post to bank page.I have used your encrypting function but the key format that I pass to is incorrect because I give the error "The init method was not found" .Could you please help me?
# Posted By setare | 2/5/13 12:33 AM
Jason's Gravatar Is the 'key' value that you are passing to the init() method a string or a com.rsa.jsafe.provider.JSA_RSAPrivateKey object?

If it is a string, then you *do* need to create a key *from* that string using the method I outlined. I am not talking about making a whole new key, I am talking about converting the string to make a key object that can be used with the cipher.

I believe that the value you have there (after the <RSAKeyValue><Modulus> tags) is the base64 value we need. I have never tried this from a pem file, but if you parse out that value and create the key object using the method I describe in the post I believe you'll be able to send *that* object to cipher.init().
# Posted By Jason | 2/5/13 8:38 AM
Andrew's Gravatar In reply to James Morrow
(http://www.12robots.com/index.cfm/2010/7/19/Using-...)

I was having the same problem using Railo.
I cured it by changing this line in decryption function:
<!--- Perforrm the decryption --->
<cfset returnString = cipher.doFinal(arguments.inputBytes) /> <!--- , 0, len(arguments.inputBytes) --->
notice I am only passing in inputBytes to doFinal().....
# Posted By Andrew | 8/8/13 12:32 PM
Andrew's Gravatar I'm trying to "reinflate" the public key object and am getting this error:
"No matching Constructor for java.security.spec.X509EncodedKeySpec(string) found"

any help appreciated.

[code]
<cfset specs = createObject("java","java.security.spec.X509EncodedKeySpec").init(publicKey) />
<cfset newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePublic(specs) />
[/code]
# Posted By Andrew | 8/9/13 12:07 PM
Jason Dean's Gravatar The X509EncodedKeySpec constructor is expecting a ByteArray, not a string.

Try this instead.

<cfset byteArray = toBinary(publicKey) />

<cfset specs = createObject("java","java.security.spec.X509EncodedKeySpec").init(byteArray) />
<cfset newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePublic(specs) />
# Posted By Jason Dean | 8/9/13 2:48 PM
Andrew's Gravatar many thanks, that works!
# Posted By Andrew | 8/9/13 3:01 PM
Andrew's Gravatar To re-inflate the private key do this:

<cfset specs = createObject("java","java.security.spec.PKCS8EncodedKeySpec").init(byteArray) />
<cfset newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePrivate(specs) />
# Posted By Andrew | 8/28/13 2:40 PM
Patrick's Gravatar This has been extremely helpful - thank you for posting it! I have this solution working with the BCprov-jdk16-145.jar file as you have it.

I ran into a problem though when I tried to download the latest jar file, bcprov-jdk15on-149.jar, from Bouncy Castle. It appears that some of the class names have changed from v1.47 on. So this code example didn't work with it. However, I was able to find an older bcprov-jdk16-145.jar somewhere else that does work. But my question is (because I'm by no means a java guy), would the newer jar file work as long as the class names changed in the cf code here or is Bouncy Castle's rewrite to the ASN.1 library going to cause a re-write of this CF interaction with it to work with the newer jar files?
# Posted By Patrick | 11/30/13 5:50 PM
Jason Dean's Gravatar @Patrick,

The only thing I am using BouncyCastle for is key generation. The rest is all standard JCA/JCE.

It looks like the proper class for RSA keygen is now:

org.bouncycastle.crypto.generators.RSAKeyPairGenerator

So I believe you would replace these two lines:
<!--- 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") />

With this line:

<cfset kpgo = createObject('java', 'org.bouncycastle.crypto.generators.RSAKeyPairGenerator') />

I have not tested it, but I think that is the only change you should need to make.
# Posted By Jason Dean | 11/30/13 7:20 PM
Mike Simone's Gravatar Hoping someone might still see this and offer help. I'm trying to build a simple CF function to encrypt a file using an RSA public key generated from a different server and available to me in a file (as XML). The other server will be decrypting the file using the private key generated along with the public one.

I must be missing something in how I'm trying to USE the public key, since I'm getting an error on the init() call. If I'm starting with a plain XML file containing my key, what should I do to it in order to be able to feed it into the API?

Also, the docs from my software provider explaining how I have to encrypt this file reference the RSACryptoServiceProvider class of the Microsoft .NET Framework. They say to "set OAEP Padding to FALSE". Any idea how that would be specified in this API?
# Posted By Mike Simone | 4/10/14 4:01 PM
Jason Dean's Gravatar @mike

You'll need to parse the encoded key out of the XML and reinflate it as a proper PublicKey object.

Something like this:

<cfset byteArray = toBinary(publicKeyString) />

<cfset specs = createObject("java","java.security.spec.X509EncodedKeySpec").init(byteArray) />
<cfset newKey = createObject('java', 'java.security.KeyFactory').getInstance("RSA").generatePublic(specs) />

The encoded string you are looking for looks something like this:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0
FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/
3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9T04ZZwIDAQAB
-----END PUBLIC KEY-----

It may or may not include the "-----BEGIN PUBLIC KEY-----" and "-----END PUBLIC KEY-----" stuff, but I believe you want to strip that out if it does.
# Posted By Jason Dean | 4/14/14 11:24 AM
Mike Simone's Gravatar Thanks, I found something else that pointed to a similar approach, and that worked for getting the key imported, but then I ran into the challenge of trying to encrypt a file that's more than 117 bytes. I understand that I need to break it into chunks, but my attempts to do so in blocks or by character all ran into errors. Anybody have examples of best practices around this?
# Posted By Mike Simone | 4/14/14 12:53 PM
Jason Dean's Gravatar You'd have to loop over the byteArray of the data you are trying to encrypt, grabbing the next X bytes and encrypting each on individually.

This type of cryptography isn't the best for large data sets, for this reason and because it is slow.

I don't know if it is possible, but it would likely be better if you can use the async crypto for exchanging a symmetric key for actually enciphering the data.

Here is some code for how you would break up the larger data into smaller, encryptable chunks.

https://gist.github.com/twelverobots/10674905
# Posted By Jason Dean | 4/14/14 2:36 PM
Andrew's Gravatar Jason mentioned two good points about Public / Private keys (asymmetric encryption):

1. Not designed for large amounts of data.
2. Is slow.

1. I've found you need to limit the size of what you want to encrypt:
(key size)
2048 - Data must not be longer than 245 bytes
1024 - "" "" 117 bytes
512 - "" "" 53 bytes

2. Asymmetric encryption is approx 1000 times slower than Symmetric
which is why it is usually used to exchange Symmetric keys.

Andrew.
# Posted By Andrew | 6/2/14 8:37 PM
BlogCFC was created by Raymond Camden. This blog is running version 5.9.1. Contact Blog Owner