Wednesday, October 22, 2008

Java 128-bit/192-bit/256-bit AES Encryption

Here is a Java implementation/example code of a 128-bit/192-bit/256-bit AES encryption utility that provides sample keys you can use if no key is set and indicates what level encryption is supported by the JVM. It also changes the key to base64 encoding before returning it and expects the decrypted string to be base64 encoded. The latter is to avoid issues with storing the encrypted password as a String (which is done most commonly). Hope it helps:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.codec.binary.Base64;
import org.jasig.portlet.mail.util.encryption.EncryptionTool;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;


/**
 * Java 128 bit/192 bit/256 bit AES encryption that outputs sample keys and indicates what levels of AES encryption are
 * supported by the JVM. This is heavily based on examples from:
 * <ul>
 * <li>http://java.sun.com/developer/technicalArticles/Security/AES/AES_v1.html</li>
 * <li>http://forums.sun.com/thread.jspa?threadID=546486</li>
 * </ul>
 *
 * @author Gary S. Weaver
 */
public class AesEncryptionToolImpl implements EncryptionTool {

    private Log log = LogFactory.getLog(AesEncryptionToolImpl.class);

    private String key;

    /**
     * Gets a message containing some sample 128, 192, and 256 bit AES keys you can use, and indicates which levels of
     * AES encryption may not be supported.
     *
     * @return
     * @throws NoSuchAlgorithmException
     */
    private String getSampleKeys() throws NoSuchAlgorithmException {
        StringBuffer sb = new StringBuffer();
        KeyGenerator kgen = KeyGenerator.getInstance("AES");
        appendSampleKey(kgen, 128, sb);
        sb.append("\n\n");
        appendSampleKey(kgen, 192, sb);
        sb.append("\n\n");
        appendSampleKey(kgen, 256, sb);

        return sb.toString();
    }

    private void appendSampleKey(KeyGenerator kgen, int numBits, StringBuffer sb) {
        try {
            kgen.init(numBits);
            SecretKey skey = kgen.generateKey();
            String exampleKey = HexUtil.asHex(skey.getEncoded());
            sb.append("Example of ");
            sb.append(numBits);
            sb.append("-bit AES key: ");
            sb.append(exampleKey);
        }
        catch (Throwable t) {
            sb.append(numBits);
            sb.append("-bit AES ENCRYPTION NOT SUPPORTED");
            log.error("Problem checking " + numBits + "-bit AES encryption (maybe that level of encryption is not supported by this JVM)", t);
        }
    }

    public String encrypt(String plaintext) throws Exception {
        String result = null;
        if (key == null) {
            String sampleKeys = getSampleKeys();
            throw new Exception("Encrypt failed. Must set key property of AesEncryptionToolImpl in spring config.\n\n" + sampleKeys);
        }

        byte[] keyBytes = HexUtil.lenientHexToBytes(key);
        if (keyBytes == null || keyBytes.length < 16) {
            String sampleKeys = getSampleKeys();
            throw new Exception("Encrypt failed. Property 'key' of AesEncryptionToolImpl in spring config was invalid (for example, use a 16, 24, or 40 byte hex string for 128-bit, 192-bit, or 256-bit AES key, respectively).\n\n" + sampleKeys);
        }

        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        try {
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
            byte[] encBytes = cipher.doFinal(plaintext.getBytes());
            result = new String(new Base64().encode(encBytes));
        }
        catch (InvalidKeyException ike) {
            String sampleKeys = getSampleKeys();
            log.error("Encrypt failed. Property 'key' was invalid in AesEncryptionToolImpl in spring config.\n\n" + sampleKeys, ike);
        }

        return result;
    }

    public String decrypt(String encryptedtext) throws Exception {
        String result = null;
        if (key == null) {
            String sampleKeys = getSampleKeys();
            throw new Exception("Decrypt failed. Must set key property of AesEncryptionToolImpl in spring config.\n\n" + sampleKeys);
        }

        byte[] keyBytes = HexUtil.lenientHexToBytes(key);
        if (keyBytes == null || keyBytes.length < 16) {
            String sampleKeys = getSampleKeys();
            throw new Exception("Encrypt failed. Property 'key' of AesEncryptionToolImpl in spring config was invalid (for example, use a 16, 24, or 40 byte hex string for 128-bit, 192-bit, or 256-bit AES key, respectively).\n\n" + sampleKeys);
        }

        SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, "AES");
        Cipher cipher = Cipher.getInstance("AES");
        try {
            cipher.init(Cipher.DECRYPT_MODE, skeySpec);
            byte[] dataBytes = new Base64().decode(encryptedtext.getBytes("ASCII"));
            byte[] decrypted = cipher.doFinal(dataBytes);
            result = new String(decrypted);
        }
        catch (InvalidKeyException ike) {
            String sampleKeys = getSampleKeys();
            log.error("Decrypt failed. Property 'key' was invalid in AesEncryptionToolImpl in spring config.\n\n" + sampleKeys, ike);
        }

