I want to implement AES encryption using JavaScript. Used AES CBC Mode. I have managed to do it in PHP. It looks like:
public function encrypt($value) {
if (empty($value)) {
return $value;
}
$value = Unicode::convertToUtf8($value, 'UTF-8');
if ($key = $this->getEncryptionKey()) {
// Generates from key 1st 16 bytes.
$iv = mb_substr($key, 0, 16);
//encrypt message with key
$message = openssl_encrypt($value, 'AES-256-CBC', $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($message);
}
}
public function getEncryptionKey() {
$key = 'secret';
$key = Unicode::convertToUtf8($key, 'UTF-8');
// Make sure the key is the correct size.
if (strlen($key) < 32) {
$key = str_pad($key, 32, "\0");
}
if (strlen($key) > 32) {
$key = mb_substr($key, 0, 32);
}
return $key;
}
If I give $value = retest2; it gives me ukUCH0SvgdmM8vTqQumAVg== output
I know it's right, I tried it using C# as well got the same result. But when I try to replicate this using JavaScript, I wasn't able to produce same PHP's output. Below is the javascript code that I have tried:
const message = utf8.encode('retest2');
const password = utf8.encode('secret').padEnd(32, '\0');
const key = CryptoJS.enc.Hex.parse(password);
const iv = CryptoJS.enc.Hex.parse(password.substring(0, 16));
const encrypted = CryptoJS.AES.encrypt(message, key, {
iv: iv
});
console.log(btoa(encrypted.toString()));
Using the same value I get dzd4bjNwenduQT09.
I have also read other similar questions asked on the same topic here, but I can't seem to figure out where I am going wrong?
Thanks!
As #symcbean said,
You shouldn't be using the key or data derived from it as your
initialization vector.
I assume, you have no option and you have to use key or data derived from it as your initialization vector.
A few months ago, I had the exact same situation and I did something like this,
const message = 'retest2';
let password = 'secret';
if (password.length < 32) {
password = password.padEnd(32, '\0');
}
const iv = CryptoJS.enc.Utf8.parse(password.substring(0, 16));
password = CryptoJS.enc.Utf8.parse(password);
const encrypted = CryptoJS.AES.encrypt((message), (password), {
iv: iv
});
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
These should be comments but space is limited....
You shouldn't be using the key or data derived from it as your initialization vector.
I know it's right, I tried it using C# as well
You should have shown us the code.
Your openssl call invokes the key derivation function to create a (better?) encryption key from the data you pass in the third parameter. OTOH, in crypto.js there is no implicit key derivation.
Related
I am trying to implement AES 256 bit encryption with string on php and JavaScript. For jasvascript I a musing CryptoJS and php I use openssl_decrypt/enecrypt.
Below is the code in JS for encryption and decryption.
JavaScript
function aes_encrypt(str_to_encrypt){
if(str_to_encrypt==null)
return "";
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv = CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");
var encrypted = CryptoJS.AES.encrypt(str_to_encrypt,key, {'mode': CryptoJS.mode.CBC, iv: iv});
var encryptedString = encrypted.toString();
return encryptedString;
}
function aes_decrypt(str_to_decrypt){
if(str_to_decrypt==null)
return "";
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv = CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");
var decrypted = CryptoJS.AES.decrypt(str_to_decrypt,key, {'mode': CryptoJS.mode.CBC, iv: iv });
var decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedString;
}
And in php the code is
PHP
class Crypto_AES256
{
public $key = "0123456789abcdef0123456789abcdef";
public $iv = "abcdef9876543210abcdef9876543210";
public $encrypt_method = 'AES-256-CBC';
function __construct ()
{
$this->key = hex2bin($this->key);
$this->iv = hex2bin($this->iv);
}
public function encrypt ( $string )
{
if ( $encrypted = base64_encode( openssl_encrypt ( $string, $this->encrypt_method, $this->key, 0, $this->iv ) ) )
{
return $encrypted;
}
else
{
return false;
}
}
public function decrypt ($string)
{
if ( $decrypted = openssl_decrypt ( base64_decode ( $string ), $this->encrypt_method, $this->key, 0, $this->iv ) )
{
return $decrypted;
}
else
{
return false;
}
}
}
But the result of encryption at JavaScript side is not same as php, I need to produce same encrypted and encrypted result at both JavaScript and php. What could be the problem.
Both codes differ in two ways:
The PHP code applies AES-256, but since only a 16 bytes key is used (because of the hex decoding), PHP automatically pads it with 0 values to a length of 32 bytes. In the CryptoJS code, the key length determines the mode, thus AES-128 is applied. So that both codes produce the same result, the key must be extended in the CryptoJS code analogously to the PHP code, or AES-128 must be used in the PHP code.
In the PHP code, openssl_encrypt() returns the ciphertext Base64 encoded by default, so the ciphertext is currently Base64 encoded twice. Therefore remove the explicit base64_encode() or use OPENSSL_RAW_DATA as the 4th parameter so that the raw data is returned. Similarly for openssl_decrypt().
When these issues are fixed, both codes provide the same ciphertext on my machine. Note that a static IV is insecure (see also the comment), but you probably only do this for testing purposes.
Example: The following code uses your unmodified CryptoJS code, i.e. AES-128:
function aes_encrypt(str_to_encrypt){
if(str_to_encrypt==null)
return "";
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv = CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");
var encrypted = CryptoJS.AES.encrypt(str_to_encrypt,key, {'mode': CryptoJS.mode.CBC, iv: iv});
var encryptedString = encrypted.toString();
return encryptedString;
}
function aes_decrypt(str_to_decrypt){
if(str_to_decrypt==null)
return "";
var key = CryptoJS.enc.Hex.parse("0123456789abcdef0123456789abcdef");
var iv = CryptoJS.enc.Hex.parse("abcdef9876543210abcdef9876543210");
var decrypted = CryptoJS.AES.decrypt(str_to_decrypt,key, {'mode': CryptoJS.mode.CBC, iv: iv });
var decryptedString = decrypted.toString(CryptoJS.enc.Utf8);
return decryptedString;
}
var ciphertext = aes_encrypt('The quick brown fox jumps over the lazy dog');
var decrypted = aes_decrypt(ciphertext);
console.log(ciphertext.replace(/(.{56})/g,'$1\n'));
console.log(decrypted);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
The PHP code returns the same ciphertext if AES-128-CBC is applied and if the OPENSSL_RAW_DATA flag is set as the 4th parameter in openssl_encrypt() and openssl_decrypt().
My task is to follow the given and working PHP encryption into node.js. using any node module packages. I dont need to do the decryption because its already existing in their API and i just need to pass the encypted value to their API for decryption which is in PHP. I tried using node-crypto and seperate md5 module. here is the pseudo code:
Data Encryption Algorithm
Create a 16-byte random salt and hash. The salt is newly created every time you call
the API.
Hash the encryption key provided.
Encrypt using 'AES-128-CBC' and use the hashed salt value as vector and hashed
encryption key.
Prefix salt and append the encrypted data.
Do Base64 encoding.
JSON encode and post request
I think I'm almost done just few steps to get a successful response here is my current node.js code
Node:
const reqBody = {
"username": "jCpVyf3VEt",
"password": "eGD6TWKmnn",
"account_no": "0030300155398",
"tran_date": "08/06/2019 10:30:45",
"reference_no": "12328ALHYGZC20",
"area": "JENRA DAU"
};
const Serialize = require('php-serialize')
const md5 = require('md5');
//encrypt
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
key = 'IfZDGbVDHTxlJIkK',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(data, key) {
let salt = crypto.randomBytes(16);
let hKey = md5(key);
let iv = md5(salt);
let serialized = Serialize.serialize(data);
let cipher = crypto.createCipheriv(algorithm, Buffer.from(hKey, 'hex'), Buffer.from(iv, 'hex'));
let crypted = cipher.update(serialized, inputEncoding, outputEncoding);
crypted += cipher.final(outputEncoding);
let encrypted = salt.toString('base64') + crypted.toString();
return encrypted;
}
encrypt(JSON.stringify(reqBody), key);
here is the working php code:
$data = json_encode([
'username' => "jCpVyf3VEt",
'password' => "eGD6TWKmnn",
'account_no' => "0030300155398",
'tran_date' => "08/06/2019 10:30:45",
'reference_no' => "12328ALHYGZC20",
'area' => "JENRA DAU"]);
function encrypt( $data) {
$key = md5("IfZDGbVDHTxlJIkK", true);
$cipher = "aes-128-cbc";
$salt = openssl_random_pseudo_bytes(16);
$iv = md5( $salt, true);
$encrypted_bin = $salt . openssl_encrypt( serialize( $data ), $cipher, $key, true, $iv);
$encrypted_str = base64_encode( $encrypted_bin);
return $encrypted_str;
}
echo encrypt($data);
for testing purpose here is the PHP code from their API for decryption:
$data = 'LI5BJJw1PEhWellnjKEt3g9oaHs8uDDknBT2qDNI7Rfs644+IjobOaFxlrIrOvDm7dkASRsOTu4Yuxzi4I5q29QoE5huH6y4/XZXsResZjLPidv1ToTnhB2UKXH5rX/g/8Od7ljO6VLVAS7zx+94xeOgtpP/idkkpDi1fRNGvnOkl1c6fcyVhwl2Pv+ijKSK9+ou+54dfQrCng2uBzKC6RrHY3lvP7ktsSvtnkXFqksrpjfJ2gnMH6sMIMzru1+D';
function decrypt($encrypted) {
$cipher = "aes-128-cbc";
$key = md5("IfZDGbVDHTxlJIkK", true);
$data = base64_decode($encrypted);
$salt = substr($data, 0, 16);
$iv = md5($salt, true);
$decrypted_bin = openssl_decrypt(substr($data, 16, strlen($data)), $cipher, $key, true, $iv);
if($decrypted_bin === false) {
return json_encode([ -102 => "Authentication Failed"]);
}
return unserialize( $decrypted_bin);
}
echo decrypt($data);
Running the PHP encryption code result a success response from the PHP decryption. But when I run my Node.js encryption I'm able to get an encrypted data but when I test the encrypted data from my Node.js and send the encrypted value into the PHP decryption code the result is authentication error. seems I'm not able to translate the PHP encryption algo into Node
.js
This is a very interesting one.. I think the main issue is the method of concatenating our salt and encrypted data in Node.js.
I found the following code worked nicely, it's giving us the same result as the PHP code.
Note that I'm using a fixed salt here (decoding from a fixed base64 string), since this gives us a deterministic output. You should probably switch to using crypto.randomBytes for this in production.
Also, PHP encodes "/" as "\/" in json output, see json_encode. This can be configured of course in PHP, (using the flag JSON_UNESCAPED_SLASHES in json_encode), but I suspect it can't be changed in your case. This is why I am replacing "/" with "\/" in the json plaintext.
const reqBody = {
"username": "jCpVyf3VEt",
"password": "eGD6TWKmnn",
"account_no": "0030300155398",
"tran_date": "08/06/2019 10:30:45",
"reference_no": "12328ALHYGZC20",
"area": "JENRA DAU"
};
const Serialize = require('php-serialize')
const md5 = require('md5');
//encrypt
const crypto = require('crypto'),
algorithm = 'aes-128-cbc',
key = 'IfZDGbVDHTxlJIkK',
inputEncoding = 'utf8',
outputEncoding = 'base64';
function encrypt(input, key, salt) {
let serialized = Serialize.serialize(input);
let iv = md5(salt);
let hKey = md5(key);
let cipher = crypto.createCipheriv(algorithm, Buffer.from(hKey, 'hex'), Buffer.from(iv, 'hex'));
let crypted = cipher.update(serialized, inputEncoding);
let encrypted = Buffer.concat([salt, crypted, cipher.final()]);
return encrypted.toString(outputEncoding);
}
// We must escape forward slashes here, since this is how PHP will behave.
let data = JSON.stringify(reqBody).replace(/\//ig, "\\/");
// We can use any random salt here, e.g. crypto.randomBytes For this example we'll use the same as the existing encrypted data.
let salt = Buffer.from('LI5BJJw1PEhWellnjKEt3g==', 'base64');
console.log("Encrypted data: ", encrypt(data, key, salt));
I have trouble decrypting an message encrypted in php with the openssl_encrypt method. I am using the new WebCrypto API (so I use crypto.subtle).
Encrypting in php:
$ALGO = "aes-256-ctr";
$key = "ae6865183f6f50deb68c3e8eafbede0b33f9e02961770ea5064f209f3bf156b4";
function encrypt ($data, $key) {
global $ALGO;
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($ALGO), $strong);
if (!$strong) {
exit("can't generate strong IV");
}
return bin2hex($iv).openssl_encrypt($data, $ALGO, $key, 0, $iv);
}
$enc = encrypt("Lorem ipsum dolor", $key);
exit($enc);
example output:
8d8c3a57d2dbb3287aca61be0bce59fbeAQ4ILKouAQ5eizPtlUTeHU=
(I can decrypt that in php and get the cleartext back)
In JS I decrypt like this:
function Ui8FromStr (StrStart) {
const Ui8Result = new Uint8Array(StrStart.length);
for (let i = 0; i < StrStart.length; i++) {
Ui8Result[i] = StrStart.charCodeAt(i);
}
return Ui8Result;
}
function StrFromUi8 (Ui8Start) {
let StrResult = "";
Ui8Start.forEach((charcode) => {
StrResult += String.fromCharCode(charcode);
});
return StrResult;
}
function Ui8FromHex (hex) {
for (var bytes = new Uint8Array(Math.ceil(hex.length / 2)), c = 0; c < hex.length; c += 2)
bytes[c/2] = parseInt(hex.substr(c, 2), 16);
return bytes;
}
const ALGO = 'AES-CTR'
function decrypt (CompCipher, HexKey) {
return new Promise (function (resolve, reject) {
// remove IV from cipher
let HexIv = CompCipher.substr(0, 32);
let B64cipher = CompCipher.substr(32);
let Ui8Cipher = Ui8FromStr(atob(B64cipher));
let Ui8Iv = Ui8FromHex (HexIv);
let Ui8Key = Ui8FromHex (HexKey);
crypto.subtle.importKey("raw", Ui8Key, {name: ALGO}, false, ["encrypt", "decrypt"]). then (function (cryptokey){
return crypto.subtle.decrypt({ name: ALGO, counter: Ui8Iv, length: 128}, cryptokey, Ui8Cipher).then(function(result){
let Ui8Result = new Uint8Array(result);
let StrResult = StrFromUi8(Ui8Result);
resolve(StrResult);
}).catch (function (err){
reject(err)
});
})
})
}
when I now run decrypt("8d8c3a57d2dbb3287aca61be0bce59fbeAQ4ILKouAQ5eizPtlUTeHU=", "ae6865183f6f50deb68c3e8eafbede0b33f9e02961770ea5064f209f3bf156b4").then(console.log) I get gibberish: SÌõÅ°blfçSÑ-
The problem I have is, that I am not sure what is meant with counter. I tried the IV but failed.
This Github tutorial suggests*1, that it is the IV - or at least part of it, as I've seen people talk about that the counter is part of the IV (something like 4 bytes, that means that the IV is made from 12 bytes IV and 4 bytes Counter)
If that is indeed true, my question then becomes: Where do I give the script the other 12 bytes of IV when counter is only 4 bytes of it.
Can anyone maybe give me a working example of encryption in php
*1 It says that the same counter has to be used for en- and decryption. This leads me to believe, that it is at least something similar to the IV
You are handling the key incorrectly in PHP.
In the PHP code you are passing the hex encoded key directly to the openssl_encrypt function, without decoding it. This means the key you are trying to use is twice as long as expected (i.e. 64 bytes). OpenSSL doesn’t check the key length, however—it just truncates it, taking the first 32 bytes and using them as the encryption key.
The Javascript code handles the key correctly, hex decoding it before passing the decoded array to the decryption function.
The overall result is you are using a different key in each case, and so the decryption doesn’t work.
You need to add a call to hex2bin on the key in your PHP code, to convert it from the hex encoding to the actual 32 raw bytes.
For a given project, I'm looking to encrypt a piece of data with AES 256, and then RSA encrypt the key. I've been using Forge and the Encryptor gem in ruby, and i can't seem get matching encryption values:
var key = 'strengthstrengthstrengthstrength';
var iv = 'cakecakecakecakecakecakecakecake';
var cipher = forge.aes.createEncryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(forge.util.createBuffer("some string"));
cipher.finish();
var encrypted = cipher.output;
console.log(btoa(encrypted.data)); // outputs: CjLmWObDO2Dlwa5tJnRBRw==
Then in IRB:
Encryptor.encrypt 'some string', :key => 'strengthstrengthstrengthstrength', :key => 'cakecakecakecakecakecakecakecake'
Base64.encode64 _
# outputs: C9Gtk9YfciVMJEsbhZrQTw==\n
Over using string values for Key & IV, tried:
var key = forge.random.getBytesSync(32);
var iv = forge.random.getBytesSync(32);
Then doing a btoa() call on each of them. Using the Base64.decode64 on the ruby side, before passing them to Encryptor.decrypt, but still no luck.
Any idea where i've gone wrong?
I managed to get it to work. Since i'm just using one key to encrypt one value, i just used the key as the IV & Salt as well. This is not recommended if you are using the key to encrypt multiple values. You will then need to generate proper salt & iv values.
Also the gen key value is a pretty poor way, as Math.random is not secure. Just ran out of time to do this properly, but works okay for this case.
var Encryption = (function () {
var api = {
getKey: function () {
var possible = "ABCDEFabcdef0123456789";
var key = '';
for (var i = 0; i < 32; i++) {
key += possible.charAt(Math.floor(Math.random() * possible.length));
}
return key;
},
encryptPII: function (rawKey, value) {
var salt = rawKey;
var iv = rawKey;
var key = forge.pkcs5.pbkdf2(rawKey, salt, 2000, 32);
var cipher = forge.aes.createEncryptionCipher(key, 'CBC');
cipher.start(iv);
cipher.update(forge.util.createBuffer(value));
cipher.finish();
return btoa(cipher.output.data);
}
};
return api;
})();
The rawKey is the value returned from getKey(). The value property is the the string to be encrypted. I use the rawkey for iv & salt values, generate a key in the same way that the Encryptor gem in ruby does. Use forge to then encrypt the string value.
If i take the base64, decode it in ruby, and pass the same rawKey value to the encryptor gem for key, salt and iv, it works.
NOTE: Yes, I understand there is a lot of code in this message, but you do encourage us to show prior research and how we've been trying.
Let me preface this by saying, I am not interested in the security of this function. All I want is to encrypt and decrypt arbitrarily long messages using RSA. Usually to do this, the message is encrypted using a block cipher (such as AES) and encrypting the key with the RSA cipher. However, I am just trying to find the easiest way to encrypt/decrypt long messages, irregardless of security. Hence why I am using RC4 in place of the block cipher.
Now, I can encrypt properly using the following code:
function encryptLong(signedCert, msg) {
var key256Bits = CryptoJS.SHA256("password");
var ciphertext = CryptoJS.RC4.encrypt(msg, key256Bits);
key = new RSAKey();
var m = CryptoJS.SHA256("password").toString(CryptoJS.enc.Hex);
m = new BigInteger(m, 16);
key.setPublic(signedCert.msg.subject.pk.n, signedCert.msg.subject.pk.e);
var ctxt = key.doPublic(m).toString(16);
var cipherstring = ciphertext + ":" + ctxt;
var obj = { "type": "CTXT-LONG", "encrypted": cipherstring };
return JSON.stringify(obj);
}
The message and the key are encrypted properly. I tested them individually using these functions.
function encryptRSA(signedCert, msg) {
//create a new RSA key object
var key = new RSAKey();
//convert ASCII message to hex
var m = asciiToHex(msg);
// create new BigInterger from m
m = new BigInteger(m, 16);
// set the values for the public key
key.setPublic(signedCert.msg.subject.pk.n, signedCert.msg.subject.pk.e);
// compute the RSA public key operation, and convert to a hex value
var ctxt = key.doPublic(m).toString(16);
//enter ctxt into the JSON obj
var obj = { "type": "CTXT-SHORT", "c": ctxt };
return JSON.stringify(obj);
}
And...
function encryptRSA(password, message) {
var key256Bits = CryptoJS.SHA256(password);
var ciphertext = CryptoJS.RC4.encrypt(CryptoJS.enc.Utf8.parse(message), key256Bits);
return ciphertext;
}
Now, here is our decryption code:
function decryptLong(sk, ctxt) {
key = new RSAKey();
encryptedStuff = JSON.stringify(ctxt.encrypted);
log(encryptedStuff);
splitEncryptedstuff = encryptedStuff.split(":");
rsaencryption = splitEncryptedstuff[1];
log(rsaencryption);
rc4encryption = splitEncryptedstuff[0];
log(rc4encryption);
c = new BigInteger(rsaencryption, 16);
key.setPrivate(sk.n, sk.e, sk.d);
var key256Bits = key.doPrivate(c).toString(16);
log(key256Bits);
// RC4 decryption
var message = CryptoJS.RC4.decrypt(rc4encryption, key224Bits);
// var ptxt = CryptoJS.enc.Utf8.stringify(message);
// log(ptxt);
return CryptoJS.enc.Utf8.stringify(message);
}
This code doesn't decrypt properly, but I know parts of it work. For example, where I have
log(key356Bits);
it returns the key exactly. So I know that at least the RSA decryption works. What I don't understand is, I followed the decryption function that I have exactly. Which is as follows.
function decryptRC4(password, ciphertext) {
var key256Bits = CryptoJS.SHA256(password);
var message = CryptoJS.RC4.decrypt(ciphertext, key256Bits);
return CryptoJS.enc.Utf8.stringify(message);
}
Well not exactly, I don't have to take the Hash of the password to get the key, as I already have the key. But, I still don't understand what is not working. When we decrypt our ciphertext using this individual function, the plaintext is correct.
Any assistance in this matter would be greatly appreciated.
Knowing my luck, it's probably just something annoying like it's in the wrong encoding type thing.