Friday, June 6, 2014

Java Encryption, Apex Decryption

Ever had a need to decrypt something in Apex that was encrypted in an external app? Well, I just ran into a need for it where an external Java web app generates a token that is encrypted with AES and when it is sent to Salesforce.com, the token had to be decrypted to do a bunch of stuff. There are a couple of things to note if you have a similar need -- Salesforce supports encryption/decryption using AES algorithm (128 bits, 192 bits and 256 bits) but is particular about the encryption mode and padding. The mode has to be cipher block chaining (CBC) and the padding has to be PKCS5. These are different from what Java supports by default, so it is important to pass the correct settings to initialize the cipher in the encrypting application. Secondly, the initialization vector (IV) used during AES encryption can be provided separately or as part of the encrypted text and the appropriate method in the Crypto class in Apex should be used for decrypting. Refer to the docs for the Crypto class for all the gory details - http://www.salesforce.com/us/developer/docs/apexcode/Content/apex_classes_restful_crypto.htm

Ok, now here is a sample class in Java for generating the AES 128-bit key that will be used by both the web application to encrypt and the Apex code to decrypt the token:

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.xml.bind.DatatypeConverter;

public class AESKeyGen {
public static String generatePrivateKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();
return DatatypeConverter.printBase64Binary(secretKey.getEncoded()); 
}
public static void main(String[] args) throws Exception {
String privateKey = generatePrivateKey();
System.out.println("Private Key = " + privateKey);
}
}

When run, this will produce a Base64 encoded AES private key that is 128-bits long. Needless to say, this should be kept private from the prying eyes.  

Private Key = useALumM/MAmHU1+hgsnPg==

Next is the Java class that actually encrypts the given text using this private key. In this case I'm including the IV data as part of the encrypted text by prefixing it before encoding to Base64 format.

import java.security.SecureRandom;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

public class AESEncrypt {
public static String encodeAES(String privateKey, String data) throws Exception {
final int AES_KEYLENGTH = 128;
byte[] iv = new byte[AES_KEYLENGTH / 8];
SecureRandom prng = new SecureRandom();
prng.nextBytes(iv);
Cipher aesCipherForEncryption = Cipher.getInstance("AES/CBC/PKCS5PADDING");
aesCipherForEncryption.init(Cipher.ENCRYPT_MODEnew SecretKeySpec(DatatypeConverter.parseBase64Binary(privateKey), "AES"), new IvParameterSpec(iv));
byte[] byteDataToEncrypt = data.getBytes();
byte[] byteCipherText = aesCipherForEncryption.doFinal(byteDataToEncrypt);

byte[] ivPlusCipher = new byte[iv.length + byteCipherText.length];
System.arraycopy(iv, 0, ivPlusCipher, 0, iv.length);
System.arraycopy(byteCipherText, 0, ivPlusCipher, iv.length, byteCipherText.length);
return DatatypeConverter.printBase64Binary(ivPlusCipher);
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("Usage: java AESEncrypt <private-key> <text>");
System.exit(-1);
}

System.out.println("Cipher Text = " + encodeAES(args[0], args[1]));
}
}

Here is a sample execution of the encryption class with the above private key: 

> java AESEncrypt "useALumM/MAmHU1+hgsnPg==" "Hello, (Encrypted) World!"

The output should be something similar to what you see below (it changes with each run as the IV is generated randomly each time) and again, the output includes the IV + Encrypted text in Base64 encoded format. 

Cipher Text = 86z+0A1LKPluwKEmeczUqMYMCLo6BlEOeHwx6zZh/bfsrXR8oRg2Z+csM9UHL59J

Now that we have our private key and encrypted text, the following lines of code will decrypt it in Apex (run through the Developer Console in this case) -- note the use of decryptWithManagedIV method since the encrypted text includes the IV in our case: 

Blob key = EncodingUtil.base64Decode('useALumM/MAmHU1+hgsnPg==');
Blob encData = EncodingUtil.base64Decode('86z+0A1LKPluwKEmeczUqMYMCLo6BlEOeHwx6zZh/bfsrXR8oRg2Z+csM9UHL59J');

Blob decryptedData = Crypto.decryptWithManagedIV('AES128', key, encData);

System.Debug( decryptedData.toString() );

Here is the output (from the debug log): 

18:16:50:025 USER_DEBUG [6]|DEBUG|Hello, (Encrypted) World!

That's it, feel free to reuse the code in your projects as needed. Happy Friday!

-Senthil

No comments:

Post a Comment