I'm writing an application for Android that uses symmetric key encryption to protect sensitive data. As far as I can tell, Android only directly supports "PBEWithMD5AndDES". How secure is this algorithm? Also, I've included my code below (non-andriod). Is my code correctly encrypting the data?
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class CipherTest
{
private static class EncryptInfo
{
private final byte[] encryptedData;
private final byte[] initVector;
private final byte[] salt;
public EncryptInfo(byte[] encryptedData, byte[] initVector, byte[] salt)
{
this.encryptedData = encryptedData.clone();
this.initVector = initVector.clone();
this.salt = salt.clone();
}
public byte[] getEncryptedData()
{
return encryptedData;
}
public byte[] getInitVector()
{
return initVector;
}
public byte[] getSalt()
{
return salt;
}
}
private static final String keyGenAlgorithm = "PBEWithMD5AndDES";
private static final String keyAlgorithm = "DES";
private static final String cipherTransform = "PBEWithMD5AndDES/CBC/PKCS5Padding";
private static EncryptInfo encrypt(char[] password, byte[] data)
throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
InvalidParameterSpecException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException
{
byte[] salt = new byte[16];
new SecureRandom().nextBytes(salt);
PBEKeySpec keySpec = new PBEKeySpec(password, salt, 1024);
SecretKeyFactory secretKeyFactory = SecretKeyFactory
.getInstance(keyGenAlgorithm);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
keySpec.clearPassword();
byte[] key = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, keyAlgorithm);
Cipher cipher = Cipher.getInstance(cipherTransform);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] initVector = cipher.getParameters().getParameterSpec(
IvParameterSpec.class).getIV();
return new EncryptInfo(cipher.doFinal(data), initVector, salt);
}
public static byte[] decrypt(byte[] data, char[] password, byte[] salt,
byte[] initVector) throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException,
IllegalBlockSizeException, BadPaddingException
{
PBEKeySpec keySpec = new PBEKeySpec(password, salt, 1024);
SecretKeyFactory secretKeyFactory = SecretKeyFactory
.getInstance(keyGenAlgorithm);
SecretKey secretKey = secretKeyFactory.generateSecret(keySpec);
keySpec.clearPassword();
byte[] key = secretKey.getEncoded();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, keyAlgorithm);
Cipher cipher = Cipher.getInstance(cipherTransform);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(
initVector));
return cipher.doFinal(data);
}
public static void main(String[] args) throws Exception
{
char[] password = "password".toCharArray();
EncryptInfo info = encrypt(password, "Message".getBytes());
byte[] decyptedText = decrypt(info.getEncryptedData(), password, info
.getSalt(), info.getInitVector());
System.out.println(new String(decyptedText));
}
}
Both MD5 and DES are weak. If your data being encrypted is really valuable, you should look for some external crypto library for Android that offers AES and SHA256/SHA512 algorithms.
If you want to encrypt data using a symmetric key encryption, I recommend:
1) Use AES, because it is certified by the NSA for data classified as secret.
2) Use a well reviewed implementation so you don't have to research the proper way to configure the code. For example, AESCrypt.
You can find AESCrypt here: http://www.aescrypt.com/java_aes_crypt.html
I've seen AESCrypt used in several financial institutions. AESCrypt for java is a single class that calls JCE methods. Android, JCE is implemented by bouncycastle. I have seen bouncycastle used in several major financial institutions.
Related
I am trying to encrypt a string with a user defined password and then decode it with the string again.
My code:
SecurityManager.java
package de.example.org;
import javax.crypto.*;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
public class SecurityManager {
private static final String ALGORITHM = "AES";
private static final String TRANSFORMATION = "AES/ECB/PKCS5Padding";
private static final SecureRandom secureRandom = new SecureRandom();
private static final Base64.Encoder base64Encoder = Base64.getUrlEncoder();
private static final SecretKeyFactory factory;
static {
try {
factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private static PBEKeySpec generatePBEKeySpec(String password) {
return new PBEKeySpec(password.toCharArray(), generateRandomString(16).getBytes(), 65536, 128);
}
public static String generateRandomString(int length) {
byte[] randomBytes = new byte[length];
secureRandom.nextBytes(randomBytes);
return base64Encoder.encodeToString(randomBytes);
}
public static String generateEncryptedKey(String password) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
SecretKey secretKey = factory.generateSecret(generatePBEKeySpec(password));
byte[] encodedKey = secretKey.getEncoded();
// Convert the secretKey to a SecretKeySpec
Key secretKeySpec = new SecretKeySpec(encodedKey, ALGORITHM);
// Encrypt the original string
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
String random = generateRandomString(128);
System.out.println(random);
byte[] encryptedBytes = cipher.doFinal(random.getBytes());
return new String(encryptedBytes);
}
public static String decryptKey(String encryptedKey, String password) throws InvalidKeySpecException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
SecretKey secretKey = factory.generateSecret(generatePBEKeySpec(password));
byte[] encodedKey = secretKey.getEncoded();
// Convert the secretKey to a SecretKeySpec
Key secretKeySpec = new SecretKeySpec(encodedKey, ALGORITHM);
// Decrypt the encrypted string
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
byte[] decryptedBytes = cipher.doFinal(encryptedKey.getBytes());
return new String(decryptedBytes);
}
}
Executing the function in main:
String encrypted = SecurityManager.generateEncryptedKey("123");
String decrypted = SecurityManager.decryptKey(encrypted, "123");
System.out.println(decrypted);
Error:
Exception in thread "main" javax.crypto.IllegalBlockSizeException: Input length must be multiple of 16 when decrypting with padded cipher
at java.base/com.sun.crypto.provider.CipherCore.prepareInputBuffer(CipherCore.java:887)
at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:729)
at java.base/com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:434)
at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2206)
at de.example.org.SecurityManager.decryptKey(SecurityManager.java:66)
at de.example.org.main.Main.main(Main.java:11)
I have been sitting on this for hours now, and with bytes I can encrypt it. But since I want to store the encrypted string, so it's still available after a restart, storing the bytes in a file would be less secure AFAIK.
My code wasn't actually able to decrypt. This code does the job:
package de.alexx1.birthdaynotf.helper;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
import java.util.Base64;
public class SecurityManager {
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final String CHARSET = "UTF-8";
private static final int KEY_SIZE = 128;
private static final int ITERATIONS = 65536;
private static final int SALT_LENGTH = 16;
private static final SecureRandom secureRandom = new SecureRandom();
private static final Base64.Encoder base64Encoder = Base64.getEncoder();
public static String generateRandomString(int length) {
byte[] randomBytes = new byte[length];
secureRandom.nextBytes(randomBytes);
return base64Encoder.encodeToString(randomBytes);
}
public static String encrypt(String plainText, String password) throws Exception {
byte[] salt = generateSalt();
SecretKey secretKey = generateSecretKey(password, salt);
byte[] keyBytes = secretKey.getEncoded();
Key key = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] encryptedBytes = cipher.doFinal(plainText.getBytes(CHARSET));
byte[] iv = cipher.getIV();
byte[] encryptedText = new byte[salt.length + iv.length + encryptedBytes.length];
System.arraycopy(salt, 0, encryptedText, 0, salt.length);
System.arraycopy(iv, 0, encryptedText, salt.length, iv.length);
System.arraycopy(encryptedBytes, 0, encryptedText, salt.length + iv.length, encryptedBytes.length);
return Base64.getEncoder().encodeToString(encryptedText);
}
public static String decrypt(String encryptedText, String password) throws Exception {
byte[] encryptedTextBytes = Base64.getDecoder().decode(encryptedText.getBytes(CHARSET));
byte[] salt = Arrays.copyOfRange(encryptedTextBytes, 0, SALT_LENGTH);
byte[] iv = Arrays.copyOfRange(encryptedTextBytes, SALT_LENGTH, SALT_LENGTH + 16);
byte[] cipherText = Arrays.copyOfRange(encryptedTextBytes, SALT_LENGTH + 16, encryptedTextBytes.length);
SecretKey secretKey = generateSecretKey(password, salt);
Key key = new SecretKeySpec(secretKey.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decryptedBytes = cipher.doFinal(cipherText);
return new String(decryptedBytes, CHARSET);
}
private static SecretKey generateSecretKey(String password, byte[] salt) throws Exception {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, ITERATIONS, KEY_SIZE);
return factory.generateSecret(spec);
}
private static byte[] generateSalt() {
byte[] salt = new byte[SALT_LENGTH];
secureRandom.nextBytes(salt);
return salt;
}
}
I was implementing encryption decryption of a file using the AES/CBC/PKCS5PADDING algorithm. And I noticed some peculiarity. With the correct order of initialization of the IV, everything is decrypted correctly. But if the order is wrong (see commented out lines), the beginning of the line is decoded incorrectly.
But if the decryption happens with wrong IV in CBC mode then nothing should be decrypted. After all, that's how AES/CBC works.
My question is - why is the string still decrypted with the wrong IV ?
Output
org.junit.ComparisonFailure:
Expected :Test string Test string Test string Test string Test string Test string
Actual :�Eݠ�/ՙ�, 9B�� string Test string Test string Test string Test string
Code
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
public class CryptographyService {
private static final String SECRET_KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256";
private static final String CIPHER_ALGORITHM_MODE_PADDING = "AES/CBC/PKCS5PADDING";
private static final String CIPHER_ALGORITHM = "AES";
private static final int SALT_LEN = 32;
private static byte[] createSalt() {
byte[] salt = new byte[SALT_LEN];
SecureRandom random = new SecureRandom();
random.nextBytes(salt);
return salt;
}
private static SecretKey secretKeyCreate(String userPassword, byte[] salt) throws NoSuchAlgorithmException,
InvalidKeySpecException {
SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_DERIVATION_ALGORITHM);
KeySpec spec = new PBEKeySpec(userPassword.toCharArray(), salt, 25000, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), CIPHER_ALGORITHM);
return secret;
}
public static void encrypt(String userPassword, String fileEncryptName, String jsonPasswordsData)
throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IOException {
byte[] salt = createSalt();
SecretKey secretKey = secretKeyCreate(userPassword, salt);
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_MODE_PADDING);
cipher.init(Cipher.ENCRYPT_MODE, secretKey);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
//cipher.init(Cipher.ENCRYPT_MODE, secretKey); // incorrect decrypt
try (FileOutputStream fileOutputStream = new FileOutputStream(fileEncryptName);
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher)) {
fileOutputStream.write(iv);
fileOutputStream.write(salt);
fileOutputStream.flush();
cipherOutputStream.write(jsonPasswordsData.getBytes(StandardCharsets.UTF_8));
}
}
public static String decrypt(String userPassword, String fileDecryptName) throws NoSuchPaddingException,
NoSuchAlgorithmException, IOException, InvalidParameterSpecException, InvalidKeySpecException,
InvalidAlgorithmParameterException, InvalidKeyException {
Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_MODE_PADDING);
AlgorithmParameters params = cipher.getParameters();
int ivLength = params.getParameterSpec(IvParameterSpec.class).getIV().length;
byte[] iv = new byte[ivLength];
byte[] salt = new byte[SALT_LEN];
byte[] plainText;
try (FileInputStream fileInputStream = new FileInputStream(fileDecryptName);
CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher)) {
fileInputStream.read(iv);
fileInputStream.read(salt);
SecretKey secretKey = secretKeyCreate(userPassword, salt);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
plainText = cipherInputStream.readAllBytes();
}
return new String(plainText, StandardCharsets.UTF_8);
}
}
Encryption and Decryption in Java is still very difficult for me to understand. I have been using the following class and methods. I wonder how to improve the safety and how long does the keystring (schlüssel) need to be?
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;
public class AES
{
public static SecretKeySpec makeKey(String schlüssel) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
byte[] key = (schlüssel).getBytes("UTF-8");
MessageDigest sha = MessageDigest.getInstance("SHA");
key = sha.digest(key);
key = Arrays.copyOf(key, 16);
return new SecretKeySpec(key, "AES");
}
public static String encryptString(String text, SecretKeySpec schlüssel) throws Exception
{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, schlüssel);
byte[] encrypted = cipher.doFinal(text.getBytes());
BASE64Encoder myEncoder = new BASE64Encoder();
return myEncoder.encode(encrypted);
}
public static String decryptString(String text, SecretKeySpec schlüssel) throws Exception
{
BASE64Decoder myDecoder2 = new BASE64Decoder();
byte[] crypted2 = myDecoder2.decodeBuffer(text);
Cipher cipher2 = Cipher.getInstance("AES");
cipher2.init(Cipher.DECRYPT_MODE, schlüssel);
byte[] cipherData2 = cipher2.doFinal(crypted2);
return new String(cipherData2);
}
}
I have been reading about the topic. But I did not understand how to transfer the ideas into my code. Any help is appreciated, please be kind with an encryption beginner. Thank you.
There are a lot of things wrong in this class.
the class uses a cryptographic hash instead of a password hash - such as PBKDF2 - to derive a key from the password;
you are using ECB mode encryption (the default), you need to use at least CBC, together with an initialization vector (IV);
your class doesn't add any integrity protection, in other words the ciphertext is malleable;
It depends on the use case if you require the integrity protection. So I'll point you to this question for more information about password based encryption (PBE). Note that the answers may still deliver malleable ciphertext.
Furthermore the class contains the following Java mistakes:
it doesn't distinguish between runtime related exceptions (missing algorithms) and input related exceptions;
it uses the default platform encoding for your plaintext;
it is using a Sun internal class to perform the Base 64 encoding/decoding.
Note that people will probably point out to you that you are using 128 bit AES encryption. That's however quite strong and - certainly at this point in time - the least of your worries. Upgrading to 192 or 256 bit AES won't increase security significantly.
Refering to Maarten Bodeswes code I try to bring his code into the form I am using.
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
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.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;
public class AESplus
{
public static SecretKeySpec makeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException
{
password = String.format("%040x", new BigInteger(1,password.getBytes(Charset.forName("UTF-8"))));
password = password.substring(password.length()-32, password.length());
final byte[] symKeyData = DatatypeConverter.parseHexBinary(password);
return new SecretKeySpec(symKeyData, "AES");
}
public static String encryptString(String text, SecretKeySpec key) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
final byte[] encodedMessage = text.getBytes(Charset.forName("UTF-8"));
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// generate random IV using block size
final byte[] ivData = new byte[blockSize];
final SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
rnd.nextBytes(ivData);
final IvParameterSpec iv = new IvParameterSpec(ivData);
cipher.init(Cipher.ENCRYPT_MODE, key, iv);
final byte[] encryptedMessage = cipher.doFinal(encodedMessage);
// concatenate IV and encrypted message
final byte[] ivAndEncryptedMessage = new byte[ivData.length + encryptedMessage.length];
System.arraycopy(ivData, 0, ivAndEncryptedMessage, 0, blockSize);
System.arraycopy(encryptedMessage, 0, ivAndEncryptedMessage, blockSize, encryptedMessage.length);
return DatatypeConverter.printBase64Binary(ivAndEncryptedMessage);
}
public static String decrytString(String crypttext, SecretKeySpec key) throws NoSuchAlgorithmException, NoSuchPaddingException,
InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException
{
final byte[] ivAndEncryptedMessage = DatatypeConverter.parseBase64Binary(crypttext);
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
final int blockSize = cipher.getBlockSize();
// retrieve random IV from start of the received message
final byte[] ivData = new byte[blockSize];
System.arraycopy(ivAndEncryptedMessage, 0, ivData, 0, blockSize);
final IvParameterSpec iv = new IvParameterSpec(ivData);
// retrieve the encrypted message itself
final byte[] encryptedMessage = new byte[ivAndEncryptedMessage.length - blockSize];
System.arraycopy(ivAndEncryptedMessage, blockSize, encryptedMessage, 0, encryptedMessage.length);
cipher.init(Cipher.DECRYPT_MODE, key, iv);
final byte[] encodedMessage = cipher.doFinal(encryptedMessage);
// concatenate IV and encrypted message
final String message = new String(encodedMessage,Charset.forName("UTF-8"));
return message;
}
}
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
public class Encryption {
public static byte[] encrypted(String t) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException{
byte[] dataToSend = t.getBytes();
byte[] key = new byte[16];
Cipher c = Cipher.getInstance("AES");
SecretKeySpec k = new SecretKeySpec(key, "AES");
c.init(Cipher.ENCRYPT_MODE, k);
byte[] encryptedData = c.doFinal(dataToSend);
return encryptedData;
}
public static byte[] decrypted(byte[] kr) throws IllegalBlockSizeException, BadPaddingException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException{
byte[] key = new byte[16];
SecretKeySpec k = new SecretKeySpec(key, "AES");
byte[] encryptedData = kr;
Cipher c2 = Cipher.getInstance("AES");
c2.init(Cipher.DECRYPT_MODE, k);
byte[] data = c2.doFinal(encryptedData);
return data;
}
public static void main(String args[]) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException{
//method1
System.out.println(encrypted("adsda"));
String f = new String (encrypted("adsda")); //working on console but not works when stores to cookies because of invalid characters
System.out.println(f);
System.out.println(new String(decrypted(f.getBytes())));// works when decrypting in console, not tried in cookies because not able encrypt
//method2
String x = encrypted("adsda").toString(); // works when stores in cookies works on console
System.out.println(x);
System.out.println(new String(decrypted(x.getBytes())));// decrypt not working both on console and cookies
System.out.println(decrypted(x.getBytes()).toString()); // decrypt not working both on console and cookies
}
}
I created a method to encrypt and decrypt cookies using AES. The details is the comment on the code.
You can't just convert the byte[] returned by encrypted(..) to a String. You need to use an encoding that do not lose data.
Use an encoding like Base64 or even a hex encoding.
I have the following code which almost works:
AES.java
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;
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.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
public class AES {
private static final int KEY_LENGTH = 128;
private static final int ITERATIONS = 100;
private static final String ALGORITHM = "AES";
private static final String SECRET_KEY_ALGORITHM = "PBKDF2WithHmacSHA1";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final Cipher m_enc_cipher;
private final Cipher m_dec_cipher;
private final byte[] m_iv;
public AES(final char[] password, final byte[] salt)
throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException,
InvalidParameterSpecException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException,
InvalidAlgorithmParameterException {
// Derive the key, given password and salt
final SecretKeyFactory factory = SecretKeyFactory
.getInstance(SECRET_KEY_ALGORITHM);
final KeySpec spec = new PBEKeySpec(password, salt, ITERATIONS,
KEY_LENGTH);
final SecretKey tmp = factory.generateSecret(spec);
final SecretKey secret = new SecretKeySpec(tmp.getEncoded(), ALGORITHM);
// Build encryptor and get IV
final Cipher enc_cipher = Cipher.getInstance(TRANSFORMATION);
enc_cipher.init(Cipher.ENCRYPT_MODE, secret);
final AlgorithmParameters params = enc_cipher.getParameters();
final byte[] iv = params.getParameterSpec(IvParameterSpec.class)
.getIV();
// Build decryptor
final Cipher dec_cipher = Cipher.getInstance(TRANSFORMATION);
dec_cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
this.m_enc_cipher = enc_cipher;
this.m_dec_cipher = dec_cipher;
this.m_iv = iv;
}
public AES(final byte[] iv) throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidParameterSpecException,
IllegalBlockSizeException, BadPaddingException,
UnsupportedEncodingException, InvalidAlgorithmParameterException {
final AlgorithmParameterSpec aps = new IvParameterSpec(iv);
final KeyGenerator keygen = KeyGenerator.getInstance(ALGORITHM);
keygen.init(KEY_LENGTH);
final SecretKey secret = keygen.generateKey();
// Build encryptor
final Cipher enc_cipher = Cipher.getInstance(TRANSFORMATION);
enc_cipher.init(Cipher.ENCRYPT_MODE, secret, aps);
// Build decryptor
final Cipher dec_cipher = Cipher.getInstance(TRANSFORMATION);
dec_cipher.init(Cipher.DECRYPT_MODE, secret, aps);
this.m_enc_cipher = enc_cipher;
this.m_dec_cipher = dec_cipher;
this.m_iv = iv;
}
public byte[] get_iv() {
return this.m_iv;
}
public byte[] encrypt(final byte[] data) throws NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidKeyException, InvalidParameterSpecException,
IllegalBlockSizeException, BadPaddingException,
UnsupportedEncodingException {
return this.m_enc_cipher.doFinal(data);
}
public byte[] decrypt(final byte[] data) throws IllegalBlockSizeException,
BadPaddingException {
return this.m_dec_cipher.doFinal(data);
}
}
AESTest.java
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.util.Arrays;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.junit.Test;
import static org.junit.Assert.*;
public class AESTest {
#Test
public void test() throws InvalidKeyException, NoSuchAlgorithmException,
InvalidKeySpecException, NoSuchPaddingException,
InvalidParameterSpecException, IllegalBlockSizeException,
BadPaddingException, UnsupportedEncodingException,
InvalidAlgorithmParameterException {
final char[] password = "my_password".toCharArray();
final byte[] salt = new byte[] { 22, 11 };
final byte[] original_data = "Hello, World!".getBytes("UTF-8");
final AES aesA = new AES(password, salt);
final byte[] encrypted_data = aesA.encrypt(original_data);
System.out.println("Encrypted:");
System.out.println(javax.xml.bind.DatatypeConverter
.printBase64Binary(encrypted_data));
final AES aesB = new AES(aesA.get_iv());
final byte[] decrypted_data_B = aesB.decrypt(encrypted_data);
System.out.println("Decrypted B:");
System.out.println(javax.xml.bind.DatatypeConverter
.printBase64Binary(decrypted_data_B));
assertTrue(Arrays.equals(original_data, decrypted_data_B));
}
}
On the test (AESTest.java), it gives me this error:
javax.crypto.BadPaddingException: Given final block not properly padded
My final purpose is to be able to send an encrypted data block and later get it back.
The term "later" could refer to a day/week/year.
Then, using the same password I want to decrypt it.
Because "later" might be a month I'll need to create a new AES object.
Now, this new AES object must be able to decrypt the data with the same fixed password/salt.
That's all.
I've tried to use the same IV but it doesn't work.
What am I doing wrong here?
Edit #1:
Please pay attention to ITERATIONS as well.
When you init AES by iv you create a different secret. Isnt's it wrong?
You are not using the same password and salt.
As already stated, the second constructor creates a new AES key, so it won't match with the one created in the first constructor. I don't really get your intention in having two different constructors, where in each you create an AES instance fit for encryption as well as another one for decryption? It might make sense to rethink that design and use just one single instance only, depending on whether you want to encrypt or decrypt.
While the key you generate in the first constructor is to be kept secret, the IV being produced is public information and can be safely published publicly, this won't harm the security. However, you should create unique IVs for every encrypted message, otherwise there's attacks that are possible against the CBC mode you are using.
So here's how I would recommend you do it:
Encryption:
Basically the first constructor, leaving out the second instance for decryption. Retrieve the IV that was created.
Decryption:
Again basically the first constructor, with an additional IV parameter. Create the key again from scratch with exactly the same parameters (salt, password, iterations), and this time init the Cipher in decryption mode, additionally passing the IV parameter.
That should do it!