Link to home
Start Free TrialLog in
Avatar of digitalwise
digitalwise

asked on

ColdFusion/Java Encryption.

New work that I have never done before and I have wandered around long enough.     I have a key and IVSpec from the vendor that has to be decoded, then create keys, etc to encrypt my string I am sending back to the vendor.    This is what I have now.   I am currently getting a The getBytes method was not found. error message from the <cfset keySpec line...  

<cfset Cipher = createObject("java", "javax.crypto.Cipher")>
<cfset encryptor = Cipher.getInstance("DESede/CBC/PKCS5Padding")>

<CFSET MyKey = "TheEncryptedKeyfromtheVendor">
<CFSET IVSpec = "TheEncryptedIVSpecfromtheVendor">

<CFSET MyKeyProperty = BinaryDecode(MyKey, "Base64")>
<CFSET IVProperty = BinaryDecode(IVSpec, "Base64")>

<cfset keySpec = createObject("java", "javax.crypto.spec.SecretKeySpec").init(MyKeyProperty.getBytes(), "DESede")>
<cfset ivSpec = createObject("java", "javax.crypto.spec.IvParameterSpec").init(IVProperty.getBytes(), "DESede")>

<CFSET URLString = "Mystringstuff">

<cfset temp = encrypt(URLString, keySpec, "DESede/CBC/PKCS5Padding", "Base64", ivSpec)>


<cfset encryptedTextFromJava = encryptor.doFinal(URLString.getBytes())>

<cfset encryptedText = BinaryEncode(encryptedTextFromJava, "Base64")>

Open in new window

Avatar of _agx_
_agx_
Flag of United States of America image

Can you post the java example too? Also, did they give you a sanitized sample you could post publicly?  Obviously don't post anything that uses your real encryption key!
Avatar of digitalwise
digitalwise

ASKER

This is the sample code they sent:

Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
//decode provided KEY and IV
byte[] key = Base64.decode(KEY); //KEY is Base64 encoded
byte[] iv = Base64.decode(IV); // IV is Base64 encoded
SecretKey secretkey= …; // create secret key using key byte array
IvParameterSpec ivSpec=…; //create initialization vector using the iv byte array
//initialize the cipher for encryption
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
//use base64 encoding for resulting bytes
String dataStream = Base64.encode(cipher.doFinal(sb.toString().getBytes()));
//redirect to gateway (url encode the data stream as it is being attached to the url)
getRespone().redirect(gatewayURL + "?data=" + URLEncoder.encode(dataStream));

Open in new window

EDIT:

At first glance I don't see anything obviously wrong w/the java code, assuming the omitted parts are the same.  

Why not just use CF's encrypt() function instead? It's doing the same thing internally.  You've almost got it, but it should be something like this:

     <CFSET IVProperty = BinaryDecode( Base64EncodedIVString, "Base64")>
     <cfset result = encrypt(plainString
           , base64keyString
           , "DESede/CBC/PKCS5Padding"
           , "base64"
           , IVProperty ) >
It seems to me like you're trying to access data in the binary object created by "BinaryDecode()" function using member functions ".getBytes()" (a functionality that is not available in ColdFusion but will be available in CF11), maybe check to see the contents of MyKeyProperty and address the portion of it you want to access using dot notation? (don't know what structure the binary object returned by BinaryDecode() is made up of).

Perhaps just pass MyKeyProperty to the init function instead of attempting to "getBytes()" from it?

<cfset keySpec = createObject("java", "javax.crypto.spec.SecretKeySpec").init(MyKeyProperty, "DESede")>

Open in new window