        return result;
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
}
And a test containing a method that will output what levels of encryption are supported by the JVM also (although typically I think 128, 192, and 256 bit AES is supported within the more major U.S. recent distributions of Java, and 128 bit AES is supported within the more major international recent distributions of Java:
import junit.framework.TestCase;
import org.jasig.portlet.mail.util.encryption.impl.AesEncryptionToolImpl;

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

/**
 * @author Gary S. Weaver
 */
public class AesEncryptionToolImplTest extends TestCase {

    private static final String KEY_128_BIT_AES = "88bdc2f87e0a197a38d108fa0cad18a3";
    private static final String KEY_192_BIT_AES = "5ebb10f16146b47f842b251fe396b2a6b892ee013cff26b6";
    private static final String KEY_256_BIT_AES = "b5f7564f7f555a1a668619bd75b4135f98fcdd98fad19da3dc29163709a0b6a5";

    public void testPasswordEncryptionDecryptionBadKey() throws Exception {
        encryptionAndDecryptionWithErrorTest("testme!11s", null);
        encryptionAndDecryptionWithErrorTest("testme!11s", "");
        encryptionAndDecryptionWithErrorTest("testme!11s", " ");
        encryptionAndDecryptionWithErrorTest("testme!11s", "!@($*$!@#");
        encryptionAndDecryptionWithErrorTest("testme!11s", "000000");
        encryptionAndDecryptionWithErrorTest("testme!11s", "1111111");
        encryptionAndDecryptionWithErrorTest("testme!11s", "ffffffff");
    }

    public void testPasswordEncryptionDecryption128Bit() throws Exception {
        encryptionAndDecryptionWithoutErrorTest("testme!11s", KEY_128_BIT_AES);
        encryptionAndDecryptionWithoutErrorTest("testuser", KEY_128_BIT_AES);
    }

    public void testPasswordEncryptionDecryption192Bit() throws Exception {
        encryptionAndDecryptionWithoutErrorTest("testme!11s", KEY_192_BIT_AES);
        encryptionAndDecryptionWithoutErrorTest("testuser", KEY_192_BIT_AES);
    }

    public void testPasswordEncryptionDecryption256Bit() throws Exception {
        encryptionAndDecryptionWithoutErrorTest("testme!11s", KEY_256_BIT_AES);
        encryptionAndDecryptionWithoutErrorTest("testuser", KEY_256_BIT_AES);
    }

    private void encryptionAndDecryptionWithoutErrorTest(String password, String key) throws Exception {
        System.out.println("Encrypting password '" + password + "' with key '" + key + "'");
        AesEncryptionToolImpl tool1 = new AesEncryptionToolImpl();
        tool1.setKey(key);
        AesEncryptionToolImpl tool2 = new AesEncryptionToolImpl();
        tool2.setKey(key);
        String encrypted = tool1.encrypt(password);
        System.out.println("Encrypted password is " + encrypted);
        String decrypted = tool2.decrypt(encrypted);
        System.out.println("Decrypted password is " + decrypted);
        assertEquals(password, decrypted);
    }

    private void encryptionAndDecryptionWithErrorTest(String password, String key) throws Exception {
        try {
            System.out.println("Encrypting password '" + password + "' with key '" + key + "'");
            AesEncryptionToolImpl tool1 = new AesEncryptionToolImpl();
            tool1.setKey(key);
            AesEncryptionToolImpl tool2 = new AesEncryptionToolImpl();
            tool2.setKey(key);
            String encrypted = tool1.encrypt(password);
            System.out.println("Encrypted password is " + encrypted);
            String decrypted = tool2.decrypt(encrypted);
            System.out.println("Decrypted password is " + decrypted);
            assertEquals(password, decrypted);
            fail("password '" + password + "' with key '" + key + "' should have thrown error");
        }
        catch (Throwable t) {
            t.printStackTrace();
            // ok
        }
    }

    public void testAES() throws Exception {

        StringBuffer sb = new StringBuffer();
        KeyGenerator kgen = KeyGenerator.getInstance("AES");

        for (int i = 0; i < 2049; i++) {
            try {
                kgen.init(i);
                SecretKey skey = kgen.generateKey();
                String exampleKey = asHex(skey.getEncoded());
                System.out.println("" + i + "-bit AES encryption supported. Sample key in hex code (" + exampleKey.length() / 2 + " bytes): " + exampleKey);
                sb.append(exampleKey);
            }
            catch (Throwable t) {
                // ignore
            }

        }
    }

    public String asHex(byte buf[]) {
        StringBuffer sb = new StringBuffer(buf.length * 2);
        for (int i = 0; i < buf.length; i++) {
            if (((int) buf[i] & 0xff) < 0x10) {
                sb.append("0");
            }

            sb.append(Long.toString((int) buf[i] & 0xff, 16));
        }
        System.out.println("asHex(" + new String(buf) + ") returned '" + sb.toString() + "'");

        return sb.toString();
    }
}

1 comment:

Dexter said...

I was looking for a website to encrypt/decrypt my data (an online AES encrypter) but this is good enough for me too since I do have a Java IDE. SecretKeySpec and Cipher is what I was looking for. Thank you.