10
\$\begingroup\$

This is the first time I've written a class in Java to do encryption using AES. Since security is involved I would love it if someone could take a look at it and let me know if anything is wrong with my implementation. Additionally, any feedback about shortcuts I could have taken or sloppy processes I've used would be greatly appreciated!

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class Encrypt {

    private Cipher encryptCipher;
    private SecretKey key;
    private IvParameterSpec iv;

    private byte[] clearText;

    /**
     * Encrypts data using AES-128
     * @param clearText The data to be encrypted
     */
    public Encrypt(String clearText) {

        //Generate the IV and key
        this.iv = new IvParameterSpec(this.generateIv());
        this.key = this.generateKey();

        //Create an AES cipher in CBC mode using PKCS5 padding
        try {
            this.encryptCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

            try {
                this.encryptCipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv);
            }
            catch(InvalidKeyException ex) {
                Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            }
            catch(InvalidAlgorithmParameterException ex) {
                Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        catch(NoSuchAlgorithmException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
        catch(NoSuchPaddingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        }

        //Convert the clear text passed by the user into bytes
        try {
            this.clearText = clearText.getBytes("UTF-16");
        }
        catch(UnsupportedEncodingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    /**
     * Generates a random IV to be used in the encryption process
     * @return The IV's byte representation
     */
    private byte[] generateIv() {
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[16];
        random.nextBytes(ivBytes);
        return ivBytes;
    }

    /**
     * Generates a secret key to be used in the encryption process
     * @return The secret key
     */
    private SecretKey generateKey() {
        KeyGenerator keygen;
        try {

            //Java normally doesn't support 256-bit key sizes without an extra installation so stick with a 128-bit key
            keygen = KeyGenerator.getInstance("AES");
            keygen.init(128);
            SecretKey aesKey = keygen.generateKey();
            return aesKey;
        }
        catch(NoSuchAlgorithmException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Encrypts the data passed during instantiation of this object
     * @return The byte representation of the encrypted data
     */
    public byte[] encrypt() {
        try {
            return this.encryptCipher.doFinal(this.clearText);
        }
        catch(IllegalBlockSizeException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        catch(BadPaddingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

}
\$\endgroup\$
4
  • \$\begingroup\$ My first suggestion is to make refactor a little bit your constructor. Start by making a Method getCipherInstance() put your try catches in that method. Then make another method initializeCipher() This will make your constructor easier to read. As for your random IV generator you might want to consider a lesson from how Mifare Desfire passes enciphered data. You basically start with a IV of 0x00 and have a key that is known elsewhere. In the end hacking is much more difficult. \$\endgroup\$ Apr 27, 2013 at 15:36
  • \$\begingroup\$ Thanks, good idea. I've refactored a little bit and moved a lot of the work that was being done in the constructor into separate methods. \$\endgroup\$
    – Aaron
    May 2, 2013 at 21:00
  • \$\begingroup\$ If you have found a good solution/answer to your question then you should consider adding an answer here. It will be beneficial to everyone. Self-answering you question is valid, and encouraged. \$\endgroup\$
    – rolfl
    Dec 4, 2013 at 5:52
  • \$\begingroup\$ One thing I wanted to point out is that I did NOT use a fixed IV with 0x00 bytes. I'm not sure why anyone suggests using a constant IV as it defeats the entire purpose of an IV. The IV is supposed to be random for each encryption operation, no? \$\endgroup\$
    – Aaron
    Sep 3, 2014 at 13:39

1 Answer 1

3
\$\begingroup\$

Here's my final implementation with the changes based on @Robert Snyder's comment:

My first suggestion is to make refactor a little bit your constructor. Start by making a Method getCipherInstance() put your try catches in that method. Then make another method initializeCipher() This will make your constructor easier to read. As for your random IV generator you might want to consider a lesson from how Mifare Desfire passes enciphered data. You basically start with a IV of 0x00 and have a key that is known elsewhere. In the end hacking is much more difficult.

import com.sun.org.apache.xml.internal.security.utils.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public class Encrypt {

    private Cipher encryptCipher;
    private SecretKey key;
    private IvParameterSpec iv;

    private byte[] clearText;
    private byte[] encryptedText;

    /**
     * Encrypts data using AES-128
     * @param clearText The data to be encrypted
     */
    public Encrypt(String clearText) {

        //Generate the IV and key
        this.iv = new IvParameterSpec(this.generateIv());
        this.key = this.generateKey();
        this.encryptCipher = createCipher();
        this.clearText = this.convertClearText(clearText);
        this.encryptedText = this.encrypt();
    }

    /**
     * Converts the clear text passed by the user to an array of bytes
     * @param clearText The clear text passed by the user
     * @return The byte representation of the clear text
     */
    private byte[] convertClearText(String clearText) {

        //Convert the clear text passed by the user into bytes
        try {
            return clearText.getBytes("UTF-8");
        }
        catch(UnsupportedEncodingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Creates an AES cipher using CBC mode with PKCS5 padding
     * @return The cipher used to encrypt data
     */
    private Cipher createCipher() {

        //Create an AES cipher in CBC mode using PKCS5 padding
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv);
            return cipher;
        }
        catch(NoSuchAlgorithmException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        catch(NoSuchPaddingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        catch(InvalidKeyException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        catch(InvalidAlgorithmParameterException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Generates a random IV to be used in the encryption process
     * @return The IV's byte representation
     */
    private byte[] generateIv() {
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[16];
        random.nextBytes(ivBytes);
        return ivBytes;
    }

    /**
     * Generates a secret key to be used in the encryption process
     * @return The secret key
     */
    private SecretKey generateKey() {
        KeyGenerator keygen;
        try {

            //Java normally doesn't support 256-bit key sizes without an extra installation so stick with a 128-bit key
            keygen = KeyGenerator.getInstance("AES");
            keygen.init(128);
            SecretKey aesKey = keygen.generateKey();
            return aesKey;
        }
        catch(NoSuchAlgorithmException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Returns the initialization vector
     * @return The randomly generated IV
     */
    public IvParameterSpec getIv() {
        return this.iv;
    }

    /**
     * Returns the key used for encryption
     * @return The randomly generated secret key
     */
    public SecretKey getKey() {
        return this.key;
    }

    /**
     * Encrypts the data passed during instantiation of this object
     * @return The byte representation of the encrypted data
     */
    public final byte[] encrypt() {
        try {
            return this.encryptCipher.doFinal(this.clearText);
        }
        catch(IllegalBlockSizeException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
        catch(BadPaddingException ex) {
            Logger.getLogger(Encrypt.class.getName()).log(Level.SEVERE, null, ex);
            return null;
        }
    }

    /**
     * Returns the encrypted text as a base64 encoded string
     * @return The encrypted base64 encoded string
     */
    @Override
    public String toString() {
        return Base64.encode(encryptedText);
    }

}

Here is the most recent version of the class. One of the suggestions was to set the IV to 0x00 which I did not do. Someone correct me if I'm wrong but I'm almost positive that it is critical to have random data for the IV for each encryption operation. I've done what others have suggested and bubbled the exceptions up. The constructor has also been cleaned up quite a bit and there's now a toString() method that returns the data as a base64 encoded string.

import org.apache.commons.codec.binary.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

public final class Encrypt {

    private Cipher encryptCipher;
    private SecretKey key;
    private IvParameterSpec iv;

    private byte[] clearText;
    private byte[] encryptedText;

    /**
     * Encrypts data using AES-128
     *
     * @param clearText The data to be encrypted
     * @throws java.security.NoSuchAlgorithmException
     * @throws javax.crypto.NoSuchPaddingException
     * @throws java.security.InvalidKeyException
     * @throws javax.crypto.BadPaddingException
     * @throws java.security.InvalidAlgorithmParameterException
     * @throws javax.crypto.IllegalBlockSizeException
     * @throws java.io.UnsupportedEncodingException
     */
    public Encrypt(String clearText) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException, UnsupportedEncodingException, IllegalBlockSizeException, BadPaddingException {

        //Generate the IV and key
        this.iv = new IvParameterSpec(this.generateIv());
        this.key = this.generateKey();
        this.encryptCipher = createCipher();
        this.clearText = this.convertClearText(clearText);
        this.encryptedText = this.encrypt();
    }

    /**
     * Converts the clear text passed by the user to an array of bytes
     *
     * @param clearText The clear text passed by the user
     * @return The byte representation of the clear text
     */
    private byte[] convertClearText(String clearText) throws UnsupportedEncodingException {

        //Convert the clear text passed by the user into bytes
        return clearText.getBytes("UTF-8");
    }

    /**
     * Creates an AES cipher using CBC mode with PKCS5 padding
     *
     * @return The cipher used to encrypt data
     */
    private Cipher createCipher() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {

        //Create an AES cipher in CBC mode using PKCS5 padding
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, this.key, this.iv);
        return cipher;
    }

    /**
     * Generates a random IV to be used in the encryption process
     *
     * @return The IV's byte representation
     */
    private byte[] generateIv() {
        SecureRandom random = new SecureRandom();
        byte[] ivBytes = new byte[16];
        random.nextBytes(ivBytes);
        return ivBytes;
    }

    /**
     * Generates a secret key to be used in the encryption process
     *
     * @return The secret key
     */
    private SecretKey generateKey() throws NoSuchAlgorithmException {
        KeyGenerator keygen;

        //Java normally doesn't support 256-bit key sizes without an extra installation so stick with a 128-bit key
        keygen = KeyGenerator.getInstance("AES");
        keygen.init(128);
        SecretKey aesKey = keygen.generateKey();
        return aesKey;

    }

    /**
     * Returns the initialization vector
     *
     * @return The randomly generated IV
     */
    public IvParameterSpec getIv() {
        return this.iv;
    }

    /**
     * Returns the key used for encryption
     *
     * @return The randomly generated secret key
     */
    public SecretKey getKey() {
        return this.key;
    }

    /**
     * Encrypts the data passed during instantiation of this object
     *
     * @return The byte representation of the encrypted data
     */
    private byte[] encrypt() throws IllegalBlockSizeException, BadPaddingException {
        return this.encryptCipher.doFinal(this.clearText);

    }

    /**
     * Returns the encrypted text as a base64 encoded string
     *
     * @return The encrypted base64 encoded string
     */
    @Override
    public String toString() {
        return Base64.encodeBase64String(encryptedText);
    }

}
\$\endgroup\$
0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.