For Rodrigo - I get the error Unable to find a constructor for class javax.crypto.spec.IvParameterSpec that accepts parameters of type ( [B, java.lang.String ). when I remove Bytes property (it seems to work ok for the Key portion).

For _agx_ - I started there - but I get a An error occurred while trying to encrypt or decrypt your input string: '' Can not decode string "javax.crypto.spec.SecretKeySpec when I use that IVorSalt parameter with a straight decode.   Their sample code looks like I have to create an initialization vector based on the iv byte array...
a functionality that is not available in ColdFusion but will be available in CF11

Hm... what functionality is that? Reason for asking is getBytes() is actually a java method, so it works in any java based version of CF.  

But you're correct about using the wrong variable. I didn't look too closely at the java code once I realized it wasn't needed. But FWIW, BinaryDecode already returns a byte array. So no need to call getBytes(). Just pass in the array:

<CFSET keyByteArray = BinaryDecode(MyKey, "Base64")>
<cfset keySpec = createObject("java", "javax.crypto.spec.SecretKeySpec").init(keyByteArray, "DESede")>

You'd only use getBytes() if you were passing in a string
@digitalwise - That's because you're mixing java and CF.  It's complaining is that you're passing in java objects and that's not what the CF function is expecting.  

encrypt(URLString, keySpec, "DESede/CBC/PKCS5Padding", "Base64", ivSpec)>

Take a look at my example again.  You don't need any java objects.  Simply decode the base64 IV string into a byte array:

<!--- where Base64EncodedIVString is your iv *string* --->
<CFSET IVProperty = BinaryDecode( Base64EncodedIVString, "Base64")>

... then pass it in along with your base64 encoded key string:

     <cfset result = encrypt(plainString
           , base64keyString
           , "DESede/CBC/PKCS5Padding"
           , "base64"
           , IVProperty ) >
I was referring to "Member Functions", http://blogs.coldfusion.com/post.cfm/language-enhancements-in-coldfusion-splendor-member-functions which would enable the use of code like:

<cfset myString = "Hello World">
<cfoutput>The length of #myString# is #Len(myString)#</cfoutput>

Open in new window


To be written as:

<cfset myString = "Hello World">
<cfoutput>The length of #myString# is #myString.length#</cfoutput>

Open in new window


But my point was that BinaryDecode() was a ColdFusion function, not a java object, so a member function that would exist in the java object does not apply to an object returned from a ColdFusion function that does not implement that member function or implicit getter.

 Edit:
However, I do agree with _agx_, using encode() decode() should be better and simpler in the long run.
EDIT: Interesting, I'll have to read up on the CF object stuff. (I don't think it impacts regular java objects though).  But I think the problem here was even simpler ;-) He just accidentally swapped the variables and ended up using the wrong method on the wrong object. ie Used this:

     <CFSET MyKeyString = "TheEncryptedKeyfromtheVendor">
     <CFSET MyKeyBytes = BinaryDecode(MyKeyString , "Base64")>

         MyKeyBytes.getBytes()  <=== fails

... instead of

        MyKeyString.getBytes()

SecretKeySpec expects:   (byte[], string).  You can either generate the byte array by using binaryDecode OR using String.getBytes()  not both.  Besides, BinaryDecode already returns binary. So you don't need to call getBytes on something that's already a byte array ;-) Hence why that method doesn't exist on the resulting object.
So if I change the code to this:

<CFSET MyKey = "EncryptedKeyfromVEndor">
<CFSET IVSpec = "EncryptedIVSpecfromVendor">

<CFSET MyKeyProperty = BinaryDecode(MyKey, "Base64")>
<CFSET IVProperty = BinaryDecode(IVSpec, "Base64")>

<CFSET URLString = "MyString">

<cfset encoded = encrypt(
URLString,
MyKeyProperty,
"DESede/CBC/PKCS5Padding",
"Base64",
IVProperty
) />

Open in new window


I get the error message:  
ByteArray objects cannot be converted to strings.

It seems that in the pure java sample provided by the vendor that they are decrypted and then created a new key based on the ivspec, etc.   Feel like I am missing something easy!
ASKER CERTIFIED SOLUTION
Avatar of _agx_
_agx_
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
EDIT: Ignore this post, _agx_ is right, binaryDecode returns a binary object which is what you need for the IV parameter. Use his example in the comment above
OK - so I used what you suggested and I get an invalid data stream back when I try to connect which means I am not sending them the right stuff.    If you look at the sample code they sent over

Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
//decode provided KEY and IV
byte[] key = Base64.decode(KEY); //KEY is Base64 encoded
byte[] iv = Base64.decode(IV); // IV is Base64 encoded
SecretKey secretkey= …; // create secret key using key byte array
IvParameterSpec ivSpec=…; //create initialization vector using the iv byte array
//initialize the cipher for encryption
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
//use base64 encoding for resulting bytes
String dataStream = Base64.encode(cipher.doFinal(sb.toString().getBytes()));
//redirect to gateway (url encode the data stream as it is being attached to the url)
getRespone().redirect(gatewayURL + "?data=" + URLEncoder.encode(dataStream));

Open in new window


it looks like I should be decoding BOTH of them, then creating a new key.   Am I misreading this?
it looks like I should be decoding BOTH of them, then creating a new key.   Am I misreading this?

No, java and CF just have different ways of doing things. Java requires byte arrays for *both* the key and iv. Whereas CF requires a base64 string for the key and a byte array for the iv. It won't work any other way, Encrypt() doesn't accept a binary key.

I asked this earlier - don't know if you saw it. Did they give you a *sanitized* sample you can share?  Like an API example that uses a dummy key? Encryption is incredibly picky about even the slightest difference. It would help us cut to the chase faster if we could see and test the sample value you're trying to match.

Feels like there's something in the code we're not aware of ... like maybe the final "data" value isn't being URL encoded properly, etc...?  That's just a guess though. The thing is CF is actually doing all the same java stuff above behind the scenes (aside from using charset=UTF8), so there's really no reason you should be getting different results.  I've done it before  w/no problem ie encrypt=>java and decrypt=>CF and vice versa. So there's definitely a piece missing here...
Yeah, I think you're definitely doing something different on your end...  I ran both as a sanity check and as expected it produced the same results in java and CF10 (shouldn't make any difference).  

ColdFusion:
encrypted = fFZi2Pa0LYgpuligcw/KFw==

Java: (using dummy key)
KEY = jCBXQLnsq8Ljfz4cWLXBjyMv8XoHl/1P
IV = AAAAAAAAAAA=
text = something
encrypted = fFZi2Pa0LYgpuligcw/KFw==



ColdFusion:
<CFSET URLString = "something">
<CFSET KeyAsBase64String = "jCBXQLnsq8Ljfz4cWLXBjyMv8XoHl/1P">
<CFSET IVAsBase64String = "AAAAAAAAAAA=">

<!--- convert IV string => binary --->
<CFSET IVAsBinary = BinaryDecode(IVAsBase64String , "Base64")>

<cfset encryptedText = encrypt(
			URLString
			, KeyAsBase64String 
			,  "DESede/CBC/PKCS5Padding"
			, "Base64"
			, IVAsBinary
		) /> 

<cfoutput>encrypted  = #encryptedText#</cfoutput>

Open in new window

Java
import javax.crypto.*;
import javax.crypto.spec.*;
import org.apache.commons.codec.binary.Base64;

public class Test {

	public static void main(String[] args) {
		try {
			String text = args[0];
			String KEY = args[1]; //KEY is Base64 encoded
			String IV = args[2]; // IV is Base64 encoded
			
			Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding");
			Base64 util = new Base64();
			byte[] key = util.decode(KEY); 
			byte[] iv = util.decode(IV); 
			SecretKey secretkey = new SecretKeySpec(key, "DESede"); 
			IvParameterSpec ivSpec = new IvParameterSpec(iv); 
			//initialize the cipher for encryption
			cipher.init(Cipher.ENCRYPT_MODE, secretkey, ivSpec);
			//use base64 encoding for resulting bytes
			String dataStream = util.encodeToString(cipher.doFinal(text.getBytes()));

			System.out.printf("KEY = %s\n", KEY);  	
		    System.out.printf("IV = %s\n", IV);  	
		    System.out.printf("text = %s\n", text);  	
		    System.out.printf("encrypted = %s\n", dataStream);  	

		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

Open in new window

They sent me something to try and it is definitely working properly with your solution.    Now we need to figure out why they are rejecting the string at their end!    Thanks for your help and patience.
You're welcome :)