Tuesday, October 8, 2013

ColdFusion (Railo) and RSA Public Key Encryption

I was working on a project that involved having to encrypt some data with a public RSA key that is retrieved from a web service, used to encrypt some data and submit that data as part of a larger XML document back to the service. The data is for performing a web based credit card authorization. The card number and security code need to be individually encrypted using a public key. As this is for a web project, my web language of choice is ColdFusion. I have used it since version 4 and think that you can not beat it for power and speed of development. I just expected that this would be no problem. I was wrong...

I am fond of ColdFusion but not so fond of the most recent releases (since MX). Just did not like where it was going and was not fond of the cost. I like the earlier releases though I thought the move to a true Java core was spot-on. I kept a lookout at the various open source efforts but was not committed.

I had reason to look back into this a couple of months ago (not for this project but close) and came across the Railo project. I am impressed. The quick start version is just fantastic. Download, unzip, and launch. There you go. A local CF development environment at no cost. So far it is really compatible (with the CF versions that I like) and I have not run into any issues to speak of. What is not to love?

Back to my current project.

So, I have to retrieve a public RSA key from a remote web service (any one of many keys will be returned and so when I submit my encrypted data I have to include the ID of the key used), rebuild the public key, use the public key to encrypt sensitive card data and then submit that data to a web service for processing. I had everything done but the encryption part. I searched the web for CF examples/samples of doing RSA and most of the examples I came across assumed you are issuing the key and so show you how to make both the public and private keys and how to encode using the private key. Not a lot on rebuilding a public key. Also, a good bit of "Bouncy Castle" but I wanted to stick with the basic Java (and I had some issues with the BC solution).

To make it function, I wound up using a number of Java classes from within CF. I have stripped out most/all of the error checking and the non-encryption related code. I also slimmed down the comments to fit within the formatting limitations. Even with that, this should still be plenty to get you going and I hope it saves someone the effort I had to go through. If you already have the key parts and just need to build a public key, lines 50 through 83 are the meat and 96 to 99 do the actual data encryption.

Have fun!

