I'm in the process of porting our Java application to OS X (10.8). One of our unit tests fails when doing encryption (it works on Windows). Both are running Java 7 Update 21 but the Windows version is using the 32 bit JDK and the Mac version is using the 64 bit JDK.
When running it on Mac I get the following exception when trying to decrypt the encrypted data:
Caused by: javax.crypto.BadPaddingException: Given final block not
properly padded at
com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:811) at
com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:676) at
com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:313)
at javax.crypto.Cipher.doFinal(Cipher.java:2087) at
com.degoo.backend.security.Crypto.processCipher(Crypto.java:56) ...
25 more
Here's the encryption class.
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
public final class Crypto {
private final static String CIPHER_ALGORITHM = "AES";
private final static String CIPHER_TRANSFORMATION = "AES/CBC/PKCS5Padding";
public final static int CRYPTO_KEY_SIZE = 16;
public static byte[] encryptByteArray(byte[] blockToEncrypt, int maxLengthToEncrypt, byte[] encryptionKey, byte[] ivBytes) {
return processCipher(blockToEncrypt, maxLengthToEncrypt, Cipher.ENCRYPT_MODE, ivBytes, encryptionKey);
}
public static byte[] decryptByteArray(byte[] encryptedData, byte[] encryptionKey, byte[] ivBytes) {
return processCipher(encryptedData, encryptedData.length, Cipher.DECRYPT_MODE, ivBytes, encryptionKey);
}
private static byte[] processCipher(byte[] blockToEncrypt, int maxLength, int cryptionMode, byte[] ivBytes, byte[] encryptionKey) {
try {
IvParameterSpec iv = new IvParameterSpec(ivBytes);
final Cipher cipher = initCipher(cryptionMode, iv, encryptionKey);
return cipher.doFinal(blockToEncrypt, 0, maxLength);
} catch (Exception e) {
throw new RuntimeException("Failure", e);
}
}
private static Cipher initCipher(int cryptionMode, IvParameterSpec iv, byte[] encryptionKey) {
KeyGenerator keyGen;
try {
keyGen = KeyGenerator.getInstance(CIPHER_ALGORITHM);
final SecureRandom randomSeed = new SecureRandom();
randomSeed.setSeed(encryptionKey);
keyGen.init(CRYPTO_KEY_SIZE * 8, randomSeed);
// Generate the secret key specs.
final SecretKey secretKey = keyGen.generateKey();
final SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), CIPHER_ALGORITHM);
// Instantiate the cipher
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(cryptionMode, secretKeySpec, iv);
return cipher;
} catch (Exception e) {
throw new RuntimeException("Failure", e);
}
}
}
The test code looks like this:
public void testEncryption() throws Exception {
int dataLength = TestUtil.nextInt(applicationParameters.getDataBlockMinSize());
byte[] dataToEncrypt = new byte[dataLength];
TestUtil.nextBytes(dataToEncrypt);
int keyLength = 16;
byte[] key = new byte[keyLength];
TestUtil.nextBytes(key);
byte[] ivBytes = new byte[16];
TestUtil.nextBytes(key);
long startTime = System.nanoTime();
byte[] encryptedBlock = Crypto.encryptByteArray(dataToEncrypt, dataToEncrypt.length, key, ivBytes);
long endTime = System.nanoTime();
System.out.println("Encryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));
startTime = System.nanoTime();
byte[] decryptedData = Crypto.decryptByteArray(encryptedBlock, key, ivBytes);
endTime = System.nanoTime();
System.out.println("Decryption-speed: " + getMBPerSecond(dataLength, startTime, endTime));
if (encryptedBlock.length == decryptedData.length) {
boolean isEqual = true;
//Test that the encrypted data is not equal to the decrypted data.
for (int i = 0; i < encryptedBlock.length; i++) {
if (encryptedBlock[i] != decryptedData[i]) {
isEqual = false;
break;
}
}
if (isEqual) {
throw new RuntimeException("Encrypted data is equal to decrypted data!");
}
}
Assert.assertArrayEquals(dataToEncrypt, decryptedData);
}
I think I've found it. For some reason the code above derives an encryption-key by seeding a SecureRandom instance with the existing encryption key to get a new byte[] (don't ask me why, it was a long time ago it was written). This is then fed to the SecretKeySpec constructor. If I skip all this and just feed the SecretKeySpec constructor the encryption key that we already have then the unit test passes. The code that does the encryption now looks like this:
final SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, CIPHER_ALGORITHM);
// Instantiate the cipher
final Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMATION);
cipher.init(cryptionMode, secretKeySpec, iv);
return cipher;
The odd thing is that it has worked on Windows. Looks like the SecureRandom implementations behave differently on OS X and on Windows. Calling setSeed on OS X appends to the seed whereas Windows replaces it.
Update: found some more details on the implementation differences of SecureRandom: http://www.cigital.com/justice-league-blog/2009/08/14/proper-use-of-javas-securerandom/
Related
I am attempting to implement this encryption scheme with AES encryption.
Basically it goes as follows:
user data is encrypted with a surrogate key (surrogateKey)
the surrogate key is XORed with a key derived from the password
(passwordKey) and stored (storedKey)
when the key is needed (to encrypt or decrypt user data) the storedKey is retrieved from the DB and XORed again with the freshly generated passwordKey to recover the surrogateKey
Except, I must be doing something wrong in implementation, because I can never seem to recover a valid surrogateKey and any attempt at decryption is giving me a BadPaddingException.
The code below demonstrates the issue. Sorry if it's a bit long, but you should be able to just copy and paste it into your IDE.`
import java.io.ByteArrayOutputStream;
import java.security.AlgorithmParameters;
import java.security.SecureRandom;
import java.security.spec.KeySpec;
import java.util.Arrays;
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 javax.xml.bind.DatatypeConverter;
public class SurrogateTest {
private static final String alphanumeric =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "1234567890"
+ "!#$%&()+*<>?_-=^~|";
private static String plainText = "I am the very model of a modern major general";
private static String cipherText = "";
private static SecureRandom rnd = new SecureRandom();
private static byte[] salt;
public static void main(String[] args) {
// arguments are password and recovery string
if (args.length > 1) {
System.out.println("password: " + args[0] + "; recovery: " + args[1]);
}
String password = args[0];
// passwordKey
SecretKey passwordKey = getKey(password);
System.out.println("passwordKey: " + DatatypeConverter.printBase64Binary(passwordKey.getEncoded()));
// Generate surrogate encryption key from random string
String rand = randomString(24);
SecretKey surrogateKey = getKey(rand);
byte[] surrogateByteArray = surrogateKey.getEncoded();
System.out.println("surrogate: " + DatatypeConverter.printBase64Binary(surrogateByteArray));
// encrypt plainText
System.out.println("text to encrypt: " + plainText);
cipherText = encryptWithKey(plainText, surrogateKey);
// XOR surrogateKey with passwordKey to get storedKey
SecretKey storedKey = xorWithKey(surrogateKey, passwordKey);
String storedKeyString = DatatypeConverter.printBase64Binary(storedKey.getEncoded());
System.out.println("storedKey: " + storedKeyString);
byte[] storedKey2Array = DatatypeConverter.parseBase64Binary(storedKeyString);
SecretKey storedKey2 = new SecretKeySpec(storedKey2Array, 0, storedKey2Array.length, "AES");
String storedKey2String = DatatypeConverter.printBase64Binary(storedKey2.getEncoded());
System.out.println("storedKey->String->key->string: " + storedKey2String);
// recover surrogateKey from storedKey2
SecretKey password2Key = getKey(password);
System.out.println("password2Key: " + DatatypeConverter.printBase64Binary(password2Key.getEncoded()));
SecretKey surrogate2Key = xorWithKey(storedKey2, password2Key);
System.out.println("surrogate2 (recovered): " + DatatypeConverter.printBase64Binary(surrogate2Key.getEncoded()));
// decrypt text
String decryptedText = decryptWithKey(cipherText, surrogate2Key);
System.out.println("decryptedText: " + decryptedText);
}
private static SecretKey xorWithKey(SecretKey a, SecretKey b) {
byte[] out = new byte[b.getEncoded().length];
for (int i = 0; i < b.getEncoded().length; i++) {
out[i] = (byte) (b.getEncoded()[i] ^ a.getEncoded()[i % a.getEncoded().length]);
}
SecretKey outKey = new SecretKeySpec(out, 0, out.length, "AES");
return outKey;
}
private static String randomString(int length) {
StringBuilder sb = new StringBuilder(length);
for (int i = 0; i < length; i++)
sb.append(alphanumeric.charAt(rnd.nextInt(alphanumeric.length())));
return sb.toString();
}
// return encryption key
private static SecretKey getKey(String password) {
try {
SecureRandom random = new SecureRandom();
salt = new byte[16];
random.nextBytes(salt);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// obtain secret key
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String encryptWithKey(String str, SecretKey secret) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedText = cipher.doFinal(str.getBytes("UTF-8")); // encrypt the message str here
// concatenate salt + iv + ciphertext
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
outputStream.write(salt);
outputStream.write(iv);
outputStream.write(encryptedText);
// properly encode the complete ciphertext
String encrypted = DatatypeConverter.printBase64Binary(outputStream.toByteArray());
return encrypted;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
private static String decryptWithKey(String str, SecretKey secret) {
try {
byte[] ciphertext = DatatypeConverter.parseBase64Binary(str);
if (ciphertext.length < 48) {
return null;
}
salt = Arrays.copyOfRange(ciphertext, 0, 16);
byte[] iv = Arrays.copyOfRange(ciphertext, 16, 32);
byte[] ct = Arrays.copyOfRange(ciphertext, 32, ciphertext.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
byte[] plaintext = cipher.doFinal(ct);
return new String(plaintext, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
Any ideas what I'm doing wrong?
Running the posted code shows that surrogate2 (recovered) is different from surrogate. It should be obvious this is badly wrong. The reason is that at encryption you derive the 'password' (wrapping) key using a random salt and write that salt at the beginning of your data blob; at decryption you derive the unwrapping key using a new salt and then read the correct salt from the blob and totally ignore it. This means your unwrapping key is wrong, so your unwrapped data key is wrong, so your decryption is wrong.
PS: the random key used to directly encrypt and decrypt the data is usually called a 'data' key (as I just did) or DEK (abbreviation for Data Encryption or Encrypting Key), or a more specific term like 'session key' or 'message key' (in this case), or 'working key' or 'transient key' to emphasize its limited scope. It is not usually called 'surrogate'. And using PBKDF2 to derive this data key from a strongly random string is a waste of time; just use SecureRandom_instance.nextBytes(byte[]) directly for the data key.
I would like to encrypt this javascript code in android.
let base64Key = CryptoJS.enc.Base64.parse(key);
let encryptedValue = CryptoJS.AES.encrypt(value, base64Key, {
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7,
iv: base64Key
});
return encryptedValue.toString();
Code:
String encryptedKey = Base64.encodeToString(keyword.getBytes(), Base64.NO_WRAP);
Key key = new SecretKeySpec(encryptedKey.getBytes(), algorithm);
Cipher chiper = Cipher.getInstance("AES");
chiper.init(Cipher.ENCRYPT_MODE, key);
byte[] encVal = chiper.doFinal(plainText.getBytes());
String encryptedValue = Base64.encodeToString(encVal, Base64.NO_WRAP);
return encryptedValue;
But it returns a completely different value.
The first line of the code itself returns a different value in both cases:
So I got this part working.
I just needed to add the following lines to the android code:
byte[] decoded = Base64.decode(key.getBytes());
String hexString = Hex.encodeHexString(decoded);
This is the equivalent of CryptoJS.enc.Base64.parse(key); this line in CryptoJS.
But still trying to figure out the end result though. Both are different.
I am also having same issue but finally, i got a Class from Git.
AESHelper.decrypt("your secret key","encryptedText")
import com.sun.jersey.core.util.Base64;
import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Random;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class AESHelper {
/**
*
* Conforming with CryptoJS AES method
* **** YOU NEED TO ADD "JCE policy" to have ability to DEC/ENC 256 key-lenght with AES Cipher ****
*
*/
static int KEY_SIZE = 256;
static int IV_SIZE = 128;
static String HASH_CIPHER = "AES/CBC/PKCS7Padding";
static String AES = "AES";
static String CHARSET_TYPE = "UTF-8";
static String KDF_DIGEST = "MD5";
// Seriously crypto-js, what's wrong with you?
static String APPEND = "Salted__";
/**
* Encrypt
* #param password passphrase
* #param plainText plain string
*/
public static String encrypt(String password,String plainText) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] saltBytes = generateSalt(8);
byte[] key = new byte[KEY_SIZE/8];
byte[] iv = new byte[IV_SIZE/8];
EvpKDF(password.getBytes(CHARSET_TYPE), KEY_SIZE, IV_SIZE, saltBytes, key, iv);
SecretKey keyS = new SecretKeySpec(key, AES);
Cipher cipher = Cipher.getInstance(HASH_CIPHER);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keyS, ivSpec);
byte[] cipherText = cipher.doFinal(plainText.getBytes(CHARSET_TYPE));
// Thanks kientux for this: https://gist.github.com/kientux/bb48259c6f2133e628ad
// Create CryptoJS-like encrypted !
byte[] sBytes = APPEND.getBytes(CHARSET_TYPE);
byte[] b = new byte[sBytes.length + saltBytes.length + cipherText.length];
System.arraycopy(sBytes, 0, b, 0, sBytes.length);
System.arraycopy(saltBytes, 0, b, sBytes.length, saltBytes.length);
System.arraycopy(cipherText, 0, b, sBytes.length + saltBytes.length, cipherText.length);
byte[] bEncode = Base64.encode(b);
return new String(bEncode);
}
/**
* Decrypt
* Thanks Artjom B. for this: http://stackoverflow.com/a/29152379/4405051
* #param password passphrase
* #param cipherText encrypted string
*/
public static String decrypt(String password,String cipherText) throws UnsupportedEncodingException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException, BadPaddingException, IllegalBlockSizeException {
byte[] ctBytes = Base64.decode(cipherText.getBytes(CHARSET_TYPE));
byte[] saltBytes = Arrays.copyOfRange(ctBytes, 8, 16);
byte[] ciphertextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.length);
byte[] key = new byte[KEY_SIZE/8];
byte[] iv = new byte[IV_SIZE/8];
EvpKDF(password.getBytes(CHARSET_TYPE), KEY_SIZE, IV_SIZE, saltBytes, key, iv);
Cipher cipher = Cipher.getInstance(HASH_CIPHER);
SecretKey keyS = new SecretKeySpec(key, AES);
cipher.init(Cipher.DECRYPT_MODE, keyS, new IvParameterSpec(iv));
byte[] plainText = cipher.doFinal(ciphertextBytes);
return new String(plainText);
}
private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
return EvpKDF(password, keySize, ivSize, salt, 1, KDF_DIGEST, resultKey, resultIv);
}
private static byte[] EvpKDF(byte[] password, int keySize, int ivSize, byte[] salt, int iterations, String hashAlgorithm, byte[] resultKey, byte[] resultIv) throws NoSuchAlgorithmException {
keySize = keySize / 32;
ivSize = ivSize / 32;
int targetKeySize = keySize + ivSize;
byte[] derivedBytes = new byte[targetKeySize * 4];
int numberOfDerivedWords = 0;
byte[] block = null;
MessageDigest hasher = MessageDigest.getInstance(hashAlgorithm);
while (numberOfDerivedWords < targetKeySize) {
if (block != null) {
hasher.update(block);
}
hasher.update(password);
block = hasher.digest(salt);
hasher.reset();
// Iterations
for (int i = 1; i < iterations; i++) {
block = hasher.digest(block);
hasher.reset();
}
System.arraycopy(block, 0, derivedBytes, numberOfDerivedWords * 4,
Math.min(block.length, (targetKeySize - numberOfDerivedWords) * 4));
numberOfDerivedWords += block.length/4;
}
System.arraycopy(derivedBytes, 0, resultKey, 0, keySize * 4);
System.arraycopy(derivedBytes, keySize * 4, resultIv, 0, ivSize * 4);
return derivedBytes; // key + iv
}
private static byte[] generateSalt(int length) {
Random r = new SecureRandom();
byte[] salt = new byte[length];
r.nextBytes(salt);
return salt;
}
}
Check out related question here and page on Java Cryptographic Extensions
To turn a text string (UTF-8 encoded) into a base-64 string, you need:
var textString = 'Hello world'; // Utf8-encoded string
var words = CryptoJS.enc.Utf8.parse(textString); // WordArray object
var base64 = CryptoJS.enc.Base64.stringify(words); // string: 'SGVsbG8gd29ybGQ='
Finally got it working in Android using the below code, if anyone else faces the issue:
public static String encrypt(String key, String value) {
try {
SecretKey secretKey = new SecretKeySpec(Base64.decode(key.getBytes(), Base64.NO_WRAP), "AES");
AlgorithmParameterSpec iv = new IvParameterSpec(Base64.decode(key.getBytes(), Base64.NO_WRAP));
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKey, iv);
return new String(Base64.encode(cipher.doFinal(value.getBytes("UTF-8")), Base64.NO_WRAP));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
The below code works for me,
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class EncryptionUtils {
private static final String key = "123456$##$^#RRRR";//16 characters
private static final String initVector = "123456$##$^#RRRR";//16 characters
public static String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(key.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
// byte[] finalCiphertext = new byte[encrypted.length+2*16];
return Base64.encodeToString(encrypted, Base64.NO_WRAP);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
[Edit:] SOLVED! See this article. Full working source code. JavaX.
[Original post:]
sorry to hit this fairly dead horse once more, but I can't get this to work... (I've provided a full example): http://tinybrain.de/1000344
Code:
import java.net.*;
import java.io.*;
import javax.swing.*;
import java.util.regex.*;
import java.util.*;
import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
public class main {
public static void main(String[] args) throws Exception {
byte[] data = "hello".getBytes("UTF-8");
printHex(data);
Random ranGen = new SecureRandom();
byte[] salt = new byte[8]; // 8 grains of salt
ranGen.nextBytes(salt);
String pw = "pw";
byte[] enc = encrypt(data, pw.toCharArray(), salt);
printHex(enc);
System.out.println("enc length: " + enc.length);
byte[] dec = decrypt(enc, pw.toCharArray(), salt);
System.out.println("decrypted: " + new String(dec, "UTF-8"));
}
static void printHex(byte[] data) {
System.out.println(bytesToHex(data));
}
static String bytesToHex(byte[] bytes) {
return bytesToHex(bytes, 0, bytes.length);
}
static String bytesToHex(byte[] bytes, int ofs, int len) {
StringBuilder stringBuilder = new StringBuilder(len*2);
for (int i = 0; i < len; i++) {
String s = "0" + Integer.toHexString(bytes[ofs+i]);
stringBuilder.append(s.substring(s.length()-2, s.length()));
}
return stringBuilder.toString();
}
static SecretKey makeKey(char[] password, byte[] salt) throws Exception {
/* Derive the key, given password and salt. */
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
// only with unlimited strength:
//KeySpec spec = new PBEKeySpec(password, salt, 65536, 256);
// Let's try this:
KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
return secret;
}
public static byte[] encrypt(byte[] data, char[] password, byte[] salt) {
try {
SecretKey secret = makeKey(password, salt);
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
baos.write(cipher.update(data));
baos.write(cipher.doFinal());
byte[] ciphertext = baos.toByteArray();
return ciphertext;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static byte[] decrypt(byte[] ciphertext, char[] password, byte[] salt) {
try {
SecretKey secret = makeKey(password, salt);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
/* Decrypt the message, given derived key and initialization vector. */
cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
baos.write(cipher.update(ciphertext));
baos.write(cipher.doFinal());
return baos.toByteArray();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It says:
javax.crypto.BadPaddingException: Given final block not properly padded
at main.decrypt(main.java:98)
at main.main(main.java:26)
... 9 more
Caused by: javax.crypto.BadPaddingException: Given final block not properly padded
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:966)
at com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:824)
at com.sun.crypto.provider.AESCipher.engineDoFinal(AESCipher.java:436)
at javax.crypto.Cipher.doFinal(Cipher.java:2048)
at main.decrypt(main.java:95)
What to do?
You are using a random IV, but you are not sharing the random IV generated during encryption with the decryption method. Instead, it is using a random IV itself.
You've got:
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
but you don't seem to do anything with the IV afterwards.
I am trying to encrypt some text using the AES algorithm on both the Android and IPhone platforms. My problem is, even using the same encryption/decryption algorithm (AES-128) and same fixed variables (key, IV, mode), I get different result on both platforms. I am including code samples from both platforms, that I am using to test the encryption/decryption. I would appreciate some help in determining what I am doing wrong.
Key: “123456789abcdefg”
IV: “1111111111111111”
Plain Text: “HelloThere”
Mode: “AES/CBC/NoPadding”
Android Code:
public class Crypto {
private final static String HEX = "0123456789ABCDEF";
public static String encrypt(String seed, String cleartext)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] result = encrypt(rawKey, cleartext.getBytes());
return toHex(result);
}
public static String decrypt(String seed, String encrypted)
throws Exception {
byte[] rawKey = getRawKey(seed.getBytes());
byte[] enc = toByte(encrypted);
byte[] result = decrypt(rawKey, enc);
return new String(result);
}
private static byte[] getRawKey(byte[] seed) throws Exception {
KeyGenerator kgen = KeyGenerator.getInstance("CBC");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(seed);
kgen.init(128, sr); // 192 and 256 bits may not be available
SecretKey skey = kgen.generateKey();
byte[] raw = skey.getEncoded();
return raw;
}
private static byte[] encrypt(byte[] raw, byte[] clear) throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] encrypted = cipher.doFinal(clear);
return encrypted;
}
private static byte[] decrypt(byte[] raw, byte[] encrypted)
throws Exception {
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] decrypted = cipher.doFinal(encrypted);
return decrypted;
}
public static String toHex(String txt) {
return toHex(txt.getBytes());
}
public static String fromHex(String hex) {
return new String(toByte(hex));
}
public static byte[] toByte(String hexString) {
int len = hexString.length() / 2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2 * i, 2 * i + 2),
16).byteValue();
return result;
}
public static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2 * buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}
IPhone (Objective-C) Code:
- (NSData *) transform:(CCOperation) encryptOrDecrypt data:(NSData *) inputData {
NSData* secretKey = [Cipher md5:cipherKey];
CCCryptorRef cryptor = NULL;
CCCryptorStatus status = kCCSuccess;
uint8_t iv[kCCBlockSizeAES128];
memset((void *) iv, 0x0, (size_t) sizeof(iv));
status = CCCryptorCreate(encryptOrDecrypt, kCCAlgorithmAES128, kCCOptionPKCS7Padding,
[secretKey bytes], kCCKeySizeAES128, iv, &cryptor);
if (status != kCCSuccess) {
return nil;
}
size_t bufsize = CCCryptorGetOutputLength(cryptor, (size_t)[inputData length], true);
void * buf = malloc(bufsize * sizeof(uint8_t));
memset(buf, 0x0, bufsize);
size_t bufused = 0;
size_t bytesTotal = 0;
status = CCCryptorUpdate(cryptor, [inputData bytes], (size_t)[inputData length],
buf, bufsize, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
status = CCCryptorFinal(cryptor, buf + bufused, bufsize - bufused, &bufused);
if (status != kCCSuccess) {
free(buf);
CCCryptorRelease(cryptor);
return nil;
}
bytesTotal += bufused;
CCCryptorRelease(cryptor);
return [NSData dataWithBytesNoCopy:buf length:bytesTotal];
}
+ (NSData *) md5:(NSString *) stringToHash {
const char *src = [stringToHash UTF8String];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(src, strlen(src), result);
return [NSData dataWithBytes:result length:CC_MD5_DIGEST_LENGTH];
}
Some of my references :
http://code.google.com/p/aes-encryption-samples/wiki/HowToEncryptWithJava
http://automagical.rationalmind.net/2009/02/12/aes-interoperability-between-net-and-iphone/
AES interoperability between .Net and iPhone?
For iPhone I used AESCrypt-ObjC, and for Android use this code:
public class AESCrypt {
private final Cipher cipher;
private final SecretKeySpec key;
private AlgorithmParameterSpec spec;
public AESCrypt(String password) throws Exception
{
// hash password with SHA-256 and crop the output to 128-bit for key
MessageDigest digest = MessageDigest.getInstance("SHA-256");
digest.update(password.getBytes("UTF-8"));
byte[] keyBytes = new byte[32];
System.arraycopy(digest.digest(), 0, keyBytes, 0, keyBytes.length);
cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
key = new SecretKeySpec(keyBytes, "AES");
spec = getIV();
}
public AlgorithmParameterSpec getIV()
{
byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, };
IvParameterSpec ivParameterSpec;
ivParameterSpec = new IvParameterSpec(iv);
return ivParameterSpec;
}
public String encrypt(String plainText) throws Exception
{
cipher.init(Cipher.ENCRYPT_MODE, key, spec);
byte[] encrypted = cipher.doFinal(plainText.getBytes("UTF-8"));
String encryptedText = new String(Base64.encode(encrypted, Base64.DEFAULT), "UTF-8");
return encryptedText;
}
public String decrypt(String cryptedText) throws Exception
{
cipher.init(Cipher.DECRYPT_MODE, key, spec);
byte[] bytes = Base64.decode(cryptedText, Base64.DEFAULT);
byte[] decrypted = cipher.doFinal(bytes);
String decryptedText = new String(decrypted, "UTF-8");
return decryptedText;
}
}
It makes me no wonder that you get different results.
Your problem is that you use misuse a SHA1PRNG for key derivation. AFAIK there is no common standard how a SHA1PRNG work internally. AFAIR even the J2SE and Bouncycaste implementation output different results using the same seed.
Hence your implementation of your getRawKey(byte[] seed) will generate you a random key. If you use the key for encryption you are getting an result that depends on that key. As the key is random you will not get the same key on iOS and therefore you are getting a different result.
If you want a key derivation function use a function like PBKDF2 with is nearly fully standardized regarding the key derivation.
On Android, you are using getBytes(). This is an error as it means you are using the default charset rather than a known charset. Use getBytes("UTF-8") instead so you know exactly what bytes you are going to get.
I don't know the equivalent for Objective-C, but don't rely on the default. Explicitly specify UTF-8 when converting strings to bytes. That way you will get the same bytes on both sides.
I also note that you are using MD5 in the Objective-C code but not in the Android code. Is this deliberate?
See my answer for password-based AES encryption, since, you are effectively using your "seed" as a password. (Just change the key length of 256 to 128, if that's what you want.)
Trying to generate the same key by seeding a DRBG with the same value is not reliable.
Next, you are not using CBC or the IV in your Android encryption. My example shows how to do that properly too. By the way, you need to generate a new IV for every message you encrypt, as my example shows, and send it along with the cipher text. Otherwise, there's no point in using CBC.
Note: For android in java
I have written this manager file and its functions are working perfectly fine for me. This is for AES 128 and without any salt.
public class CryptoManager {
private static CryptoManager shared;
private String privateKey = "your_private_key_here";
private String ivString = "your_iv_here";
private CryptoManager(){
}
public static CryptoManager getShared() {
if (shared != null ){
return shared;
}else{
shared = new CryptoManager();
return shared;
}
}
public String encrypt(String value) {
try {
IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
byte[] encrypted = cipher.doFinal(value.getBytes());
return android.util.Base64.encodeToString(encrypted, android.util.Base64.DEFAULT);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public String decrypt(String encrypted) {
try {
IvParameterSpec iv = new IvParameterSpec(ivString.getBytes("UTF-8"));
SecretKeySpec skeySpec = new SecretKeySpec(privateKey.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
byte[] original = new byte[0];
original = cipher.doFinal(android.util.Base64.decode(encrypted, android.util.Base64.DEFAULT));
return new String(original);
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
You need to call the functions like this.
String dataToEncrypt = "I need to encrypt myself";
String encryptedData = CryptoManager.getShared().encrypt(data);
And you will get your encrypted string with the following line
String decryptedString = CryptoManager.getShared().decrypt(encryptedData);
If you want an example of compatible code for Android and iPhone, look at the RNCryptor library for iOS and the JNCryptor library for Java/Android.
Both projects are open source and share a common data format. In these libraries, AES 256-bit is used, however it would be trivial to adapt the code if necessary to support 128-bit AES.
As per the accepted answer, both libraries use PBKDF2.
I am able to encrypt an SMS and send it from one simulator (Android 2.2) to another.
On the receiving end I am able to do the decryption successfully. But the problem is if do the encryption in one OS version (i.e Android 2.2) and trying to decrypt in another OS version ( Android 2.3 ) i am getting 'Bad padding exception'. I checked that i used the same key on both ends.
The code is shown below
public class ED {
private String Key;
public ED() {
Key = "abc12"; // Assigning default key.
}
public ED(String key) {
// TODO Auto-generated constructor stub
Key = key;
}
public String encrypt(String toEncrypt) throws Exception {
byte[] rawKey = getRawKey(Key.getBytes("UTF-8"));
byte[] result = encrypt(rawKey, toEncrypt.getBytes("UTF-8"));
return toHex(result);
}
public byte[] encrypt(byte[] key, byte[] toEncodeString) throws Exception {
SecretKeySpec sKeySpec = new SecretKeySpec(key, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, sKeySpec);
byte[] encrypted = cipher.doFinal(toEncodeString);
return encrypted;
}
private byte[] getRawKey(byte[] key) throws Exception {
KeyGenerator kGen = KeyGenerator.getInstance("AES");
SecureRandom sr = SecureRandom.getInstance("SHA1PRNG");
sr.setSeed(key);
kGen.init(128, sr);
SecretKey sKey = kGen.generateKey();
byte[] raw = sKey.getEncoded();
return raw;
}
/************************************* Decription *********************************************/
public String decrypt(String encryptedString) throws Exception {
byte[] rawKey = getRawKey(Key.getBytes("UTF-8"));
System.out.println("Decrypted Key in bytes : "+rawKey);
System.out.println("Key in decryption :"+rawKey);
SecretKeySpec sKeySpec = new SecretKeySpec(rawKey, "AES");
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, sKeySpec);
byte[] decrypted = cipher.doFinal(toByte(encryptedString));
System.out.println("Decrypted mess in bytes---------->" +decrypted);
return new String(decrypted);
}
public String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer(2*buf.length);
for (int i = 0; i < buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final String HEX = "0123456789ABCDEF";
private void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b>>4)&0x0f)).append(HEX.charAt(b&0x0f));
}
public byte[] toByte(String hexString) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for (int i = 0; i < len; i++)
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16).byteValue();
return result;
}
}
And I am using sendTextMessage() function to send an sms. I read that encryption/decryption doesn't depend on OS but in this case that is not true. Am I missing any important things while configuring the Cipher (in AES) ? Please let me know.
It's setSeed(). It does not do what you think it does: it just adds the entropy of the given seed to the underlying algorithm. You'll probably find out that it returns somehthing different on both platforms. SHA1PRNG is a pseudo random function, but if it is already seeded, it's likely to return different results.
If the problem is in the key length, you could derivate a key from your password, instead of using it directly. You could use a Hash (like SHA-1, MD5, etc) and crop it to the correct size (128, 192 or 256 bits), or use PBEKeySpec instead of SecretKeySpec.
That to remove problems with the key length. If the padding problems were in the plaintext, I suggest you to use CipherInputStream and CipherOutputStream, which are more programmer-friendly to use than Cipher.doFinal.
Don't rely on KeyGenerator to generate the same key just because you seeded the RNG the same way. If you are pre-sharing a key, share the key, not the seed.
You should also specify the encryption transform completely: "AES/ECB/PKCS5Padding"
Finally, ECB mode is not secure for general use.
See another answer of mine for an example to perform encryption correctly with the JCE.
The problem is with SecureRandom generation. It is giving different results on different platforms. It's because of a bug fix on line 320 (in Gingerbread source) of SHA1PRNG_SecureRandomImpl.java in the engineNextBytes() method where
bits = seedLength << 3 + 64;
was changed to
bits = (seedLength << 3) + 64;
Use SecretKeyFactory() to generate a Secure key instead of secure random.
public class Crypto {
Cipher ecipher;
Cipher dcipher;
byte[] salt = { 1, 2, 4, 5, 7, 8, 3, 6 };
int iterationCount = 1979;
Crypto(String passPhase) {
try {
// Create the key
KeySpec keySpec = new PBEKeySpec(passPhase.toCharArray(), salt, iterationCount);
SecretKey key = SecretKeyFactory.getInstance("PBEWITHSHA256AND128BITAES-CBC-BC").generateSecret(keySpec);
ecipher = Cipher.getInstance(key.getAlgorithm());
dcipher = Cipher.getInstance(key.getAlgorithm());
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt, iterationCount);
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
} catch (Exception e) {
// TODO: handle exception
//Toast.makeText(this, "I cought ", Toast.LENGTH_LONG).show();
}
}
public String encrypt(String str) {
String rVal;
try {
byte[] utf8 = str.getBytes("UTF8");
byte[] enc = ecipher.doFinal(utf8);
rVal = toHex(enc);
} catch (Exception e) {
// TODO: handle exception
rVal = "Exception Caught "+e.getMessage();
}
return rVal;
}
public String decrypt(String str) {
String rVal;
try {
byte[] dec = toByte(str);
byte[] utf8 = dcipher.doFinal(dec);
rVal = new String(utf8, "UTF8");
} catch(Exception e) {
rVal = "Error in decrypting :"+e.getMessage();
}
return rVal;
}
private static byte[] toByte(String hexString ) {
int len = hexString.length()/2;
byte[] result = new byte[len];
for ( int i=0; i<len; i++ ) {
result[i] = Integer.valueOf(hexString.substring(2*i, 2*i+2), 16 ).byteValue();
}
return result;
}
private static String toHex(byte[] buf) {
if (buf == null)
return "";
StringBuffer result = new StringBuffer( 2*buf.length);
for ( int i=0; i<buf.length; i++) {
appendHex(result, buf[i]);
}
return result.toString();
}
private final static String HEX = "0123456789ABCDEF";
private static void appendHex(StringBuffer sb, byte b) {
sb.append(HEX.charAt((b >> 4) & 0x0f)).append(HEX.charAt(b & 0x0f));
}
}