I have administration site developed with CakePHP 3.0 framework and i use default Security::encrypt($text, $key, $hmacSalt = null) to encrypt token for API authorization.
I also have simple NodeJS service for real time communication and i want to use the same token for API and this real time communication.
I try to rewrite CakePHP decryption function to NodeJS on different ways but i can't get correct results. Below is CakePHP decrypt function:
public static function decrypt($cipher, $key)
{
$method = 'AES-256-CBC';
$ivSize = openssl_cipher_iv_length($method);
$iv = mb_substr($cipher, 0, $ivSize, '8bit');
echo "---- IV --- \r\n";
var_dump($iv);
$cipher = mb_substr($cipher, $ivSize, null, '8bit');
echo "---- KEY --- \r\n";
var_dump($key);
echo "---- CIPHER LAST --- \r\n";
var_dump($cipher);
return openssl_decrypt($cipher, $method, $key, OPENSSL_RAW_DATA, $iv);
}
Result from CakePHP:
---- IV ---
string(16) "��r�N3U�Y6Q�#��"
---- KEY ---
string(32) "1c494314996afe280bc5981c4e185f79"
---- CIPHER LAST ---
string(160) "~a�xh�z��+���M����j*!�(����f�ZG;�)w��Kl�3�m��Z��ە��OR9~���6[X�/��n��B6��C��˟f��!6��1���|S��*�mG+���OR�kr��t�;�+�㟱��"���<i����e:��"
Here is my simple code in NodeJS:
var buf = new Buffer(socket.handshake.query.token, 'base64').toString('utf8', 64);
var iv = buf.substr(0,16);
console.log("-----IV------")
console.log(iv);
var key = sha256(config.tokenKey+config.tokenSalt).substr(0,32);
console.log("-----KEY------")
console.log(key);
var cipher = buf.substr(16);
console.log("------CIPHER-----");
console.log(cipher);
var decipher = crypto.createDecipheriv('AES-256-CBC', key, iv);
//decipher.setAutoPadding(false);
var dec = decipher.update(cipher);
dec += decipher.final('utf-8');
Result from NodeJS:
-----IV------
��r�N3U�Y6Q�#��
-----KEY------
1c494314996afe280bc5981c4e185f79
------CIPHER-----
~a�xh�z��+���M���
��j*!�(����f�ZG;�)w��Kl��m���Z����ە��OR9~���6[X�/��n��B6��C��˟f���!6��1���|S��*�mG+���OR�kr��t�;�+�㟱��"���<i����e:��
crypto.js:239
this._handle.initiv(cipher, toBuf(key), toBuf(iv));
Error: Invalid IV length
I try to create IV on different ways, but it doesnt work, even if i succeed to avoid exception i dont get correct result, I assume that issue is in "8bit" encoding in PHP code.
If someone know how to solve this i would be very thankful!
I'm not overly familiar with Node.js, but what I can see is that you screw up the data when you convert the input to an UTF-8 string, you need to work with binary data, not with a string.
I'd suggest to work with buffers until you actually need to convert something to a string, at least that's how I did it when I had to decrypt data that was encrypted with CakePHP:
var data = Buffer.from(socket.handshake.query.token, 'base64');
var key = config.tokenKey;
var salt = config.tokenSalt;
var hmacSize = 64;
var ivSize = 16;
var keySize = 32;
var encrypted = data.slice(hmacSize);
key = crypto
.createHash('sha256')
.update(key + salt)
.digest('hex')
.substr(0, keySize);
var iv = encrypted.slice(0, ivSize);
encrypted = encrypted.slice(ivSize);
var decipher = crypto.createDecipheriv('AES-256-CBC', key, iv);
var decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
console.log(decrypted.toString('utf8'));
Related
The key that I have to use for decryption is a 80 characters long string.
I can't figure out the length of iv needed for that kind of key in the Node.js implementation.
PHP snippet that I'm trying to convert to Node.js:
protected function Decryption($theData, $theKey) {
$method = 'aes-256-ctr';
$nonceSize = openssl_cipher_iv_length($method);
$nonce = mb_substr($theData, 0, $nonceSize, '8bit');
$ciphertext = mb_substr($theData, $nonceSize, null, '8bit');
$plaintext = openssl_decrypt( $ciphertext, $method, $theKey, OPENSSL_RAW_DATA, $nonce);
return json_decode($plaintext);
}
Node.js implementation, throwing Invalid IV length error:
const crypto = require('crypto');
const nonce = crypto.randomBytes(16).toString('base64');
const decipher = crypto.createDecipheriv('aes-256-ctr', theKey, nonce);
let plainText = decipher.update(theData, 'base64', 'utf8');
plainText += decipher.final('utf8');
console.log(plainText);
This could be solved by using different length of key but in this case I need to use 80 characters.
EDIT
After updating to use the fix from Getting error of Invalid IV Length while using aes-256-cbc for encryption in node , I'm getting Invalid key length error with this code:
const crypto = require('crypto');
var iv = new Buffer(crypto.randomBytes(16))
var nonce = iv.toString('hex').slice(0, 16);
const decipher = crypto.createDecipheriv('aes-256-ctr', theKey, nonce);
let plainText = decipher.update(theData, 'base64', 'utf8');
plainText += decipher.final('utf8');
console.log(plainText);
We have a legacy PHP system that encrypted some data via openssl_encrypt. The PHP code is pretty straight forward. (All values are randomly generated for this example, but are the same format and lengths as the real values and reproduce the same errors).
$in = '12345';
$method = 'AES-256-CBC';
$key = '5fjfwc7kp84z5yet358t';
$options = 0;
$iv = '8x69nt6qnptg3x4j';
openssl_encrypt($in, $method, $key, $options, $iv);
Decrypting via PHP is also pretty straight forward.
$in = 'yy03+cUpsq5uGWclBLtwIA==';
$method = 'AES-256-CBC';
$key = '5fjfwc7kp84z5yet358t';
$options = 0;
$iv = '8x69nt6qnptg3x4j';
openssl_decrypt($in, $method, $key, $options, $iv);
However, when trying to port it over to Node crypto I keep getting errors on key length, iv length, and numerous other errors as I try different approaches.
const input = Buffer.from('yy03+cUpsq5uGWclBLtwIA==');
const iv = Buffer.from('8x69nt6qnptg3x4j');
const key = Buffer.from('5fjfwc7kp84z5yet358t');
let decipher = crypto.createDecipheriv('aes-256-cbc', key, iv, 0);
let clearText = decipher.update(input, 'base64', 'utf8');
clearText += decipher.final('utf8');
I've probably tried half a dozen or more examples in NodeJS and all produce errors and fail to decrypt entirely.
Current error is "Invalid key length" which remains the error even if I restrict it to 16 characters.
Padding and base64 processing was the solution. Working code looks closer to this:
const keyStr = '5fjfwc7kp84z5yet358t';
const diff = Math.abs(keyStr.length - 32);
const padding = Buffer.alloc(diff, 0x00);
const input = Buffer.from('yy03+cUpsq5uGWclBLtwIA==', 'base64');
const iv = Buffer.from('8x69nt6qnptg3x4j');
let key = Buffer.from('5fjfwc7kp84z5yet358t');
key = Buffer.concat([key, padding]);
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv, 0);
let clearText = decipher.update(input, 'base64', 'utf8');
clearText += decipher.final('utf8');
I'm having trouble getting a message encrypted with cryptojs decrypted with php.
On the javascript side, the code to create the encryption is:
var keySize = 256;
var ivSize = 128;
var saltSize = 256;
var iterations = 100;
var message = "This is my test";
var password = "thepassword";
function encrypt(msg, pass) {
// Generate salt, key and iv
var salt = CryptoJS.lib.WordArray.random(saltSize / 8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: 256 / 32,
iterations: iterations
});
console.log('Message key: ' + key);
var iv = CryptoJS.lib.WordArray.random(ivSize / 8);
// encrypt message
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
// convert encrypted message to hex
var encryptedHex = base64ToHex(encrypted.toString());
// Prepare result to transmit
var base64result = hexToBase64(salt + iv + encryptedHex);
return base64result;
}
This creates a string like:
g281MRrrEdiysHSAolnMmy3Au3yYkb2TK1t7iF4dv8X2k9Fod1DkOt/LF8eLgX8OxRvkSOMqtrcGEMaCL7A8YVBcugcirNg44HcWGWt+hfA=
When I bring that into php, I can correctly pull back the pieces sent (salt, iv, message), but can't decode the message.
$text_key = 'thepassword';
$cipher = "aes-256-cbc";
$received_message = $_REQUEST['message'];
// Decode message and pull out pieces:
$decoded = base64_decode($received_message);
$hex_version = bin2hex($decoded);
// Pull out salt, iv and encrypted message
$salt = substr($hex_version, 0,64);
$iv = substr($hex_version, 64,32);
$encrypted_string = substr($hex_version, 96);
// Message key
$generated_key = bin2hex(openssl_pbkdf2($text_key, $salt, 32, 100, 'sha256'));
// Decode Message
$result = openssl_decrypt($text_encoded, $cipher, $generated_key, $options=0, hex2bin($iv));
If I replace $generated_key with the key displayed in the javascript console, however, the message decrypts successfully.
What am I doing incorrectly to generate the key in php?
After creating a routine to run through all possible algorithms for openssl_pbkdf2 and the hash_pbkdf2 functions, discovered that the hash_pbkdf2 function is the one that will create the key:
$generated_key = hex2bin(hash_pbkdf2('sha1', $text_key, hex2bin($salt), 100, 64, FALSE));
Once the right algorithm and size was in place, the decryption works as expected.
I am attempting to reproduce an encryption operation using AES-256-CCM that is currently performed in Java with the Bouncy Castle provider. When attempting the same operation in PHP using openssl I cannot find a set of parameters that produces the same output.
As the AEAD modes were recently added to PHP (7.1), documentation on how this works is scarce.
A minimum example of the "working" encryption in Java looks like:
public static void main(String args[]) {
try {
java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.decodeBase64("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = Base64.decodeBase64("rcFcdcgZ3Q/A+uHW".getBytes());
SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));
byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: 411d89ff74205c106d8d85a8
}
catch (Throwable e) {
e.printStackTrace();
}
}
As I am trying to re-produce this using different two different libraries and languages I have set the key and iv to known values.
When trying to re-produce this using PHP and openssl I am trying with the following code
$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = base64_decode('rcFcdcgZ3Q/A+uHW');
$data = 'test';
$tag = null;
$encrypted = openssl_encrypt($data,'aes-256-ccm', $key,OPENSSL_RAW_DATA, $iv, $tag,"",8);
echo(bin2hex($encrypted . $tag));
// d1a7403799b8c37240f36edb
Clearly the results do not match. In search of an answer as to what is incorrect I created the same operation using SJCL in javascript. The example for that is:
var data = "test";
var key = sjcl.codec.base64.toBits("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
var iv = sjcl.codec.base64.toBits("rcFcdcgZ3Q/A+uHW");
var p = {
adata: "",
iter: 0,
mode: "ccm",
ts: 64,
ks: 256,
iv: iv,
salt: ""
};
var encrypted = sjcl.encrypt(key, data, p, {});
console.log(encrypted);
// Output: {"iv":"rcFcdcgZ3Q/A+uHW","v":1,"iter":0,"ks":256,"ts":64,"mode":"ccm","adata":"","cipher":"aes","salt":"","ct":"QR2J/3QgXBBtjYWo"}
// QR2J/3QgXBBtjYWo === 411d89ff74205c106d8d85a8
The Bouncy Castle and SJCL libraries produce the same output but I can't tell what is different.
I have tried pre-processing the key with PBKDF2 as suggested in Encrypt in Javascript with SJCL and decrypt in PHP with no success. I have tried SHA256'ing the key with no success.
Why is the output in php/openssl different than Bouncy Castle and SJCL?
When I stumbled upon a similar problem, I discovered that the problem resided in the IV, more precisely: the length of it. As far as You use an IV with the length under 12, it results with the same hashes. You can try it with your own code:
java.security.Security.addProvider(new BouncyCastleProvider());
byte[] key = Base64.getDecoder().decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=".getBytes());
byte[] iv = "12345678901".getBytes();
SecretKey aesKey = new SecretKeySpec(key, 0, key.length, "AES");
Cipher aesCipher = Cipher.getInstance("AES/CCM/NoPadding", "BC");
aesCipher.init(1, aesKey, new IvParameterSpec(iv));
byte[] encrypted = aesCipher.doFinal("test".getBytes());
System.out.println(Hex.encodeHex(encrypted));
// Output: e037af9889af21e78252ab58
and same with PHP:
$key = base64_decode("Z4lAXU62WxDi46zSV67FeLj3hSK/th1Z73VD4/y6Eq4=");
$iv = "12345678901";
$tag = null;
$encrypted = openssl_encrypt("test", "aes-256-ccm", $key, OPENSSL_RAW_DATA, $iv, $tag, null, 8);
print bin2hex($encrypted . $tag);
# e037af9889af21e78252ab58
If you would extend the IV, you'll see the results will differ.
NB! Keep in mind that if you'd shorten the AES key (to 128 bytes), then Java will automatically switch to aes-128, but in PHP you have to change the algorithm manually.
I'm trying to decrypt a string, previously encrypted by a third party software using PHP RIJNDAEL_128 in CBC mode, using node.js.
Here is an interactive link of the following PHP code, in a sandbox, so you can compile and see for yourself. http://sandbox.onlinephpfunctions.com/code/504a7d052c5b123fac8103a073c05c2ff5f80571
PHP source code:
<?php
class CryptClass{
private $key;
public function __construct($key){
$this->key = $key;
}
public function cryptage($message){
$key = base64_decode($this->key);
$iv_size = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $message, MCRYPT_MODE_CBC, $iv);
$ciphertext = $iv . $ciphertext;
return base64_encode($ciphertext);
}
public function decryptage($message){
$key = base64_decode($this->key);
$iv_size2 = mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC);
$ciphertext_dec = base64_decode($message);
$iv_dec = substr($ciphertext_dec, 0, $iv_size2);
$ciphertext_dec = substr($ciphertext_dec, $iv_size2);
$message_decrypt = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $key,$ciphertext_dec, MCRYPT_MODE_CBC, $iv_dec);
return str_replace("\0", "", $message_decrypt);
}
}
// Secret key and data
define('KEY', 'azertyuiolskzif');
define('DATA', 'user#email.com|1477576941|origin.com');
// Crypt
$Crypt = new CryptClass(base64_encode(KEY));
$encodedData = base64_encode($Crypt->cryptage(DATA));
// Decrypt
$decodedData = $Crypt->decryptage(base64_decode($encodedData));
echo 'base64_encode: '.base64_encode(KEY);
echo "\nDATA: ".DATA;
echo "\nDATA length: ".strlen(DATA);
echo "\n\nencodedData: ".$encodedData;
echo "\n\ndata: ".$decodedData;
echo "\n\ndata length: ".strlen($decodedData);
echo "\n\ncrypt/decrypt match?: ".(DATA == $decodedData ? 'yes':'no');
Here is my implementation in node.js: NOT WORKING, see below for working solution
var crypto = require('crypto');
var textToEncrypt = 'user#email.com|1477576941|origin.com';
var encryptionMethod = 'AES-128-CBC';
var secret = "azertyuiolskzif";
var iv = 'aaaabbbbccccdddd';
var encrypt = function (plain_text, encryptionMethod, secret, iv) {
var encryptor = crypto.createCipheriv(encryptionMethod, secret, iv);
return encryptor.update(plain_text, 'utf8', 'base64') + encryptor.final('base64');
};
var decrypt = function (encryptedMessage, encryptionMethod, secret, iv) {
var decryptor = crypto.createDecipheriv(encryptionMethod, secret, iv);
return decryptor.update(encryptedMessage, 'base64', 'utf8') + decryptor.final('utf8');
};
var encryptedMessage = encrypt(textToEncrypt, encryptionMethod, secret, iv);
var decryptedMessage = decrypt(encryptedMessage, encryptionMethod, secret, iv);
console.log(decrypt());
console.log(encryptedMessage);
console.log(decryptedMessage);
I have tried many things and I'm getting lost here between Invalid key length and other error messages. One thing I don't quite understand is that the KEY apparently used to encrypt the data is azertyuiolskzif which is 15 chars long while most script use a required 32 chars string... Maybe PHP doesn't need a 32 char string but Node does?
Or maybe it's related to the difference between 128 and 256. Or is it due to the difference of padding between PHp and Node implementation?
I tried to follow the advices given at Encrypt string in PHP and decrypt in Node.js but even tho, I didn't succeed at encrypting my data in node yet.
Edit:
After some more digging around (and thanks for the explanations in the answers), I finally made crypt/decrypt work in Node.js. But I haven't succeeded to decrypt something crypted by PHP yet.
var crypto = require('crypto');
var AES = {};
AES.encrypt = function(dataToEncrypt, encryptionMethod, secret, iv, padding) {
var encipher = crypto.createCipheriv(encryptionMethod, secret, iv);
encipher.setAutoPadding(padding || 0); // "true" or "128" would work with aes-128-cbc
var encryptedData = encipher.update(dataToEncrypt, 'utf8', 'base64');
encryptedData += encipher.final('base64');
return encryptedData;
};
AES.decrypt = function(encryptedData, encryptionMethod, secret, iv, padding) {
var decipher = crypto.createDecipheriv(encryptionMethod, secret, iv);
decipher.setAutoPadding(padding || 0); // "true" or "128" would work with aes-128-cbc
var decoded = decipher.update(encryptedData, 'base64', 'utf8');
decoded += decipher.final('utf8');
return decoded;
};
// ----
var textToEncrypt = 'user#email.com|1477576941|origin.com';
var secret = "aaaabbbbccccdddd"; // Must be 16 chars
var iv = crypto.randomBytes(16); // Must be 16 chars
var encryptionMethod = 'AES-128-CBC';
// Testing crypt/decrypt using Node.js algorithm.
var encryptedMessage = AES.encrypt(textToEncrypt, encryptionMethod, secret, iv, 128);
var decryptedMessage = AES.decrypt(encryptedMessage, encryptionMethod, secret, iv, 128);
console.log('encryptedMessage', encryptedMessage); // Displays "GWpMWORNKkqlrHJDPuNgSmTKr1vJhaAApHP+ssK3SH5EALTkdWneUZRp9PXNpVQ2"
console.log('decryptedMessage', decryptedMessage); // Displays "user#email.com|1477576941|origin.com"
// Testing decrypt from a string generated by PHP algorithm.
// XXX Doesn't work "Error: error:0606506D:digital envelope routines:EVP_DecryptFinal_ex:wrong final block length"
// XXX Probably due to wrong padding between PHP/Node implementation?
var stringToDecode = 'RlM3Wkl3N3JRM0dnaEh4SkdoZWFDRy9mZGRoTnkxNlZUL2IvcHl4TkdzUUlRSXQwSWNwWUZ5OFpaRENZQys3S2t0bFZIUWoweUVsZGxUU21sYU9tS0E9PQ==';
console.log('stringToDecode', stringToDecode);
console.log(AES.decrypt(
stringToDecode,
encryptionMethod, secret, iv, 128
));
AES keys must be exactly one of 128, 192 or 256-bits. Some implementations will pad keys in some way but this should not be relied on. Make the key a correct size.