Pages

Wednesday, September 21, 2011

JCE Exploration Part 2

As I explained in my prior post I'm trying to figure out how to use the Java Cryptography classes to encrypt a string using AES.

I have a little test that passes but doesn't actually do any encryption. How shall I proceed?

Since I know nothing about this space, I do a bit of searching on the web to find out what I need to do to get started. Let's just see what might come up. Hey! That stack overflow entry looks to be just the ticket. What can I do with that?

After copying the code fragment from the posting, it doesn't compile. IntelliJ is giving me warnings about exceptions. Here is what I have for my encrypt function after adding try/catch to the sample:

private String encrypt(String plainText) {
byte[] sessionKey = null; //Where you get this from is beyond the scope of this post
byte[] iv = null; //Ditto
byte[] ciphertext = null;
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
ciphertext = cipher.doFinal(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return ciphertext.toString();
}

Running the test doesn't work. It is complaining about a 'Missing argument' in the SecretKeySpec constructor. Well, there are those caveats about we'll need a byte array for the key and the vector. I can make up a vector, but how should I initialize the key buffer (sessionKey) with something realistic? It looks like there is a KeyGenerator class. So, with initializing the vector and using the KeyGenerator class to set my byte array looks like:
byte[] sessionKey = null;
byte[] iv = new byte[] { 0x7F, 0x6E, 0x5D, 0x4C, 0x3B, 0x2A, 0x19, 0x08 };
byte[] ciphertext = null;
Cipher cipher;
try {
KeyGenerator kGen = KeyGenerator.getInstance("AES");
kGen.init(128);
sessionKey = kGen.generateKey().getEncoded();
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
ciphertext = cipher.doFinal(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {

When I run the test, however, I get a useful error message:
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
That is easy enough to fix, I'll just add some more bytes to my array....
And, I get another good error message!
org.junit.ComparisonFailure: Expected :Original Plaintext Actual :[B@5e7808b9

Can it really be that easy? Do I just have to reverse the process in my decrypt and I'll have end to end decryption?
public class JCEExampleTest {
private byte[] sessionKey = null;
private final byte[] iv = new byte[] { 0x7F, 0x6E, 0x5D, 0x4C, 0x3B, 0x2A, 0x19, 0x08,
0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x00 };
@Test
public void decryptingCiphertextShouldReturnOriginalPlaintext() {
assertEquals("Original Plaintext", decrypt(encrypt("Original Plaintext")));
}
private String decrypt(String cipherText) {
byte[] plaintext = null;
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
plaintext = cipher.doFinal(cipherText.getBytes());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return plaintext.toString();
}
private String encrypt(String plainText) {
byte[] ciphertext = null;
Cipher cipher;
try {
KeyGenerator kGen = KeyGenerator.getInstance("AES");
kGen.init(128);
sessionKey = kGen.generateKey().getEncoded();
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
ciphertext = cipher.doFinal(plainText.getBytes());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
}
return ciphertext.toString();
}
}
And the moment of truth--running the tests...
javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher

Well, I guess I know what to do--another search!

And I see some things in this post. Hmm. UTF-8 and base64 encoding. That suggests another post from that search. (The one about Base64 on stack overflow).

private String decrypt(String cipherText) {
byte[] plaintext = null;
Cipher cipher;
try {
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
plaintext = cipher.doFinal(new sun.misc.BASE64Decoder().decodeBuffer(cipherText));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
} catch (IOException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
return plaintext.toString();
}
private String encrypt(String plainText) {
byte[] ciphertext = null;
Cipher cipher;
try {
KeyGenerator kGen = KeyGenerator.getInstance("AES");
kGen.init(128);
sessionKey = kGen.generateKey().getEncoded();
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
//You can use ENCRYPT_MODE or DECRYPT_MODE
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(sessionKey, "AES"), new IvParameterSpec(iv));
ciphertext = cipher.doFinal(plainText.getBytes("utf-8"));
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
} catch (InvalidAlgorithmParameterException e) {
e.printStackTrace();
} catch (IllegalBlockSizeException e) {
e.printStackTrace();
} catch (BadPaddingException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
return new BASE64Encoder().encode(ciphertext).toString();
}

OK. So at this point I'm closer but still have a problem. The decryption is not working. But that is enough for this post. I'll mull on it for a bit. I think first I should clean up the code some and that may make it easier to spot my issue.

Stay tuned.

No comments:

Post a Comment