Friday, September 14, 2012

Default transformations for AES, DES, DESede (Triple DES) in Java


I posted on StackOverflow regarding the default transformation used for AES in Java (http://stackoverflow.com/questions/6258047/java-default-crypto-aes-behavior/10084030). Initially I couldn't find any mention of it in the JDK documentation, but I recently stumbled upon it (http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#trans).

I was working with a vendor who was using .NET to emulate the AES encryption on a sensitive value that my Java web service is requiring as input. He kept asking about things like what mode e.g. ECB, CBC and what padding to use e.g. no padding or PKCS. I checked the Java code used to encrypt, and the only input needed is "AES":

Cipher cipher = Cipher.getInstance("AES");

So I dug deeper into the JDK documentation and found out that to pass the mode and padding, you can pass it in one string e.g. AES/CBC/NoPadding. From the documentation, I took the recommended transformations (http://docs.oracle.com/javase/6/docs/technotes/guides/security/StandardNames.html) and wrote a JUnit test to compare the cipher texts of the various transformation options against the default plain-vanilla "AES".

I've extended it further to include DES and DESede (Triple DES). Running it on Oracle JDK 7 yielded the following output (the transformation that produced the same cipher text value as the default transformation is in bold):

AES/ECB/PKCS5Padding: true
AES/ECB/NoPadding: Input length not multiple of 16 bytes
AES/CBC/NoPadding: Input length not multiple of 16 bytes
AES/CBC/PKCS5Padding: false
==========================================================
DES/CBC/NoPadding: Input length not multiple of 8 bytes
DES/CBC/PKCS5Padding: false
DES/ECB/NoPadding: Input length not multiple of 8 bytes
DES/ECB/PKCS5Padding: true
==========================================================
DESede/CBC/NoPadding: Input length not multiple of 8 bytes
DESede/CBC/PKCS5Padding: false
DESede/ECB/NoPadding: Input length not multiple of 8 bytes
DESede/ECB/PKCS5Padding: true
==========================================================

The JUnit test for reference:

package org.gizmo.crypto;

import java.security.Provider;
import java.security.Security;
import java.util.ArrayList;
import java.util.List;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;

import org.apache.commons.codec.binary.Hex;
import org.junit.Before;
import org.junit.Test;

public class DefaultEncryptionAlgoTests
{
 private List<MyCipher> myCiphers = new ArrayList<MyCipher>();
 
 private String plainText = "Sticks and stones may break my bones but coding makes me joyous";

 private class MyCipher
 {
  public String cipherName;
  public String[] algorithms;
  public SecretKey secretKey;
 }
 
 @Before
 public void generateKeys() throws Exception
 {
  //AES
  MyCipher c = new MyCipher();
  c.cipherName = "AES";
  c.algorithms = new String[] {"AES/ECB/PKCS5Padding", "AES/ECB/NoPadding", "AES/CBC/NoPadding", "AES/CBC/PKCS5Padding"};
  KeyGenerator kgen = KeyGenerator.getInstance(c.cipherName);
  kgen.init(128);
  c.secretKey = kgen.generateKey();
  myCiphers.add(c);
  
  //DES
  c = new MyCipher();
  c.cipherName = "DES";
  c.algorithms = new String[] {"DES/CBC/NoPadding", "DES/CBC/PKCS5Padding", "DES/ECB/NoPadding", "DES/ECB/PKCS5Padding"};
  kgen = KeyGenerator.getInstance(c.cipherName);
  kgen.init(56);
  c.secretKey = kgen.generateKey();
  myCiphers.add(c);
  
  //DESede (or Triple DES)
  c = new MyCipher();
  c.cipherName = "DESede";
  c.algorithms = new String[] {"DESede/CBC/NoPadding", "DESede/CBC/PKCS5Padding", "DESede/ECB/NoPadding", "DESede/ECB/PKCS5Padding"};
  kgen = KeyGenerator.getInstance(c.cipherName);
  kgen.init(168);
  c.secretKey = kgen.generateKey();
  myCiphers.add(c);
 }

 @Test
 public void testSecurityProvider() throws Exception
 {
  for (Provider provider: Security.getProviders())
  {
   System.out.println(provider.getName());
   for (String key: provider.stringPropertyNames())
   {
    System.out.println("\t" + key + "\t" + provider.getProperty(key));
   }
  }
 }
 
 @Test
 public void testAlgorithms() throws Exception
 {
  for(MyCipher c :  myCiphers)
  {
   //Default algorithm
   Cipher cipher = Cipher.getInstance(c.cipherName);
   cipher.init(Cipher.ENCRYPT_MODE, c.secretKey);
         byte[] cipherText = cipher.doFinal(plainText.getBytes());
         String defaultAlgoEncryptedHex = Hex.encodeHexString(cipherText);
   
   //Possible algorithms
   for(String a : c.algorithms)
   {
    try
    {
     cipher = Cipher.getInstance(a);
     cipher.init(Cipher.ENCRYPT_MODE, c.secretKey);
     cipherText = cipher.doFinal(plainText.getBytes());
     
           String encryptedHex = Hex.encodeHexString(cipherText);
           
           System.out.println(a + ": " + defaultAlgoEncryptedHex.equals(encryptedHex));
    }
    catch (Exception e)
    {
     System.out.println(a + ": " + e.getMessage());
    }
   }   
   System.out.println("==========================================================");
  }
 }
}


Anyway, to make things clear in your program, it's best to specify the full string rather than relying on default transformations in Java.

No comments: