Related
I am trying to convert my java code to NodeJs code. It's a little more complicate because the customised format included the password and salt.
In main method there is one example.
Here is my java code:
public class App {
private static final int DYN_SALT_LENGTH = 10;
private static final int ITERATION_COUNT = 65556;
private static final int KEY_LENGTH = 256;
private static final String SECRET_KEY_ALGORITHM = "AES";
private static final String CIPHER_TRANSFORMER = "AES/CBC/PKCS5Padding";
private static Base64 base64Instance = new Base64();
public static String decrypt(String data, String password, String salt) {
try {
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
ITERATION_COUNT,
KEY_LENGTH);
SecretKey secretKey = factory.generateSecret(spec);
ByteBuffer buffer = ByteBuffer.wrap(base64Instance.decode(data));
buffer.position(DYN_SALT_LENGTH);
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMER);
// Read the IV
byte[] ivBytes = new byte[cipher.getBlockSize()];
buffer.get(ivBytes, 0, ivBytes.length);
// Read encrypted text.
byte[] encryptedTextBytes = new byte[buffer.capacity() - DYN_SALT_LENGTH - ivBytes.length];
buffer.get(encryptedTextBytes);
// Initialize Cipher.
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), SECRET_KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes));
String result = new String(cipher.doFinal(encryptedTextBytes), StandardCharsets.UTF_8);
return result;
} catch (Exception e) {
throw new RuntimeException("Failed to decrypt data", e);
}
}
public static String encrypt(String data, String password, String salt) {
// Create new salt for every new encryption request.
byte[] saltBytes = new byte[DYN_SALT_LENGTH];
new SecureRandom().nextBytes(saltBytes);
try {
// Create secret key spec.
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(StandardCharsets.UTF_8),
ITERATION_COUNT,
KEY_LENGTH);
SecretKey secretKey = factory.generateSecret(spec);
SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), SECRET_KEY_ALGORITHM);
byte[] ivBytes;
byte[] encryptedTextBytes;
// Initialize cipher
Cipher cipher = Cipher.getInstance(CIPHER_TRANSFORMER);
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
// Create initialization vector IV
ivBytes = params.getParameterSpec(IvParameterSpec.class).getIV();
// Encrypt the text.
encryptedTextBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// Response will be in the form of <salt><IV><encryptedText>
ByteBuffer byteBuffer = ByteBuffer.allocate(saltBytes.length + ivBytes.length + encryptedTextBytes.length);
byteBuffer.put(saltBytes);
byteBuffer.put(ivBytes);
byteBuffer.put(encryptedTextBytes);
return base64Instance.encodeToString(byteBuffer.array());
} catch (Exception e) {
throw new RuntimeException("Failed to encrypt data", e);
}
}
public static void main(String[] args) {
String password = "password";
String salt = "salt";
String data = "hello world";
String resultEncrypted = encrypt(data, password, salt);
System.out.println(resultEncrypted);
String resultDecrypted = decrypt(resultEncrypted, password, salt);
System.out.println(resultDecrypted);
}
}
I'm trying with JS code like below but without clue about what i'm doing wrong:
function getAlgorithm(keyBase64) {
var key = Buffer.from(keyBase64, "base64");
switch (key.length) {
case 16:
return "aes-128-cbc";
case 32:
return "aes-256-cbc";
}
throw new Error("Invalid key length: " + key.length);
}
function decrypt(messagebase64, keyBase64, ivBase64) {
const key = Buffer.from(keyBase64, "base64");
const iv = Buffer.from(ivBase64, "base64");
const decipher = crypto.createDecipheriv(
getAlgorithm(keyBase64),
key,
iv.slice(0, 16)
);
let decrypted = decipher.update(messagebase64, "base64", "utf8");
decrypted += decipher.final("utf8");
return decrypted;
}
const base64Encrypted =
"2vSIh0J64zhrQuayUV+UIyPTpmSaN4gAv7B3CVC/a68eBfeU0bMwRm2I";
const key = crypto.scryptSync("password", "salt", 16);
const encrypted = Buffer.from(base64Encrypted, "base64");
const encryptedWOSalt = Buffer.from(base64Encrypted, "base64").slice(10);
const iv = encrypted.slice(10, 10 + 17);
const result = decrypt(
encryptedWOSalt.toString("base64"),
key,
iv.toString("base64")
);
console.log(result);
It's throwing an error:
Error: error:06065064:digital envelope routines:EVP_DecryptFinal_ex:bad decrypt
Thanks
Since you only posted a NodeJS decryption code, I focus on decryption. The encryption is to be implemented analogously. If you have problems with this, please post a new question with the corresponding encryption code.
There are several bugs in the NodeJS code:
Wrong key derivation (in the NodeJS code scrypt is used, while in the Java code PBKDF2/HMAC-SHA1 is applied).
Incorrect/missing separation of salt, IV and ciphertext
Encoding bugs and unnecessary encoding/decoding cycles
The following NodeJS code works:
var crypto = require('crypto')
function getAlgorithm(key) {
switch (key.length) {
case 16:
return "aes-128-cbc";
case 32:
return "aes-256-cbc";
}
throw new Error("Invalid key length: " + key.length);
}
function decrypt(message, key, iv) {
const decipher = crypto.createDecipheriv(
getAlgorithm(key),
key,
iv
);
let decrypted = Buffer.concat([decipher.update(message), decipher.final()]);
return decrypted.toString("utf8");
}
const DYN_SALT_LENGTH = 10;
const IV_LENGTH = 16;
const ITERATION_COUNT = 65556;
const KEY_LENGTH = 256;
const base64Encrypted = "ossqoyCaaQINWUkTsHNGRe5Isd5s7c7U8KcLua78Ehm9jAxQNOd2tyjj";
// Separate salt, IV and ciphertext
const encrypted = Buffer.from(base64Encrypted, "base64");
const salt = encrypted.slice(0, DYN_SALT_LENGTH);
const iv = encrypted.slice(DYN_SALT_LENGTH, DYN_SALT_LENGTH + IV_LENGTH);
const ciphertext = encrypted.slice(DYN_SALT_LENGTH + IV_LENGTH);
// Derive key voa PBKDF2/HMAC-SHA1
const key = crypto.pbkdf2Sync("password", "salt", ITERATION_COUNT, KEY_LENGTH/8, "sha1");
// Decrypt
const result = decrypt(
ciphertext,
key,
iv
);
console.log(result); // hello world
Note that the current Java code (and therefore also the NodeJS code) does not use the random salt for key derivation, but a static salt, possibly for testing purposes. In the final solution, the random salt is to be used for security reasons.
I'm doing an integration with another system and the data given is encrypted in AES-256-CBC(Java) and need to decrypt it in NodeJs in order to proceed.
I have tried many ways from internet and stuck in error. Below is the sample code of Java(decryption) which is working and NodeJs(my code of decryption)
private static final int ITERATION_COUNT = 65536;
private static final int KEY_LENGTH = 256;
private static final byte[] DEFAULT_IV = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
public static byte[] decryptToBytes(String src, String secret, String salt, byte[] iv) {
try{
IvParameterSpec ivspec = new IvParameterSpec(iv);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
KeySpec spec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), ITERATION_COUNT, KEY_LENGTH);
SecretKey tmp = factory.generateSecret(spec);
SecretKeySpec secretKey = new SecretKeySpec(tmp.getEncoded(), "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivspec);
return cipher.doFinal(Base64.getDecoder().decode(src));
}catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
public static String decrypt(String src, String secret, String salt, byte[] iv) {
try{
return new String(decryptToBytes(src, secret, salt, iv));
}catch (Exception ex) {
return null;
}
}
public static void main(String[] args) {
String secret = "abcd123456";
String salt = "123abc";
String plainText ="This is AES256 encryption test";
String cipherText = "gbYgtu5EWxOYRSUmMsEtdn8oQLxBjejfwUBSRhhls08=";
byte[] IV = new byte[16];
String originalText = decrypt(cipherText,secret, salt, IV);
}
import crypto from "crypto";
public aesCdcDecrypt(input: string) {
let iterationCount = 65536;
let keyLength = 256;
let iv = new Buffer(16);
let keyHex = "abcd123456";
let salt = "123abc";
let decryptText: string;
try {
crypto.pbkdf2(new Buffer(keyHex), new Buffer(salt), iterationCount, keyLength, "sha256", function (err, key) {
let secretKey = key.toString("hex");
let decipher = crypto.createDecipheriv("aes-256-cbc", secretKey, iv);
decryptText = decipher.update(input, "binary", "utf8");
decryptText += decipher.final("utf8");
console.log('Result: ' + decryptText);
});
} catch (e) {
console.log(e);
}
return decryptText;
}
Result getting this error -->
Error: Invalid key length
at new Decipheriv (crypto.js:267:16)
at Object.createDecipheriv (crypto.js:627:10)
There are a few minor issues in your TS code:
key length is in bytes, not bits
new Buffer() does not decode base64 by default
Here's a working version (JS):
const crypto = require('crypto')
function aesCdcDecrypt(ciphertext) {
let iterationCount = 65536;
let keyLength = 32;
let iv = Buffer.alloc(16);
let keyHex = "abcd123456";
let salt = "123abc";
let key = crypto.pbkdf2Sync(keyHex, Buffer.from(salt), iterationCount, keyLength, "sha256");
var cipher = crypto.createDecipheriv("aes-256-cbc", key, iv);
cipher.setAutoPadding(true);
let ciph = cipher.update(Buffer.from(ciphertext, "base64"));
let ciphf = cipher.final();
return Buffer.concat([ciph, ciphf]).toString();
}
console.log(aesCdcDecrypt("gbYgtu5EWxOYRSUmMsEtdn8oQLxBjejfwUBSRhhls08="));
Prints:
This is AES256 encryption test
I've been having trouble adding a MAC to my password-based AES encryption/decryption program. I am trying to add the MAC'd plaintext and salt (to be used with password) (both byte arrays) to a final array along with the ciphertext, and then decrypt by reading in the ciphertext file and splitting it back up into salt, MAC, and cipher text byte arrays.
The encryption class seems to be running smoothly but the decryption class does not. I debugged the the class and found that it fails because it never enters the if statement that checks whether the computed and recovered MACs are the same:
if(Arrays.equals(macBytes, hmac))
I couldn't figure out why until I printed out the byte arrays for the salt, message, and MAC, and found that they don't match when printed from the encryption and decryption classes. All the array sizes match up across the two classes, but the byte values change somewhere.
Both classes worked perfectly without the MAC before, but I didn't add the salt directly to the encrypted data then and instead wrote it to a separate file. Including it with the encrypted data makes this slightly more portable for me, but was it a bad choice to do so? Is it better to write it to a separate file? Or am I just missing something blatantly obvious in my code?
Here is the full code.
Encryption class
public class AESEncryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private static final int SALT_SIZE = 64;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESEncryption()
{
try
{
encrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void encrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(PLAINTEXT));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(ENCRYPTED));
//Create salt
byte[] salt = new byte[SALT_SIZE];
SecureRandom secureRandom = new SecureRandom();
secureRandom.nextBytes(salt);
//Create cipher key
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);
//Create cipher
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Read plaintext file into byte array
byte[] input = new byte[encryptedSize];
Path path = Paths.get(PLAINTEXT);
input = Files.readAllBytes(path);
byte[] crypt = cipher.doFinal(input);
//Create MAC object and apply to the byte array crypt[] containing the plaintext
KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
SecretKey macKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] macBytes = mac.doFinal(crypt);
//Add salt, MAC'd plaintext, and encrypted plaintext to final array
byte[] output = new byte[SALT_SIZE + crypt.length + macBytes.length];
System.arraycopy(salt, 0, output, 0, SALT_SIZE);
System.arraycopy(macBytes, 0, output, SALT_SIZE, macBytes.length);
System.arraycopy(crypt, 0, output, SALT_SIZE + macBytes.length, crypt.length);
//Write array with encrypted data to a new file
bufferedOutputStream.write(output);
bufferedInputStream.close();
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
Decryption class
public class AESDecryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 64;
private final int IV_SIZE = 16;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESDecryption()
{
try
{
decrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void decrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));
//Read in the encrypted data
byte[] input = new byte[encryptedSize];
Path path = Paths.get(ENCRYPTED);
input = Files.readAllBytes(path);
int increment = (input.length-SALT_SIZE)/2;
if(input.length >= (SALT_SIZE + increment))
{
//Recover salt, MAC, and encrypted data and store in arrays
byte[] salt = Arrays.copyOfRange(input, 0, SALT_SIZE);
byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE + increment);
byte[] crypt = Arrays.copyOfRange(input, SALT_SIZE + increment, input.length);
//Regenerate original MAC
KeyGenerator keyGenerator = KeyGenerator.getInstance(MAC_ALGORITHM);
SecretKey macKey = keyGenerator.generateKey();
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] hmac = mac.doFinal(crypt);
if(Arrays.equals(macBytes, hmac)) //This is where it fails, never enters
{
//Regenerate cipher and decrypt data
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
KeySpec keySpec = new PBEKeySpec(PASSWORD.toCharArray(), salt, ITERATIONS, KEY_LENGTH);
SecretKey secret = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Write decrypted data to new text file
byte[] output = cipher.doFinal(crypt);
bufferedOutputStream.write(output);
bufferedInputStream.close();
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
}
Thanks for any help. It is much appreciated.
public class AESEncryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String PRNG_ALGORITHM = "SHA1PRNG";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String PLAINTEXT = "/Volumes/CONNOR P/Unencrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String IV_FILE_NAME = "iv.enc";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 16;
private final int IV_SIZE = 16;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
private final int START = 0;
public AESEncryption()
{
try
{
encrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void encrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int plaintextSize = (int)plaintext.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(plaintext));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(encrypted));
//Create salt for cipher key
byte[] salt = new byte[SALT_SIZE];
SecureRandom saltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
saltSecureRandom.nextBytes(salt);
//Create cipher key & use to initialize cipher
byte[] keyBytes = PBEKeyFactory.getKey(PASSWORD, salt, ITERATIONS, KEY_LENGTH);
SecretKeySpec secret = new SecretKeySpec(keyBytes, ALGORITHM);
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16]));
//Create byte array of encrypted data
byte[] input = new byte[plaintextSize];
Path path = Paths.get(PLAINTEXT);
input = Files.readAllBytes(path);
byte[] crypt = cipher.doFinal(input);
//Create salt for the MAC key for added security
byte[] macsalt = new byte[SALT_SIZE];
SecureRandom macsaltSecureRandom = SecureRandom.getInstance(PRNG_ALGORITHM);
macsaltSecureRandom.nextBytes(macsalt);
//PBEKeyFactory.getKey(password, salt, iterations, keylength)
//returns a byte array representation of a SecretKey.
//Used a SecretKeyFactory instead of a KeyGenerator to make key.
//SecretKeyFactory gives back the same key given the same specifications
//whereas KeyGenerator gives back a new random key each time.
byte[] macPBE = PBEKeyFactory.getKey(PASSWORD, macsalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec macKey = new SecretKeySpec(macPBE, MAC_ALGORITHM);
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] macBytes = mac.doFinal(crypt);
byte[] output = new byte[SALT_SIZE + SALT_SIZE + crypt.length + macBytes.length];
System.arraycopy(salt, START, output, START, SALT_SIZE);
System.arraycopy(macsalt, START, output, SALT_SIZE, SALT_SIZE);
System.arraycopy(macBytes, START, output, SALT_SIZE + SALT_SIZE, macBytes.length);
System.arraycopy(crypt, START, output, SALT_SIZE + SALT_SIZE + macBytes.length, crypt.length);
bufferedInputStream.close();
bufferedOutputStream.write(output);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
public class AESDecryption
{
private final String ALGORITHM = "AES";
private final String MAC_ALGORITHM = "HmacSHA256";
private final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
private final String PLAINTEXT = "/Volumes/CONNOR P/De-Encrypted.txt";
private final String ENCRYPTED = "/Volumes/CONNOR P/Encrypted.txt";
private final String PASSWORD = "javapapers";
private final String LOC = Paths.get(".").toAbsolutePath().normalize().toString();
private final int SALT_SIZE = 16;
//MAC key size is 256 bits (32 bytes) since it is created with
//the HmacSHA256 algorithm
private final int MAC_SIZE = 32;
private final int IV_SIZE = 16;
private final int START = 0;
private final int KEY_LENGTH = 128;
private final int ITERATIONS = 100000;
public AESDecryption()
{
try
{
decrypt();
}
catch(Exception ex)
{
JOptionPane.showMessageDialog(null, "Error: " + ex.getClass().getName(), "Error", JOptionPane.ERROR_MESSAGE);
}
}
private void decrypt() throws Exception
{
File encrypted = new File(ENCRYPTED);
File plaintext = new File(PLAINTEXT);
int encryptedSize = (int)encrypted.length();
BufferedInputStream bufferedInputStream = new BufferedInputStream(new FileInputStream(encrypted));
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(plaintext));
//Read in encrypted data
byte[] input = new byte[encryptedSize];
Path path = Paths.get(ENCRYPTED);
input = Files.readAllBytes(path);
if(input.length >= (SALT_SIZE*2 + MAC_SIZE))
{
byte[] cryptSalt = Arrays.copyOfRange(input, START, SALT_SIZE);
byte[] macSalt = Arrays.copyOfRange(input, SALT_SIZE, SALT_SIZE*2);
byte[] macBytes = Arrays.copyOfRange(input, SALT_SIZE*2, (SALT_SIZE*2 + MAC_SIZE));
byte[] cryptBytes = Arrays.copyOfRange(input, (SALT_SIZE*2 + MAC_SIZE), input.length);
//This generates the same MAC key from encryption.
//Before, the KeyGenerator created a new random key
//meaning the derived and computed MAC keys were never the same
byte[] macKeyBytes = PBEKeyFactory.getKey(PASSWORD, macSalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec macKey = new SecretKeySpec(macKeyBytes, MAC_ALGORITHM);
Mac mac = Mac.getInstance(MAC_ALGORITHM);
mac.init(macKey);
byte[] compMacBytes = mac.doFinal(cryptBytes);
//Check if computed and derived MAC's are the same
if(Arrays.equals(macBytes, compMacBytes))
{
//Creates same key from encryption
byte[] cryptKeyBytes = PBEKeyFactory.getKey(PASSWORD, cryptSalt, ITERATIONS, KEY_LENGTH);
SecretKeySpec cryptKey = new SecretKeySpec(cryptKeyBytes, ALGORITHM);
//Creates cipher and reads decrypted data to array
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, cryptKey, new IvParameterSpec(new byte[16]));
byte[] output = cipher.doFinal(cryptBytes);
bufferedInputStream.close();
bufferedOutputStream.write(output);
bufferedOutputStream.flush();
bufferedOutputStream.close();
}
}
}
}
//This class has only one method, getKey(), which returns a byte array
//of a SecretKey of the corresponding parameters
public class PBEKeyFactory
{
private static final String KEY_DERIVATION_FUNCTION = "PBKDF2WithHmacSHA1";
public static byte[] getKey(String password, byte[] salt, int iterations, int length) throws Exception
{
KeySpec keySpec = new PBEKeySpec(password.toCharArray(), salt, iterations, length);
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_DERIVATION_FUNCTION);
return factory.generateSecret(keySpec).getEncoded();
}
}
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'm trying to support PBE for AES, Serpent, and TwoFish. Currently I am able to generate an AES PBEKey in Java using BC like this:
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC", provider);
PBEKeySpec pbeKeySpec = new PBEKeySpec("Password12".toCharArray());
SecretKey key = factory.generateSecret(pbeKeySpec);
but I can't figure out how to generate a PBEKey for Serpent, so I'm assuming its not possible out of the box. How would I go about implementing this? Is there a hook somewhere that I can just register my own SecretKeyFactory to handle Serpent keys?
Coincidentally, I have noticed that using an AES PBEKey (as generated above) for encrypting/decrypting with Serpent/TwoFish "works", but I have no idea what the repercussions are. Could I just get away with using the AES PBEKey?
After discussions with PaŭloEbermann (above), I put together the following solution. It generates a PBE key for AES256 and then simply copies the required number of bytes from the generated key into a new SecretKeySpec(), which allows me to specify the desired algorithm and key length. Currently I am salting the password AND creating a random IV on each call to encrypt. My assumption is that the IV is unnecessary since a random salt is applied to each encrypted message, but I wasn't 100% sure so I added the IV anyway. I'm hoping someone can confirm or deny this assumption, since if the IV isnt needed, then its bloating the size of the output from encrypt() for no valid reason. Ideally I would be able to generate a PBEKey of variable length with no algorithm ties (as per PKCS5), but it appears I am bound to the key sizes defined in the available PBE schemes provided by the selected Provider. This implementation is therefore bound to using BouncyCastle, since I was unable to find a PBE scheme that provided at least 256bit keys from the standard JCE provider.
/**
* parts of this code were copied from the StandardPBEByteEncryptor class from the Jasypt (www.jasypt.org) project
*/
public class PBESample {
private final String KEY_ALGORITHM = "PBEWithSHA256And256BitAES-CBC-BC";
private final String MODE_PADDING = "/CBC/PKCS5Padding";
private final int DEFAULT_SALT_SIZE_BYTES = 16;
private final SecureRandom rand;
private final String passwd = "(Password){12}<.....>!";
public PBESample() throws Exception {
rand = SecureRandom.getInstance("SHA1PRNG");
}
private byte[] generateSalt(int size) {
byte[] salt = new byte[size];
rand.nextBytes(salt);
return salt;
}
private SecretKey generateKey(String algorithm, int keySize, byte[] salt) throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeySpecException{
SecretKeyFactory factory = SecretKeyFactory.getInstance(KEY_ALGORITHM);
PBEKeySpec pbeKeySpec = new PBEKeySpec(passwd.toCharArray(), salt, 100000);
SecretKey tmpKey = factory.generateSecret(pbeKeySpec);
byte[] keyBytes = new byte[keySize / 8];
System.arraycopy(tmpKey.getEncoded(), 0, keyBytes, 0, keyBytes.length);
return new SecretKeySpec(keyBytes, algorithm);
}
private byte[] generateIV(Cipher cipher) {
byte[] iv = new byte[cipher.getBlockSize()];
rand.nextBytes(iv);
return iv;
}
private byte[] appendArrays(byte[] firstArray, byte[] secondArray) {
final byte[] result = new byte[firstArray.length + secondArray.length];
System.arraycopy(firstArray, 0, result, 0, firstArray.length);
System.arraycopy(secondArray, 0, result, firstArray.length, secondArray.length);
return result;
}
public byte[] encrypt(String algorithm, int keySize, final byte[] message) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// The salt size for the chosen algorithm is set to be equal
// to the algorithm's block size (if it is a block algorithm).
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
// Create salt
final byte[] salt = generateSalt(saltSizeBytes);
SecretKey key = generateKey(algorithm, keySize, salt);
// create a new IV for each encryption
final IvParameterSpec ivParamSpec = new IvParameterSpec(generateIV(cipher));
// Perform encryption using the Cipher
cipher.init(Cipher.ENCRYPT_MODE, key, ivParamSpec);
byte[] encryptedMessage = cipher.doFinal(message);
// append the IV and salt
encryptedMessage = appendArrays(ivParamSpec.getIV(), encryptedMessage);
encryptedMessage = appendArrays(salt, encryptedMessage);
return encryptedMessage;
}
public byte[] decrypt(String algorithm, int keySize, final byte[] encryptedMessage) throws Exception {
Cipher cipher = Cipher.getInstance(algorithm + MODE_PADDING);
// determine the salt size for the first layer of encryption
int saltSizeBytes = DEFAULT_SALT_SIZE_BYTES;
int algorithmBlockSize = cipher.getBlockSize();
if (algorithmBlockSize > 0) {
saltSizeBytes = algorithmBlockSize;
}
byte[] decryptedMessage = new byte[encryptedMessage.length];
System.arraycopy(encryptedMessage, 0, decryptedMessage, 0, encryptedMessage.length);
// extract the salt and IV from the incoming message
byte[] salt = null;
byte[] iv = null;
byte[] encryptedMessageKernel = null;
final int saltStart = 0;
final int saltSize = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivStart = (saltSizeBytes < decryptedMessage.length ? saltSizeBytes : decryptedMessage.length);
final int ivSize = cipher.getBlockSize();
final int encMesKernelStart = (saltSizeBytes + ivSize < decryptedMessage.length ? saltSizeBytes + ivSize : decryptedMessage.length);
final int encMesKernelSize = (saltSizeBytes + ivSize < decryptedMessage.length ? (decryptedMessage.length - saltSizeBytes - ivSize) : 0);
salt = new byte[saltSize];
iv = new byte[ivSize];
encryptedMessageKernel = new byte[encMesKernelSize];
System.arraycopy(decryptedMessage, saltStart, salt, 0, saltSize);
System.arraycopy(decryptedMessage, ivStart, iv, 0, ivSize);
System.arraycopy(decryptedMessage, encMesKernelStart, encryptedMessageKernel, 0, encMesKernelSize);
SecretKey key = generateKey(algorithm, keySize, salt);
IvParameterSpec ivParamSpec = new IvParameterSpec(iv);
// Perform decryption using the Cipher
cipher.init(Cipher.DECRYPT_MODE, key, ivParamSpec);
decryptedMessage = cipher.doFinal(encryptedMessageKernel);
// Return the results
return decryptedMessage;
}
public static void main(String[] args) throws Exception {
// allow the use of the BC JCE
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
final String message = "Secret Message";
PBESample engine = new PBESample();
byte[] encryptedMessage = engine.encrypt("AES", 128, message.getBytes());
byte[] decryptedMessage = engine.decrypt("AES", 128, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("AES OK");
}
encryptedMessage = engine.encrypt("Serpent", 256, message.getBytes());
decryptedMessage = engine.decrypt("Serpent", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("Serpent OK");
}
encryptedMessage = engine.encrypt("TwoFish", 256, message.getBytes());
decryptedMessage = engine.decrypt("TwoFish", 256, encryptedMessage);
if (message.equals(new String(decryptedMessage))) {
System.out.println("TwoFish OK");
}
}
}