I am in a situation where a JSON is encrypted in PHP's openssl_encrypt and needs to be decrypted in JAVA.
$encrypted = "...ENCRYPTED DATA...";
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
var_dump(strlen($secret)); // prints : int(370)
$iv = substr($encrypted, 0, 16);
$data = substr($encrypted, 16);
$decrypted = openssl_decrypt($data, "aes-256-cbc", $secret, null, $iv);
This $decrypted has correct data which is now decrypted.
Now, the problem is when I try to do same things in Java it doesn't work :(
String path = "/path/to/secret/saved/in/text";
String payload = "...ENCRYPTED DATA...";
StringBuilder output = new StringBuilder();
String iv = payload.substring(0, 16);
byte[] secret = Base64.getDecoder().decode(Files.readAllBytes(Paths.get(path)));
String data = payload.substring(16);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
SecretKeySpec secretKeySpec = new SecretKeySpec(secret, "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv.getBytes(), 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec); // This line throws exception :
cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
Here it is:
Exception in thread "main" java.security.InvalidKeyException: Invalid AES key length: 370 bytes
at com.sun.crypto.provider.AESCrypt.init(AESCrypt.java:87)
at com.sun.crypto.provider.CipherBlockChaining.init(CipherBlockChaining.java:91)
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:591)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:346)
at javax.crypto.Cipher.init(Cipher.java:1394)
at javax.crypto.Cipher.init(Cipher.java:1327)
at com.sample.App.main(App.java:70)
I have already visited similar question like
AES-256 CBC encrypt in php and decrypt in Java or vice-versa
openssl_encrypt 256 CBC raw_data in java
Unable to exchange data encrypted with AES-256 between Java and PHP
and list continues.... but no luck there
btw, this is how encryption is done in PHP
$secretFile = "/path/to/secret/saved/in/text_file";
$secret = base64_decode(file_get_contents($secretFile));
$iv = bin2hex(openssl_random_pseudo_bytes(8));
$enc = openssl_encrypt($plainText, "aes-256-cbc", $secret, false, $iv);
return $iv.$enc;
and yes, I forgot to mention that my JRE is already at UnlimitedJCEPolicy and I can't change PHP code.
I am totally stuck at this point and can't move forward. Please help out.
EDIT#1
byte[] payload = ....;
byte[] iv = ....;
byte[] secret = ....; // Now 370 bits
byte[] data = Base64.getDecoder().decode(payload);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
SecretKeySpec secretKeySpec = new SecretKeySpec(Arrays.copyOfRange(secret, 0, 32), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv, 0, cipher.getBlockSize());
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
byte[] output = cipher.doFinal(data);
System.out.println(new String(output).trim());
Above snippet seems to be working with openssl_encrypt
EDIT#2
I am not sure if this is correct, but following is what I have done and encryption-decryption on both side are working fine.
Encrypt in PHP, Decrypt in JAVA use AES/CBC/NoPadding
Encrypt in JAVA, Decrypt in PHP use AES/CBC/PKCS5Padding
I won't provide a complete solution, but there are a few differences you should take care of
Encoding:
String iv = payload.substring(0, 16);
String data = payload.substring(16);
are you sure the IV and data are the same in Java and PHP (The IV is string?)? If the data are encrypted, they should be treated as a byte array, not string. Just REALLY make sure they are THE SAME (print hex/base64 in php and java)
For the IV you at the end call iv.getBytes(), but the locale encoding may/will corrupt your values. The String should be use only when it's really string (text). Don't use string for binaries.
Simply treat data and iv as byte[]
Key generation according to the openssl
AES key must have length of 256 bit for aes-256-cbc used. The thing is - openssl by default doesn't use the provided secret as a key (I believe it can, but I don't know how it is to be specified in PHP).
see OpenSSL EVP_BytesToKey issue in Java
and here is the EVP_BytesToKey implementation: https://olabini.com/blog/tag/evp_bytestokey/
you should generate a 256 bit key usging the EVP_BytesToKey function (it's a key derivation function used by openssl).
Edit:
Maarten (in the comments) is right. The key parameter is the key. Seems the PHP function is accepting parameter of any length which is misleading. According to some articles (e.g. http://thefsb.tumblr.com/post/110749271235/using-opensslendecrypt-in-php-instead-of) the key is trucated or padded to necessary length (so seems 370 bit key is truncated to length of 256 bits).
According to your example, I wrote fully working code for PHP and Java:
AesCipher class: https://gist.github.com/demisang/716250080d77a7f65e66f4e813e5a636
Notes:
-By default algo is AES-128-CBC.
-By default init vector is 16 bytes.
-Encoded result = base64(initVector + aes crypt).
-Encoded/Decoded results present as itself object, it gets more helpful and get possibility to check error, get error message and get init vector value after encode/decode operations.
PHP:
$secretKey = '26kozQaKwRuNJ24t';
$text = 'Some text'
$encrypted = AesCipher::encrypt($secretKey, $text);
$decrypted = AesCipher::decrypt($secretKey, $encrypted);
$encrypted->hasError(); // TRUE if operation failed, FALSE otherwise
$encrypted->getData(); // Encoded/Decoded result
$encrypted->getInitVector(); // Get used (random if encode) init vector
// $decrypted->* has identical methods
JAVA:
String secretKey = "26kozQaKwRuNJ24t";
String text = "Some text";
AesCipher encrypted = AesCipher.encrypt(secretKey, text);
AesCipher decrypted = AesCipher.decrypt(secretKey, encrypted);
encrypted.hasError(); // TRUE if operation failed, FALSE otherwise
encrypted.getData(); // Encoded/Decoded result
encrypted.getInitVector(); // Get used (random if encode) init vector
// decrypted.* has identical methods
Related
I am finding it difficult to convert a piece of code to php from java.
I searched on the internet about the meaning of each line of code written in my java code example but I didn't find any.
I want to understand what each line does in this particular example.
This is what I tried.
function my_aes_encrypt($key, $data) {
if(16 !== strlen($key)) $key = hash('MD5', $key, true);
$padding = 16 - (strlen($data) % 16);
$data .= str_repeat(chr($padding), $padding);
return base64_encode(mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $data, MCRYPT_MODE_ECB, str_repeat("\0", 16)));
}
function my_aes_decrypt($str, $key){
$str = base64_decode($str);
$str = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key, $str, MCRYPT_MODE_ECB);
$block = mcrypt_get_block_size('rijndael_128', 'ecb');
$pad = ord($str[($len = strlen($str)) - 1]);
$len = strlen($str);
$pad = ord($str[$len-1]);
return substr($str, 0, strlen($str) - $pad);
}
Convert from Java to PHP
//provided key
byte[] keyBinary = DatatypeConverter.parseBase64Binary("r/RloSflFkLj3Pq2gFmdBQ==");
SecretKey secret = new SecretKeySpec(keyBinary, "AES");
// encrypted string
byte[] bytes = DatatypeConverter.parseBase64Binary("IKWpOq9rhTAz/K1ZR0znPA==");
// iv
byte[] iv = DatatypeConverter.parseBase64Binary("yzXzUhr3OAt1A47g7zmYxw==");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String msisdn = new String(cipher.doFinal(bytes), "UTF-8");
It would be great if you guys let me know the details of each line written in Java.
The functionalities of the Java- and the PHP-code differ significantly. First of all, the Java-code contains only the decryption part, whereas the PHP-part contains both, the encryption- and decryption part. Contrary to the Java-code, in the PHP-my_aes_decrypt-method the insecure ECB-mode (https://crypto.stackexchange.com/questions/20941/why-shouldnt-i-use-ecb-encryption) seems to be used instead of the CBC-mode and thus, no IV is involved. Less important, but nonetheless different, the key doesn't seem to be base64-encoded because it's not decoded anywhere. In addition, in the PHP-code deprecated methods like mcrypt_encrypt (http://php.net/manual/de/function.mcrypt-encrypt.php) or cryptographic weak algorithms like MD5 (https://en.wikipedia.org/wiki/MD5) are used.
If I get it right, the Java code is the reference code and you need the PHP-counterpart. Thus, I focus on the Java-code and ignore the differing and outdated PHP-code completely.
In the Java-code, the key, the data and the IV, all base64-encoded, become decoded and then, the encrypted data are decrypted using these decoded data.
A possible PHP-counterpart for the decryption could be:
<?php
$keyBinary = base64_decode('r/RloSflFkLj3Pq2gFmdBQ=='); // decode base64-encoded key in a string (internally, PHP strings are byte arrays)
$bytes = base64_decode('IKWpOq9rhTAz/K1ZR0znPA=='); // decode base64-encoded encrypted data in a string
$iv = base64_decode('yzXzUhr3OAt1A47g7zmYxw=='); // decode base64-encoded IV in a string
$msisdn = openssl_decrypt($bytes, 'AES-128-CBC', $keyBinary, OPENSSL_RAW_DATA, $iv); // decrypt data using AES-128, CBC-mode and PKCS7-Padding (default-padding)
// - when OPENSSL_RAW_DATA is specified raw data are returned, otherwise base64-encoded data (= default)
// - when OPENSSL_ZERO_PADDING is specified no padding is used, otherwise PKCS7-padding (= default)
// - The value XXX in AES-XXX-CBC is determined by the length of the key in Bit used in the Java-code,
// e.g. for a 32 Byte (256 Bit)-key AES-256-CBC has to be used.
print $msisdn."\n"; // Output: 1234567 // print decrypted data
The desired explanation for the Java-code can be found in the comments:
//provided key
byte[] keyBinary = DatatypeConverter.parseBase64Binary("r/RloSflFkLj3Pq2gFmdBQ=="); // decode base64-encoded key in a byte-array
SecretKey secret = new SecretKeySpec(keyBinary, "AES"); // create AES-key from byte-array (currently 16 Byte = 128 Bit long)
// encrypted string
byte[] bytes = DatatypeConverter.parseBase64Binary("IKWpOq9rhTAz/K1ZR0znPA=="); // decode base64-encoded encrypted data in a byte-array
// iv
byte[] iv = DatatypeConverter.parseBase64Binary("yzXzUhr3OAt1A47g7zmYxw=="); // decode base64-encoded IV in a byte-array
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); // create cipher-instance for using AES in CBC-mode with PKCS5-Padding (Java counterpart to PKCS7)
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv)); // initialize cipher-instance for decryption with specified AES-key and IV (the latter created from corresponding byte-array)
String msisdn = new String(cipher.doFinal(bytes), "UTF-8"); // decrypt data using AES-128 (128 determined by length of used key in Bit), CBC-mode and PKCS5-Padding,
// and put them in a UTF-8 string
System.out.println(msisdn); // Output: 1234567 // print decrypted data
The PHP-encryption part could be:
<?php
$keyBinary = base64_decode('r/RloSflFkLj3Pq2gFmdBQ==');
$msisdn = '1234567'; // plain text
$iv = openssl_random_pseudo_bytes(16); // generate random IV
//$iv = base64_decode('yzXzUhr3OAt1A47g7zmYxw=='); // use this line for tests with your base64-encoded test-IV yzXzUhr3OAt1A47g7zmYxw==
$bytes = openssl_encrypt($msisdn, 'AES-128-CBC', $keyBinary, OPENSSL_RAW_DATA, $iv); // encrypt data using AES-128, CBC-mode and PKCS7-Padding (default-padding)
$ivBase64 = base64_encode($iv); // base64-encode IV
$bytesBase64 = base64_encode($bytes); // base64-encode encrypted data
print $ivBase64."\n".$bytesBase64."\n"; // print base64-encoded IV and encrypted data
I was trying encryption in android and decryption in nodejs server. I generated an AES 128bit key and encrypt it using AES algorithm and then encrypt this generated key using RSA algorithm. Then send both to the server. But while decrypting on the server side, I think the RSA decryption works fine but have a problem in AES decryption.
I'm not getting the string in server side that I encrypted on the client side.
This is the code for the encryption on android side:
String encryptedSecretKey;
String cipherTextString;
// 1. generate secret key using AES
KeyGenerator keyGenerator = null;
keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
// 2. get string which needs to be encrypted
String text = "This is the message to be encrypted";
// 3. encrypt string using secret key
byte[] raw = secretKey.getEncoded();
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
cipherTextString = Base64.encodeToString(cipher.doFinal(text.getBytes(Charset.forName("UTF-8"))), Base64.DEFAULT);
// 4. get public key
X509EncodedKeySpec publicSpec = new X509EncodedKeySpec(Base64.decode(publicKeyString, Base64.DEFAULT));
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(publicSpec);
// 5. encrypt secret key using public key
Cipher cipher2 = Cipher.getInstance("RSA/ECB/OAEPWithSHA1AndMGF1Padding");
cipher2.init(Cipher.ENCRYPT_MODE, publicKey);
encryptedSecretKey = Base64.encodeToString(cipher2.doFinal(secretKey.getEncoded()), Base64.DEFAULT);
Then send this to the server side.
The code for server side is given below:
var encryptedMessage = req.body.cipherText;
var encryptedAesKey = req.body.secretKey;
//printing those values
console.log("\nEncryptedMessage: \n" + encryptedMessage);
console.log("\nEncrypted key: \n" + encryptedAesKey);
var privateKey = fs.readFileSync('././Keys/privkey_server.pem', "utf8");
var bufferForAesKey = new Buffer(encryptedAesKey, "base64");
var obj = {
key: privateKey
// , padding: constants.RSA_PKCS1_PADDING
// , padding: constants.RSA/ECB/OAEPWithSHA-1
};
var decryptedAes = crypto.privateDecrypt(obj, bufferForAesKey);
console.log("Decrypted AES: " + decryptedAes);
var decryptedAesKeyString = decryptedAes.toString("base64");
console.log("Decrypted AES Key: " + decryptedAesKeyString);
var bufferForAES = new Buffer(decryptedAes, "base64");
//decrypting using AES
var bufferForEncryptedMsg = new Buffer(encryptedMessage, "base64");
var decipher = crypto.createDecipher('aes-128-cbc',bufferForAES);
decipher.setAutoPadding(false);
var dec = decipher.update(bufferForEncryptedMsg,"base64", "utf8");
dec += decipher.final("utf8");
console.log(dec);
Here the final result 'dec' is not giving the correct result but the intermediate results are same in client and server. That means, RSA works fine but have problem in AES.
The output is given below:
EncryptedMessage:
SfosHg+cTrQXYUdF0FuqCJMHgfcP13ckp2L0B9QqOcl8UtWnnl8fLi5lxgR2SKOj
Encrypted key:
C/pa52PZda3xShrPXkHZx8OL6sW4JBEhG/ggNAoHhSVXIGt+iDq/B1ByG5yStBGF3GFJRQT0aGsG
+bZJydP7j9gTivmt99H/bxiZan4CHZnqfGKG1dJCI7ILAYZMCw7JhIcRC8qHMM4LMdF+rxRhENDe
alUfnsLWpcrX9J6fKejJ7EWnWQ1VadKqCDmrJ5xw0lBbsOpwN/vY09+VhF4WkOz8Y3cQGk+FVdz5
tr4L9/jgXlTZdOC2KVBLSH+9pvtHwMWFKKoDSAzvkil4htBjbWTqlBuEINC4I/J/4P3RX2riT5Pv
xHQi/Dv7vdBlo9AEdvWe3Ek8oBleIpmIQHXwQWknPOYghhBAVmACG/tbEQcAtbcmRLruT/XzjPJt
HNBt2HeG9JHYKNoHC3kOuJdnlGe8mv8k0Nzwj04RhEGKSmPIiu/oDgYwS0l96KIlS2ELqBlS5O0L
AJ+RBG7m0WwC9dfrufsuwu0+SPUmg5/ElXRmA3T81lXtQqQbGg8G6r/bAVFGduy4a49s/VWoylx+
/sI079IwyY0IOfwQTVGZRyDC5O1ZBjoYv2+TRo3bjG8GXNQoybkmWkhgotcqVD9mXO67D2NBsFPT
EJnw+1ApSqR7ggIAF+qsMxejFKBICBL/4J8FP4+obA07J1pWiciTRKX+G130qzIBKM08Zdaf/50=
Decrypted AES: %Kp[ϪS�/�W l��9ӊ˽��~��
B�A�
Decrypted AES Key: JUtwW8+qU6Mv/FcgbMbkOdOKy72pun4B490KQrRB4QQ=
T�Ϝ��u��q�
���w�p���u`�̗r[`H0[tW��=��~i-�W
Here the Decrypted AES key is same as the key that we generate in android. But the final output is not giving the desired result. Is there any error in my code??
Neardupe Decrypting strings from node.js in Java? which is the same thing in the opposite direction.
[In Java] I generated an AES 128bit key and encrypt [with] it using AES algorithm and then encrypt this generated key using RSA algorithm.
No you didn't. Your Java code instantiates a KeyGenerator for AES-128, but doesn't use it to generate any key. The key you actually used (and as you say the server correctly decrypted from RSA-OAEP) is 32 bytes, corresponding to AES-256.
But your main problem is that createDecipher takes a password NOT the key. Per the doc
crypto.createDecipher(algorithm, password[, options])
The implementation of crypto.createDecipher() derives keys using the OpenSSL function EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt.
You passed what is actually a key as a password; this results in nodejs using a key that is completely different from the one used in Java and thus getting completely wrong results. You should instead use createDecipheriv which does take the key, and IV (Initialization Vector).
And that is your other problem. To decrypt you must use the same IV as encrypt did, normally by including the IV with the ciphertext sent from the sender to receiver, but you don't. As a result the following (simplified) code cannot decrypt the first 16 bytes of your data, but does the rest.
const crypto = require('crypto');
msg = Buffer.from('SfosHg+cTrQXYUdF0FuqCJMHgfcP13ckp2L0B9QqOcl8UtWnnl8fLi5lxgR2SKOj','base64');
aeskey = Buffer.from('JUtwW8+qU6Mv/FcgbMbkOdOKy72pun4B490KQrRB4QQ=','base64');
dec = crypto.createDecipheriv('aes-256-cbc',aeskey,Buffer.alloc(16)/*this should be the IV*/);
console.log(dec.update(msg,'','latin1')+dec.final('latin1'));
// I used latin1 instead of utf8 because the garbaged first block
// isn't valid UTF-8, and the rest is ASCII which works as either.
->
Y;øï«*M2WÚâeage to be encrypted
// some garbaged chars are control chars and Stack (or browser?)
// may not display them but there really are 16 in total
As an aside, the statement in the doc that 'Initialization vectors [must] be unpredictable and unique ... [but not secret]' is correct for CBC mode, but not some other modes supported by OpenSSL (thus nodejs) and Java. However, that's not a programming Q and thus offtopic here; it belongs on crypto.SX or possibly security.SX where it has already been answered many times.
I'm only asking this because I have read many posts for 2 days now about crypto AES encryption, and just when I thought I was getting it, I realized I wasn't getting it at all.
This post is the closest one to my issue, I have exactly the same problem but it is unanswered:
CryptoJS AES encryption and JAVA AES decryption value mismatch
I have tried doing it in many ways but I haven't gotten it right.
First Off
I'm getting the already encrypted string (I only got the code to see how they were doing it), so modifying the encryption way is not an option. That's why all the similar questions aren't that useful to me.
Second
I do have access to the secret key and I can modify it (so adjusting length is an option if neccessary).
The encryption is done on CryptoJS and they send the encrypted string as a GET parameter.
GetParamsForAppUrl.prototype.generateUrlParams = function() {
const self = this;
return new Promise((resolve, reject) => {
const currentDateInMilliseconds = new Date().getTime();
const secret = tokenSecret.secret;
var encrypted = CryptoJS.AES.encrypt(self.authorization, secret);
encrypted = encrypted.toString();
self.urlParams = {
token: encrypted,
time: currentDateInMilliseconds
};
resolve();
});
};
I can easily decrypt this on javascript using CryptoJS with:
var decrypted = CryptoJS.AES.decrypt(encrypted_string, secret);
console.log(decrypted.toString(CryptoJS.enc.Utf8));
But I don't want to do this on Javascript, for security reasons, so I'm trying to decrypt this on Java:
String secret = "secret";
byte[] cipherText = encrypted_string.getBytes("UTF8");
SecretKey secKey = new SecretKeySpec(secret.getBytes(), "AES");
Cipher aesCipher = Cipher.getInstance("AES");
aesCipher.init(Cipher.DECRYPT_MODE, secKey);
byte[] bytePlainText = aesCipher.doFinal(byteCipherText);
String myDecryptedText = = new String(bytePlainText);
Before I had any idea of what I was doing, I tried base64 decoding, adding some IV and a lot of stuff I read, of course none of it worked.
But after I started to understand, kinda, what I was doing, I wrote that simple script above, and got me the same error on the post: Invalid AES key length
I don't know where to go from here. After reading a lot about this, the solution seems to be hashing or padding, but I have no control on the encryption method, so I can't really hash the secret or pad it.
But as I said, I can change the secret key so it can match some specific length, and I have tried changing it, but as I'm shooting in the dark here, I don't really know if this is the solution.
So, my question basically is, If I got the encrypted string (in javascript like the first script) and the secret key, is there a way to decrypt it (in Java)? If so, how to do it?
Disclaimer: Do not use encryption unless you understand encryption concepts including chaining mode, key derivation functions, IV and block size. And don't roll your own security scheme but stick to an established one. Just throwing in encryption algorithms doesn't mean an application has become any more secure.
CryptoJS implements the same key derivation function as OpenSSL and the same format to put the IV into the encrypted data. So all Java code that deals with OpenSSL encoded data applies.
Given the following Javascript code:
var text = "The quick brown fox jumps over the lazy dog. 👻 👻";
var secret = "René Über";
var encrypted = CryptoJS.AES.encrypt(text, secret);
encrypted = encrypted.toString();
console.log("Cipher text: " + encrypted);
We get the cipher text:
U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=
On the Java side, we have
String secret = "René Über";
String cipherText = "U2FsdGVkX1+tsmZvCEFa/iGeSA0K7gvgs9KXeZKwbCDNCs2zPo+BXjvKYLrJutMK+hxTwl/hyaQLOaD7LLIRo2I5fyeRMPnroo6k8N9uwKk=";
byte[] cipherData = Base64.getDecoder().decode(cipherText);
byte[] saltData = Arrays.copyOfRange(cipherData, 8, 16);
MessageDigest md5 = MessageDigest.getInstance("MD5");
final byte[][] keyAndIV = GenerateKeyAndIV(32, 16, 1, saltData, secret.getBytes(StandardCharsets.UTF_8), md5);
SecretKeySpec key = new SecretKeySpec(keyAndIV[0], "AES");
IvParameterSpec iv = new IvParameterSpec(keyAndIV[1]);
byte[] encrypted = Arrays.copyOfRange(cipherData, 16, cipherData.length);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
String decryptedText = new String(decryptedData, StandardCharsets.UTF_8);
System.out.println(decryptedText);
The result is:
The quick brown fox jumps over the lazy dog. 👻 👻
That's the text we started with. And emojis, accents and umlauts work as well.
GenerateKeyAndIV is a helper function that reimplements OpenSSL's key derivation function EVP_BytesToKey (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
/**
* Generates a key and an initialization vector (IV) with the given salt and password.
* <p>
* This method is equivalent to OpenSSL's EVP_BytesToKey function
* (see https://github.com/openssl/openssl/blob/master/crypto/evp/evp_key.c).
* By default, OpenSSL uses a single iteration, MD5 as the algorithm and UTF-8 encoded password data.
* </p>
* #param keyLength the length of the generated key (in bytes)
* #param ivLength the length of the generated IV (in bytes)
* #param iterations the number of digestion rounds
* #param salt the salt data (8 bytes of data or <code>null</code>)
* #param password the password data (optional)
* #param md the message digest algorithm to use
* #return an two-element array with the generated key and IV
*/
public static byte[][] GenerateKeyAndIV(int keyLength, int ivLength, int iterations, byte[] salt, byte[] password, MessageDigest md) {
int digestLength = md.getDigestLength();
int requiredLength = (keyLength + ivLength + digestLength - 1) / digestLength * digestLength;
byte[] generatedData = new byte[requiredLength];
int generatedLength = 0;
try {
md.reset();
// Repeat process until sufficient data has been generated
while (generatedLength < keyLength + ivLength) {
// Digest data (last digest if available, password data, salt if available)
if (generatedLength > 0)
md.update(generatedData, generatedLength - digestLength, digestLength);
md.update(password);
if (salt != null)
md.update(salt, 0, 8);
md.digest(generatedData, generatedLength, digestLength);
// additional rounds
for (int i = 1; i < iterations; i++) {
md.update(generatedData, generatedLength, digestLength);
md.digest(generatedData, generatedLength, digestLength);
}
generatedLength += digestLength;
}
// Copy key and IV into separate byte arrays
byte[][] result = new byte[2][];
result[0] = Arrays.copyOfRange(generatedData, 0, keyLength);
if (ivLength > 0)
result[1] = Arrays.copyOfRange(generatedData, keyLength, keyLength + ivLength);
return result;
} catch (DigestException e) {
throw new RuntimeException(e);
} finally {
// Clean out temporary data
Arrays.fill(generatedData, (byte)0);
}
}
Note that you have to install the Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy. Otherwise, AES with key size of 256 won't work and throw an exception:
java.security.InvalidKeyException: Illegal key size
Update
I have replaced Ola Bini's Java code of EVP_BytesToKey, which I used in the first version of my answer, with a more idiomatic and easier to understand Java code (see above).
Also see How to decrypt file in Java encrypted with openssl command using AES?.
When encrypting on one system and decrypting on another you are at the mercy of system defaults. If any system defaults do not match (and they often don't) then your decryption will fail.
Everything has to be byte for byte the same on both sides. Effectively that means specifying everything on both sides rather than relying on defaults. You can only use defaults if you are using the same system at both ends. Even then, it is better to specify exactly.
Key, IV, encryption mode, padding and string to bytes conversion all need to be the same at both ends. It is especially worth checking that the key bytes are the same. If you are using a Key Derivation Function (KDF) to generate your key, then all the parameters for that need to be the same, and hence specified exactly.
Your "Invalid AES key length" may well indicate a problem with generating your key. You use getBytes(). That is probably an error. You need to specify what sort of bytes you are getting: ANSI, UTF-8, EBCDIC, whatever. The default assumption for the string to byte conversion is the likely cause of this problem. Specify the conversion to be used explicitly at both ends. That way you can be sure that they match.
Crypto is designed to fail if the parameters do not match exactly for encryption and decryption. For example, even a one bit difference in the key will cause it to fail.
I have "inherited" a Ruby on Rails app, and I must translate this app from Ruby to Java, and the most important thing, I don't have contact with the creator.
My problem is with the IV vector in AES-256 authentication. Ruby app uses AESCrypt gem to encrypt and decrypt user's password. It works fine, and I have already some thousands of users in DB.
The problem is when I try to do the same in Java (I've already updated JCE to allow 256bit key lenght). The Key and the IV are writen as binary strings in ruby source code (see bellow), and when I try to use it in Java I get a exception which say that the IV lenght must be 16 bytes long (I know that it must be 16 bytes long, but the binary string in Ruby has 32 characters).
Ruby code (works fine):
require 'openssl'
require 'digest/md5'
require 'base64'
module AESCrypt
KEY = "AB1CD237690AF13B6721AD237A"
IV = "por874hyufijdue7w63ysxwet4320o90"
TYPE = "AES-256-CBC"
def AESCrypt.key(key)
key = Digest::MD5.hexdigest(key)
key.slice(0..32)
end
# Encrypts a block of data given an encryption key and an
# initialization vector (iv). Keys, iv's, and the data returned
# are all binary strings. Cipher_type should be "AES-256-CBC",
# "AES-256-ECB", or any of the cipher types supported by OpenSSL.
# Pass nil for the iv if the encryption type doesn't use iv's (like
# ECB).
#:return: => String
#:arg: data => String
#:arg: key => String
#:arg: iv => String
#:arg: cipher_type => String
def AESCrypt.encrypt(data)
return nil if data.nil?
return data if data.blank?
aes = OpenSSL::Cipher::Cipher.new(TYPE)
aes.encrypt
aes.key = AESCrypt.key(KEY)
aes.iv = IV if IV != nil
result = aes.update(data) + aes.final
Base64.encode64(result)
end
end
and this is my Java code (it should do the same, seems that works with a 16 chars/bytes IV):
public static void main(String[] args) throws UnsupportedEncodingException {
String KEY = "AB1CD237690AF13B6721AD237A";
String IV = "por874hyufijdue7w63ysxwet4320o90";
SecretKeySpec key = generateKey(KEY);
String message = "password";
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
IvParameterSpec ivSpec = new IvParameterSpec(IV.getBytes());
cipher.init(Cipher.ENCRYPT_MODE, key, ivSpec);
byte[] ciphedText = cipher.doFinal(message.getBytes());
String encoded = Base64.encodeBase64String(ciphedText);
System.out.println("ENCRYPTED text= " + encoded);
}
public static SecretKeySpec generateKey(final String password) throws NoSuchAlgorithmException, UnsupportedEncodingException {
final MessageDigest digest = MessageDigest.getInstance("MD5");
byte[] bytes = password.getBytes("UTF-8");
digest.update(bytes, 0, bytes.length);
byte[] key = digest.digest();
SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES");
return secretKeySpec;
}
And I'm getting this exception (obviously):
java.security.InvalidAlgorithmParameterException: Wrong IV length: must be 16 bytes long
at com.sun.crypto.provider.CipherCore.init(CipherCore.java:516)
at com.sun.crypto.provider.AESCipher.engineInit(AESCipher.java:339)
at javax.crypto.Cipher.implInit(Cipher.java:801)
at javax.crypto.Cipher.chooseProvider(Cipher.java:859)
at javax.crypto.Cipher.init(Cipher.java:1370)
at javax.crypto.Cipher.init(Cipher.java:1301)
at com.javi.test.security.Test.main(Test.java:129)
I guess my problem is the way I convert the IV java string in byte[]. I think that openSSL code in ruby is unpacking (or doing something internally) the 32 bytes of the IV to 16 bytes. I have tried a lot of things, but I'm going crazy.
Anyone had the same problem or figure out where could be my problem?
I have posted the encryption code but I hace the same issue with decryption.
Thanks in advance, I'll be very grateful with every answer. :)
First, your IV is not actually iv, IV should be HEX encoded, but you have ASCII string "por874hyufijdue7w63ysxwet4320o90", may be it is some how encoded?
Second, IV.getBytes() will transofr IV's each character to hex encoding like p = 0x70, o = 0x6F, r = 0x72, etc...
It is not a useful answer, but may be hint.
Actually IV must be the same length as block cipher single block length. You have 32 bytes long IV itself, if you make IV.getBytes() IV length should match the cipher block length
I'm trying to decrypt text in java that is encrypted using CryptoJS. I've read on other posts that they use different default modes and padding so I set them both(java/cryptojs) both to use aes/cbc/nopadding. I no longer get an exception in java, but I am getting a garbled output during decryption
Encryption(JS):
var parsedLogin = JSON.parse(login);
var publicKey = "abcdefghijklmnio";
var publiciv = "abcdefghijklmnio";
var key = CryptoJS.enc.Hex.parse(publicKey);
var iv = CryptoJS.enc.Hex.parse(publiciv);
var encrypted = CryptoJS.AES.encrypt(parsedLogin.password, publicKey, {iv: publiciv}, { padding: CryptoJS.pad.NoPadding, mode: CryptoJS.mode.CBC});
// send encrypted to POST request
DECRYPT (Java)
String PUBLIC_KEY = "abcdefghijklmnio";
String PUBLIC_IV = "abcdefghijklmnio";
byte[] byteArr = PUBLIC_KEY.getBytes();
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
final SecretKeySpec secretKey = new SecretKeySpec(byteArr, "AES");
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(PUBLIC_IV.getBytes()));
byte[] parsed = Base64.decodeBase64(encrypted.getBytes());
//byte[] parsed = DatatypeConverter.parseBase64Binary(encrypted);
byte[] fin = cipher.doFinal(parsed);
String decryptedString = new String(fin);
The result that I'm getting is like this: Š²Û!aå{’`#"Ûîñ?Œr˜krÆ
I have already tried changing the CHARSET in the getBytes() to US-ASCII, UTF-8 and UTF-16 but all this does is change the garbled text
I have also tried using othe blocking modes and paddings but they failed at the js level. I just need a simple encryption method right now.
NOTE:
Ignore the security issues...like having the key exposed in js, etc. I'll be handling those later..
You shouldn't be able to use AES CBC without padding unless the password is always 16 bytes. It probably applies some sort of default padding that may or may not be a good idea.
Anyway: you need to pass your key and iv to CryptoJS as a WordArray; if you give it a string it will assume you're giving it a passphrase and derive a different key from that. As such, your Java decryption code will be using a different key/iv pair. You can create a WordArray from your strings using
var key = CryptoJS.enc.Utf8.parse("abcdefghijklmnio")
var iv = ...