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);
}
}
getCipherInstance()
put your try catches in that method. Then make another methodinitializeCipher()
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\$