1:  <!--- create a web service object --->  
2:  <cfset webService =   
3:     createobject("webservice", "https://URL/RequestHandler.svc?wsdl")>  
5:  <!--- build request for public RSA encryption key --->  
6:  <cfxml variable="xmldoc" caseSensitive="yes">  
7:     <requestHeader>  
8:        <stuff />  
9:     </requestHeader>  
10:  </cfxml>  
12:  <!--- make xml document internet safe --->  
13:  <cfset xmldoc64 = toBase64( #xmldoc# )>  
15:  <!--- make the request (via web services) --->  
16:  <cfset resultsRaw =   
17:     webService.ProcessRequest( "#xmldoc64#" )>  
19:  <!--- convert/format results into an xml object --->  
20:  <cfxml variable="resultsXmlDoc" caseSensitive="yes">  
21:     <cfoutput>#ToString( ToBinary( resultsRaw ) )#</cfoutput>  
22:  </cfxml>  
24:  <!--- check the results code to see if it worked --->  
25:  <cfif resultsXmlDoc.EncryptionKey.ResponseCode.XmlText eq "0">  
26:     <!--- now convert the encryption key information  
27:     into an xml object so it is easier to use (the  
28:     initial XML doc actually contains another XML doc  
29:     with the key info) --->  
30:     <cfxml variable="xmlResultsPublicKey" caseSensitive="yes">  
31:        <cfoutput>#resultsXmlDoc.EncryptionKey.responseMessage.PublicKey.XmlText#</cfoutput>  
32:     </cfxml>  
34:     <!--- save the parts of the RSA key data --->  
35:     <!--- service can return many keys, save ID --->  
36:     <cfset publicKeyID =   
37:        resultsXmlDoc.EncryptionKey.responseMessage.ID.XmlText>  
38:     <!--- main part of the public key (to be) --->  
39:     <cfset epublicKeyModulus =   
40:        xmlResultsPublicKey.RSAKeyValue.Modulus.XmlText>  
41:     <!--- minor part of the public key (to be) --->  
42:     <cfset epublicKeyExponent =   
43:        xmlResultsPublicKey.RSAKeyValue.Exponent.XmlText>  
44:  </cfif>  
47:  <!--- now that we have all of the parts, we need to  
48:   make a valid java RSA key object from the parts --->  
50:  <!--- create java string object for key modulus --->  
51:  <cfset encodedKeyModulus =   
52:     createObject( "java", "java.lang.String" ).init( epublicKeyModulus )>  
53:  <!--- create java string object for key exponent --->  
54:  <cfset encodedKeyExponent =   
55:     createObject( "java", "java.lang.String" ).init( epublicKeyExponent )>  
57:  <!--- need a java BigInt AND decoding on the fly --->  
58:  <cfset modulusKey =   
59:     createObject( "java", "java.math.BigInteger" ).init( 1, BinaryDecode( encodedKeyModulus.getBytes( "UTF-8" ), "base64" ) )>  
60:  <cfset exponentKey =   
61:     createObject( "java", "java.math.BigInteger" ).init( 1, BinaryDecode( encodedKeyExponent.getBytes( "UTF-8" ), "base64" ) )>  
63:  <!--- build the KeySpec and load it with key parts --->  
64:  <cfset javaKeySpec =   
65:     createObject( "java", "java.security.spec.RSAPublicKeySpec" ).init( modulusKey, exponentKey )>  
67:  <!--- build RSA key factory for rebuilt public key --->  
68:  <cfset javaKeyObject =   
69:     createObject( "java", "java.security.KeyFactory" ).getInstance( "RSA" )>  
71:  <!--- use the keyspec to initialize the key factory,  
72:   instantiating a RSA public key from its encoding and  
73:   recreating a valid public key --->  
74:  <cfset javaKey = javaKeyObject.generatePublic( javaKeySpec )>  
77:  <!--- create a new java cipher object and load it with  
78:   our new (rebuilt) RSA public key --->  
80:  <!--- mode tells the Cipher we will be encrypting --->  
81:  <cfset cipher =   
82:     createObject( "java", "javax.crypto.Cipher" ).getInstance( "RSA" )>  
83:  <cfset i = cipher.init( cipher.ENCRYPT_MODE, javaKey )>  
85:  <!--- convert strings into java strings --->  
86:  <cfset stringCardData =   
87:     createObject( "java", "java.lang.String" ).init( Session.data.card )>  
88:  <cfset stringCvvData =   
89:     createObject( "java", "java.lang.String" ).init( Session.data.cvv )>  
91:  <!--- ensure strings are the proper character set --->  
92:  <cfset stringCardDataBytes = stringCardData.getBytes( "UTF8" )>  
93:  <cfset stringCvvDataBytes = stringCvvData.getBytes( "UTF8" )>  
95:  <!--- now to perform the encryption on each string --->  
96:  <cfset encryptedCardData =   
97:     cipher.doFinal( stringCardDataBytes, 0, len( StringCardData ) )>  
98:  <cfset encryptedCvvData =   
99:     cipher.doFinal( stringCvvDataBytes, 0, len( StringCvvData ) )>  
101:  <!--- base64 them so they are internet safe --->  
102:  <cfset base64CardData =   
103:     BinaryEncode( encryptedCardData, "base64" )>  
104:  <cfset base64CvvData =   
105:     BinaryEncode( encryptedCvvData, "base64" )>  
107:  <!--- DONE, we should now have proper, public key  
108:   encrypted data! Congratulations! --->  

I then include the web safe, encrypted strings in my larger XML document, BineryEncode the entire thing and submit.

Good luck and hope this helps!

Update: 5/12/2016

OK, looks like Railo is dead and a replacement is available called Lucee. I am just starting to use it so can not say much about it at this time. If I get a chance [and think about it], I will update with additional information.

No comments:

Post a Comment