openssl_encrypt cipher decypted using Crypto JS - javascript

I have a custom payment method on WordPress that processes the payment through a NodeJS API. For obvious reasons, I should encrypt some data, like credit card numbers and cvv2s.
This is how I encrypt my fields in PHP:
$cvv2 = openssl_encrypt(
$_POST['cvv2'],
'AES-256-CBC',
"2c2702d12c747661358dfe9b77425628", // Testing key, thats why its set like this
0
);
This is the output ciphered text: CRqJRoqHriB7X/u1bwSE1Q==
This is how I try to decrypt in JS
const key = "2cea02d12c747661358dfe9b77425628";
const dec = CryptoJS.AES.decrypt(cvv2,key);
console.log(dec.toString(CryptoJS.enc.Utf8))
But the output is always an empty string.
This is me decrypting it using an online tool
I am really frustrated as I cannot seem to make this work, no matter how much I read online.
What could be the problem?

In the CryptoJS code, the key must be passed as WordArray (using a suitable encoder) and the zero IV (16 0x00 values) used implicitly in the PHP code must be specified explicitly.
The ciphertext must be passed as CipherParams object or Base64 encoded (the latter is implicitly converted to a CipherParams object):
const key = CryptoJS.enc.Utf8.parse("2cea02d12c747661358dfe9b77425628");
const iv = CryptoJS.lib.WordArray.create([], 16);
const ciphertext = "CRqJRoqHriB7X/u1bwSE1Q==";
const dec = CryptoJS.AES.decrypt(ciphertext, key, {iv: iv});
console.log(dec.toString(CryptoJS.enc.Base64));
console.log(dec.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that a static IV is insecure.

Related

Match PHPs openssl_encrypt with blank IV in JavaScript

How can I match the output of openssl_encrypt in JavaScript, when no IV is declared in PHP and a non-compliant key length is used?
PHP
php -r '$value = openssl_encrypt("test", "AES-128-CBC", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa"); echo $value;'
/u+5lB/VwbMX9U1YY4cnCQ==
JavaScript
iv = CryptoJS.enc.Utf8.parse("");
var encrypted = CryptoJS.AES.encrypt("test", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", {iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
console.log(encrypted.toString());
U2FsdGVkX19Cn1f6x8C/rJdfwzsZk5m5WWCUrR4z3U4=
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
l1/DOxmTmblZYJStHjPdTg==
I can only modify the Javascript code and I need a way to encrypt a string so that it can be decrypted by PHP's openssl_decrypt. It works fine obviously if the string it's encrypted from PHP.
I understand that by not declaring a IV renders the code less secure, but in my particular case it's not a big issue.
I've read in other topics that PHP defaults the padding to Pkcs7, which is why I added it to the JS method.
My current theory is that either the default IV of PHP is the issue or the output of the JS function needs to be further processed. As you can see I tried the Base64.stringify method, but the results are still different.
The sample key I'm using here is of the same length of the actual key.
I'm using https://github.com/sytelus/CryptoJS/blob/master/rollups/aes.js (whatever I use in JS, it needs to be easily distributed like a standalone file, with a relatively small footprint)
There are some issues with your code:
If you want to use an existing key, then you need to provide a WordArray object to CryptoJS.<cipher>.encrypt. That means it has to be parsed in some way. If you simply provide a string as a "key", then CryptoJS will assume that it is a password, generate a random salt and use EVP_BytesToKey to derive the key and IV from password and salt.
Your "key" is 29 characters long. AES only supports three key sizes: 16, 24 and 32 bytes. Since you've used AES-128 in PHP, you need to provide a 16 byte key in CryptoJS so that AES-128 is automatically selected. Remember: a key is supposed to be randomly chosen and be indistinguishable from random noise so that it has some kind of security. If you must print the key in some way, use Hex or Base64 encoding.
Full example:
var iv = CryptoJS.enc.Utf8.parse("");
var key = CryptoJS.enc.Utf8.parse("aaaaaaaaaaaaaaaa");
var encrypted = CryptoJS.AES.encrypt("test", key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(CryptoJS.enc.Base64.stringify(encrypted.ciphertext));
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>

Encode data using codeiginter encryption library and dycrypting using js-mcrypt

I have been trying to figure out how to decrypt strings using javascript that were encoded using codeiginter's encryption library.
So far I found this as a guide php to js-mcrypt
But I could not figure out how to supply the iv variable.
Because codeiginter randomly generates it upon encryption.
My sample code is
//PHP Side
$this->encrypt->encode('apple','1234567');
//The result is : 2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==
//JS Side
var encrypted = '2lek4Q1mz4CJtTy2ot/uJWlfeGKuGiUKuKkR5Utkwc1nSWjf3JqG8gOhNmS13mt25QVbgP/2QOuffpn7rhIOmQ==';
var key = 'fcea920f7412b5da7be0cf42b8c93759';//md5 version of "1234567"
var iv = 'some 32 length string';// I don't know how to get the IV because it constantly change in PHP
var decrypted = mcrypt.Decrypt(atob(encrypted), iv, key, 'rijndael-256', 'cbc');
console.log(decrypted);
A random iv is generally pre-pended to the encrypted data.
Simple encryption of 5 bytes ('apple') with padding using 'rijndael-256' would produce 32 bytes of output. In this case the encrypted output is 88-bytes so the iv is probably there along with something else.
Also mcrypt is somewhat brain-dead in that it does not support the standard PKCS#7 (AKA PKCS#5) padding so that is also an interoperability problem.
Note: 'rijndael-256' means a block size of 256-bits, not a key size and AES is essentially Rijndael with a block size of 128-bits, it is best to use a block size of 128-bits and be compatible with AES.

Encrypting with CryptoJS and decrypt with php: What is the use of the IV?

I am looking for a way, to encrypt a password in CryptoJS and then decrypt it in php. I have looked at other posts concerning the same subject, but I need someone to explain all that IV and key stuff.
My CryptoJS encryption code:
password = document.getElementById("usrp").value;
password = CryptoJS.AES.encrypt(password, <?php echo '"'.$_SESSION['adk'].'"'; ?>);
IV
You're using the CBC mode of operation which requires an IV. If you use a static IV for all your ciphertexts then you miss out on an important property of encryption which is semantic security. If you use the same IV, attackers may observe your ciphertext and determine whether you sent the same plaintext with the same key, because the ciphertext will be the same.
To prevent that, you can generate a random IV for each encryption you do. The IV doesn't have to be secret, but it has to be unpredictable. Since it doesn't have to be secret, you can simply prepend it to the ciphertext and slice it off before decryption or send it otherwise in a structured fashion. You need to use IV during decryption. Otherwise, the first block will be different from the original plaintext.
Keep in mind that CryptoJS' WordArray.random() uses Math.random() internally which is not cryptographically secure. It would be better to use a better randomness source. You can use this drop in replacement from my project of that function for semi-modern browsers which uses the WebCrypto API:
(function(C){
var WordArray = C.lib.WordArray;
var crypto = window.crypto;
var TypedArray = Int32Array;
if (TypedArray && crypto && crypto.getRandomValues) {
WordArray.random = function(nBytes){
var array = new TypedArray(Math.ceil(nBytes / 4));
crypto.getRandomValues(array);
return new WordArray.init(
[].map.call(array, function(word){
return word
}),
nBytes
);
};
} else {
console.log("No cryptographically secure randomness source available");
}
})(CryptoJS);
and use it like this:
var iv = CryptoJS.lib.WordArray.random(128/8);
Key
The key is trickier, because it needs to be kept confidential. The basic way is:
Let the user type in the password that is also present on the server and derive the key from the password by for example using PBKDF2 which CryptoJS also provides. Perfectly secure as long as you use TLS and the developers don't change the code.

Load RSA public key created in JSBN, then encrypt a message

I am trying to create a RSA key pair in JavaScript using JSBN and transfer the public key to Crypto++. Then, I am trying to encrypt a message in Crypto++ and send it back to JavaScript and decrypt it.
But I am relatively new at this, so I think I am doing something wrong ( data is not getting decrypted ofc )
any help would be greatly appreciated :D
Here is my cpp code
Integer n(nStr->c_str()),
e("0x10001");
RSA::PublicKey pubKey;
pubKey.Initialize(n, e);
AutoSeededRandomPool rng;
if (!pubKey.Validate(rng, 3))
throw std::exception("Rsa private key validation failed");
////////////////////////////////////////////////
// Encryption
RSAES_PKCS1v15_Encryptor enc(pubKey);
StringSource ss1(data, true,
new PK_EncryptorFilter(rng, enc,
new StringSink(retStr)
));
std::string retData2 = "";
StringSource ss2((const byte*)retStr.data(), retStr.size(), true,
new Base64Encoder(
new StringSink(retData2)
));
retStr = retData2;
And my javascript code
// nStr in CPP is "0x" + localStorage.getItem("rsa_public") from javascript
// data in CPP is "secret"
var rsa = new RSAKey();
var publickey = localStorage.getItem("rsa_public");
var privatekey = localStorage.getItem("rsa_private");
rsa.setPrivate(publickey, "10001", privatekey);
alert(b64tohex(dec) + "\n" + rsa.encrypt("secret")); <-- these don't match at all .. and ofc rsa.decrypt returns null
dec in javascript is retStr from CPP
The PKCS#1 v1.5 Padding that is used by JSBN and your Crypto++ code is a random padding, so if you encrypt data with the same key it will look differently. You have to check whether your implementation works by encrypting on one end and decrypting on the other in both directions.
RSAES_PKCS1v15_Encryptor enc(pubKey);
StringSource ss1(data, true,
new PK_EncryptorFilter(rng, enc,
new StringSink(retStr)
));
...
StringSource ss2((const byte*)retStr.data(), retStr.size(), true,
new Base64Encoder(
new StringSink(retData2)
));
I'm not sure this is correct for interop'ing with Javascript and JSBN. It is correct stand alone, and it is correct for interop'ing with OpenSSL.
Crypto++ uses an early Base64 encoding scheme. It was seen in email and other standards of the time (the time was around the 1990s). The alphabet uses the plus (+) and forward slash (/) characters.
The Javascript and JSON technologies like JSON Web Keys (JWKs) tend to favor the Base64 encoding using the URL or Web Safe alphabet. The alphabet uses the minus (-) and underscore (_) characters.
Both the old and new Base64 encoding schemes are specified in RFC 4648, The Base16, Base32, and Base64 Data Encodings.
You should fetch the patch for the Base64URLEncoder, and apply it in-place over top of the Crypto++ sources. After you patch, you will have the existing Base6Encoder and the new Base64URLEncoder. Finally, recompile and reinstall the library. You have to patch it because its not part of the Crypto++ library as written and offered by Wei Dai.
Then, do the following:
RSAES_PKCS1v15_Encryptor enc(pubKey);
string encoded;
StringSource ss(data, true,
new PK_EncryptorFilter(prng, enc,
new Base64URLEncoder(
new StringSink(encoded)
)));
// Print encoded cipher text
cout << encoded << endl;
return encoded;
As for "these don't match at all..." - I think that's expected. RSA encryption uses randomized padding so that when you encrypt messages m1 and m2, the cipher text is different. Its called "semantic security" and its a strong (stronger?) notion of security. This way, the bad guy can't tell when the same message has been sent twice.

SJCL encryption results wrong file size

I have successfully implemented Rob Napier's AES encryption method for iOS in one of my apps. I would now like to be able to encrypt and decrypt files from that app with my JavaScript implementation. I am using FileReader to get a local from from the user and loading it with
reader.readAsArrayBuffer(file);
When this is done the file gets encrypted using the Stanford JavaScript Crypto Library and finally the encrypted file can be downloaded:
reader.onloadend = function(e) {
var content = new Uint8Array(e.target.result);
var utf8 = "";
for (var i = 0, len = content.length; i < len; i++) {
utf8 += String.fromCharCode(content[i]);
}
var b64 = btoa(utf8);
//we finally encrypt it
var encrypted = sjcl.encrypt(password, b64,{ks:256});
var json = JSON.parse(encrypted);
var ciphertext = json.ct;
a.attr('href', 'data:application/octet-stream,' + ciphertext);
a.attr('download', file.name + '.encrypted');
step(4);
};
reader.readAsArrayBuffer(file);
The problem is that the encrypted file is much larger than the original. This is not the case in my iOS implementation which works just fine. And of course it can't be decrypted without error. In fact the resulting file will have 0 bytes of size.
I am hoping someone can point out the error in my code to me. That would really be great.
#Duncan:
Thank you. I looked into that but I'm not really sure about all the steps I have to take. Especially what they mean in code. Maybe somebody could help me out here. Thanks a lot!
Encryption
Generate a random encryption salt
Generate the encryption key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random encryption salt, and 10,000 iterations.
Generate a random HMAC salt
Generate the HMAC key using PBKDF2 (see your language docs for how to call this). Pass the password as a string, the random HMAC salt, and 10,000 iterations.
Generate a random IV
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
Pass your header and ciphertext to an HMAC function, along with the HMAC key (above), and the PRF "SHA-256" (see your library's docs for what the names of the PRF functions are; this might also be called "SHA-2, 256-bits").
Put these elements together in the format given above.
Encrypt the data using the encryption key (above), the IV (above), AES-256, and the CBC mode. This is the default mode for almost all AES encryption libraries.
This is a false assumption, according to this sjcl uses ccm as a default mode.

Categories