Crypto-js local side to php server side - javascript

I have an app that use crypto-Js AES. The simulate working code is:
var ciphertext = CryptoJS.AES.encrypt('My_message', 'My_secret_key');
console.log(ciphertext.toString());
the answer is:
U2FsdGVkX1/Dd3uAr/mdw5lVoBvq0UX5LHnNoX24JAM=
when I try to reproduce it server side I never get the same answer:
$passphrase='My_secret_key';
$value='My_message';
$salt = openssl_random_pseudo_bytes(8);
$salt ='';
$salted = '';
$dx = '';
while (strlen($salted) < 48) {
$dx = md5($dx.$passphrase.$salt, true);
$salted .= $dx;
}
$key = substr($salted, 0, 32);
$iv = substr($salted, 32,16);
$encrypted_data = openssl_encrypt($value, 'aes-256-cbc', $key, true, $iv);
echo base64_encode($encrypted_data);
server side answer:
3jSTl1yR55lfTbz7f0o3Yw==
I must miss something but can't point out what. Local side can't touched.
All help is welcome

If the second parameter in CryptoJS.AES.encrypt is passed as string, it is interpreted as a passphrase from which the actual key and IV are derived, [1]. This is achieved by using the functionality of the OpenSSL-function EVP_BytesToKey with an iteration count of 1 and the MD5-digest, [2] [3] (note that CryptoJS doesn't consider the switch of the default digest from MD5 to SHA256 from OpenSSL version 1.1.0c on, [4]).
CryptoJS.AES.encrypt returns a CipherParams-object that encapsulates ciphertext, key, IV, and salt, [5]. In addition CipherParams#toString() returns the result in OpenSSL-format as Base64-encoded string. The OpenSSL-format consists of a 16-byte header and the subsequent ciphertext. The header starts with the ASCII-encoded string Salted__ followed by an 8-byte salt. The salt is randomly generated each time and used together with the password to derive the key / IV. This creates a different key / IV each time.
The PHP-code is functionally identical: Key and IV are derived with an analog logic from a passphrase using a freshly generated salt each time (for the proof, see below). However, some minor changes are necessary:
The following line must be removed: $salt ='';
In the current code, only the Base64-encoded ciphertext is displayed. For a Base64-encoded output of the result in OpenSSL-format the code must be instead:
echo base64_encode('Salted__'.$salt.$encrypted_data);
The 4th parameter in openssl_encrypt should be changed from true to OPENSSL_RAW_DATA. Both are functionally identical, but the use of OPENSSL_RAW_DATA is more transparent.
The JavaScript- and PHP-code generate a new salt each time and thus a different key and IV, which changes the ciphertext each time. That's the way it should be. Since the salt is stored together with the ciphertext, it is possible to decrypt the ciphertext at any time using the passphrase.
Proof that both codes use the same logic to derive key and IV: The new salt / ciphertext generated each time prevents a direct comparison of the results of both codes. In order to perform this comparison without much effort, it is best to use the salt generated in the JavaScript-code in the PHP-code as well. The salt in the JavaScript-code can be determined as hexadecimal string with:
console.log(ciphertext.salt.toString(CryptoJS.enc.Hex));
This salt is to be used in the PHP-code instead of the randomly generated salt (of course only for this one comparison):
$salt = hex2bin('<Salt from JavaScript-Code as hexadecimal string>');
A comparison of both outputs now proves that they are equal, showing that both codes are functionally identical.

I've been in your exact situation before. We had extreme difficulty getting the same results on PHP and Java (for Android). Many developers at 2 companies over many days. No luck.
We eventually ended up calling CryptoJS from PHP. If I remember correctly, there is something non-standard in Crypto JS. I could be wrong it was a while back.
Calling CryptoJS with node through PHP can be a more robust solution. Then you can use the same library which ensures compatibility.
$result = shell_exec('node yourCryptoJSprogram.js');
We did reach a limit to how much data can be passed as arguments this way. I would recommend writing to a file with PHP and reading with NodeJS again.
If performance becomes problematic, consider running an Express server and making REST calls from PHP.
If that answer doesn't satisfy you, consider using or copying this simple OpenSSL based PHP library I wrote to figure it out:
https://github.com/io-digital/php-crypto

Related

openssl_decrypt PHP to CryptoJS

In PHP I use openssl_decrypt($encryptData,'AES128',$key, OPENSSL_ZERO_PADDING|OPENSSL_RAW_DATA) and I need the exact same thing with same option on JS so I use CryptoJS with this function cryptoJS.AES.decrypt(encryptData, key, {padding: cryptoJS.pad.ZeroPadding}).toString().
With PHP my decoded message start with C7 with others hexa character. But in JS only decimal values.
Here encryptData and key on PHP and JS have the exact same value. But cryptoJS return something different from PHP.
I don't know what can be the problem. I've tried to change the base of my values with UTF8 or Base64. But nothing works.
How can I convert this function to crypto and have it working ?
Thank you !
PHP example :
$encryptData = hex2bin("D5F630E93F36C21293012D78E5A384F1");
$key = hex2bin("A254FE00A791AA74386E8DEF3712B256");
var_dump(bin2hex(openssl_decrypt($encryptData,'AES128', $key, OPENSSL_ZERO_PADDING|OPENSSL_RAW_DATA)));
//result : c704469332aa61804601008a92dc10e5
JS example : (and of course hex2bin return the same output than in PHP)
let encData = hex2bin("D5F630E93F36C21293012D78E5A384F1");
let key = hex2bin("A254FE00A791AA74386E8DEF3712B256");
let data = bin2hex(cryptoJS.AES.decrypt(encData, key, {padding: cryptoJS.pad.ZeroPadding}).toString());
console.log(data);
//result : 326638346632336661323135353832343236376139343564
Result change every time I reload on JS.
CryptoJS uses the WordArray type and provides encoders for conversion, e.g. the Hex encoder.
In the PHP code, aes128 is applied, which is an alias for aes-128-cbc and requires an IV. If none is specified, PHP implicitly uses a zero vector, which must be explicitly specified in the CryptoJS code. The CBC mode itself does not need to be explicitly specified, since it is the default.
Moreover the PHP code disables padding, which must be explicitly specified in the CryptoJS code, since PKCS7 padding is the default. Note that OPENSSL_ZERO_PADDING does not enable Zero padding, but disables padding, i.e. the CryptoJS counterpart is NoPadding.
Your CryptoJS code must be changed as follows to be functionally equivalent to the PHP code:
let encData = CryptoJS.enc.Hex.parse("D5F630E93F36C21293012D78E5A384F1");
let key = CryptoJS.enc.Hex.parse("A254FE00A791AA74386E8DEF3712B256");
let iv = CryptoJS.enc.Hex.parse("00000000000000000000000000000000");
let data = CryptoJS.AES.decrypt(
{ciphertext: encData},
key,
{iv: iv, padding: CryptoJS.pad.NoPadding}
).toString(CryptoJS.enc.Hex);
console.log(data); // c704469332aa61804601008a92dc10e5
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Please also note that a static IV is insecure. Typically, a random IV is generated for each encryption, which is not secret and passed along with the ciphertext (usually concatenated) to the recipient, who separates both before decryption.

Encrypting in CryptoJS and decrypting in mCrypt not working properly

For the past 2 days I have been struggling with what I first thought would be a breeze to implemenent.
I am in need of a very simple and non-secure way to send a cipher thru AJAX and decrypt it server-side
I am using AES from CryptoJS:
JS encryption
msg = "message";
var key = CryptoJS.enc.Hex.parse('000102030405060708090a0b0c0d0e0f');
var iv = CryptoJS.enc.Hex.parse('101112131415161718191a1b1c1d1e1f');
var encrypted = CryptoJS.AES.encrypt(msg, key, { iv: iv });
var data_base64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
var iv_base64 = encrypted.iv.toString(CryptoJS.enc.Base64);
var key_base64 = encrypted.key.toString(CryptoJS.enc.Base64);
console.log(data_base64,iv_base64,key_base64) //If I use these keys in the PHP decryption it works
return encrypted.toString();
...and PHP decryption using mCrypt
$encrypted = "f82126a59b76d86946a013d9f575d0d4"; //this is what the JS function above returned.
$key = "000102030405060708090a0b0c0d0e0f"; //same key as in JS function
$iv = "101112131415161718191a1b1c1d1e1f"; //same IV as in JS function
$plaintext = rtrim( mcrypt_decrypt( MCRYPT_RIJNDAEL_256, $key, $encrypted, MCRYPT_MODE_CBC, $iv ), "\t\0 " );
echo "Original string : " . $encrypted . "<br />\n";
echo "Decrypted string : " . $plaintext . "<br />\n";
Since I am using the same IV's and key's to encrypt I would expect that this would work decrypt just fine. However I seem to be missing something since I still see gibberish in the plaintext.
EDIT:
Well it seems that the whole purpose of AES is that the keys and
IV's I must use are output by CryptoJS (see the console.log's in
the JS function). If I use those the function seems to run just fine.
But I don't want that actually since those keys are dynamically
generated thus each time I ran the JS function they change. I just
need a shared private-key between client and server that is used to
encrypt/decrypt and is static. As simple as that.
Your key is 128 bit (the length of $key is 32 hexadecimal characters, which means 16 byte or 128 bit). However, in your mcrypt_decrypt() call you're telling PHP to use MCRYPT_RIJNDAEL_256 with a 256-bit key. Try using MCRYPT_RIJNDAEL_128 instead. Normally ciphers should adapt to the length of the passed key, but it could be that PHP is padding the key with null bytes to use 256-bit encryption.
Secondly, in PHP mcrypt_decrypt is set to use the CBC mode (see MCRYPT_MODE_CBC). You don't specify which mode CryptoJS should use. Luckily for you, according to docs for CryptoJS, the CBC mode is the one used by default; however, since you're writing portable code you should consider making that explicit.
edit
If it tells you that the key is too long, it's because you're not packing them. You're giving PHP an hex-encoded string long 32 bytes (256 bit), which is not your key! To get the binary data you need to do:
$key = pack('H*', "000102030405060708090a0b0c0d0e0f");
$iv = pack('H*', "101112131415161718191a1b1c1d1e1f");
the pack('H*', $str); function converts an hex representation to the binary string.
MCRYPT_RIJNDAEL_256 is not the same as AES. The 256 relates to the block size of the cipher, not the key size.
Quick history lesson - AES was a competition that was eventually won by an algorithm called Rijndael. Rinjdael is a cipher defined for several block sizes (128, 160, 192, 224 and 256 bits). However, for AES only the 128-bit block size was selected.
The block size defines the size of the IV, when you use a mode that needs an IV (like CBC-mode). So for AES, you'll always need a 128-bit IV, regardless of key size. The supported key sizes are 128, 192 or 256 bits.
In PHP, one can use AES by using the cipher MCRYPT_RIJNDAEL_128.

Why is AES function returning different value?

Why is AES with same secret phrase and message returns different values each time?
Let's say we have a same salt for each PBKDF2 functions( I know it's bad, it's just for an example). Let's say we are entering same passphrase each time.
var salt = "5J07c/a7+2bf=15$56aQc75Ub55=60&0";
console.log(req.body.password);
console.log(salt);
var PBKDF2hash = crypto.PBKDF2(req.body.password, salt, { keySize: 256/32 });
console.log(PBKDF2hash.toString());
var AEScipher = crypto.AES.encrypt(req.body.password, PBKDF2hash);
console.log(AEScipher.toString());
In this case we receive same PBKDF2hash (as expected), but any time AES provides different chipher.
zz
5J07c/a7+2bf=15$56aQc75Ub55=60&0
3949676666ed318087a52896be98dc80b0cad99f4b662d48565283f71a2ace80
U2FsdGVkX19O1pqgL+V6Chk8NdiJQhf15N1uEfYXgxw=
zz
5J07c/a7+2bf=15$56aQc75Ub55=60&0
3949676666ed318087a52896be98dc80b0cad99f4b662d48565283f71a2ace80
U2FsdGVkX1/C7GAmLJvfFAHyOYj7LKZI5278/ZoeA3M=
These answers says the thing is salt is differrent and cbc mode matters. In my case salt is constant and I've switched to other modes. Output is still different each time.
The initialization vector used in CBC mode is a random block, so each encryption will be different. An IV is sort of like a salt, except when encrypting a message there is no reason to ever specify any specific IV to use (a random IV should always be used). The IV is often put in front of the first block of the encrypted message.

CryptoJS AES pattern always ends with =

I'm using CryptoJS to encrypt some usernames and passwords, it's working well enough I think.
But I have some questions regarding the encrypted data plaintext.
No matter what the key or data is it always starts with "U2FsdGVkX1...".
The encrypted data changes constantly even if the input data remains the same as shown below:
U2FsdGVkX1/BshMm2v/DcA6fkBQGPss6xKa9BTyC8g0=
U2FsdGVkX1/uc5OTSD7CfumdgqK1vN2LU4ISwaQsTQE=
U2FsdGVkX1/8OOLOTZlfunN4snEVUdF2ugiL7SeAluE=
U2FsdGVkX1+c8j3l1NRBJDb1byHwOmmNSmbTci22vsA=
username_encrypted = CryptoJS.AES.encrypt(username, key);
password_encrypted = CryptoJS.AES.encrypt(password, key);
console.log(username_encrypted.toString());
console.log(password_encrypted.toString());
console.log(CryptoJS.AES.decrypt(username_encrypted, key).toString(CryptoJS.enc.Utf8));
console.log(CryptoJS.AES.decrypt(password_encrypted, key).toString(CryptoJS.enc.Utf8));
Is this the way it is supposed to work or am I doing something wrong? Because on some online AES encryption sites I get very different results, encrypted data not changing all the time for one.
That's correct. CryptoJS uses the OpenSSL proprietary format. If it uses a salted password to derive a key, it has a magic value in front. E.g. your first base64 translates into
53616C7465645F5F C1B21326DAFFC3700E9F9014063ECB3AC4A6BD053C82F20D
in hex. Connoisseurs will immediately recognize the first 8 bytes as being ASCII,
Salted__ | C1B21326DAFFC370 0E9F9014063ECB3AC4A6BD053C82F20D
So what you have is first a 8 byte magic, then an 8 byte salt, then the ciphertext.
The actual key and IV are derived from the key in your code (which is actually interpreted as being a passphrase). As the salt is always random, so are the derived key and IV. This is how it should be as otherwise you could distinguish plaintext that start with (or is fully identical) to other plaintext.

Include nonce and block count in PyCrypto AES MODE_CTR

Some background information, you can skip this part for the actual question
this is my third question about this topic here at stackoverflow. To be complete, these are the other questions AES with crypt-js and PyCrypto and Match AES de/encryption in python and javascript. Unfortunately my last attempt got two downvots for the original question. The problem was, even I did not know what my real question was. I just dug around to find the real question I was looking for. With the feedback in the comments, and reading some additional information, I updated my question. I excavate the right question, I think. But my problem didn't get any more views after my updates. So I really hope, that this question is now more clear and understandable - even I know what my Problem is now :D
Thank you all for making stackoverflow to this cool community - I often found here solutions for my problems. Please keep giving feedback to bad questions, so they can be improved and updated, which increases this huge knowledge and solutions database.
And feel free to correct my english grammar and spelling.
The Problem
AES in Javascript
I have an encrypted String which I can decrypt with this this Javascript Implementation of AES 256 CTR Mode
password = "myPassphrase"
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
origtext = Aes.Ctr.decrypt(ciphertext, password, 256);
alert(origtext)
This decrypts my string and the alert box with This is a test Text pops up.
AES with PyCrypto
Now I want to decrypt this string with python and PyCrypto
password = 'myPassphrase'
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
ctr = Counter.new(nbits=128)
encryptor = AES.new(key, AES.MODE_CTR, counter=ctr)
origtext = encryptor.decrypt(base64.b64decode(ciphertext))
print origtext
This code does not run. I get an ValueError: AES key must be either 16, 24, or 32 bytes long. When I recognized, that I have to do more in PyCrypto then just call a decrypt method, I started to investigate end try to figure out what I have to do.
Investigation
The basic things I figured out first, were:
AES 256 Bit (?). But AES standard is 128 bit. Does increasing the passphrase to 32 Byte is enough?
Counter Mode. Easily to set in PyCrypto with AES.MODE_CTR. But I have to specify a counter() Method. So I used the basic binary Counter provided by PyCrypto. Is this compatible with the Javascript Implementation? I can't figure out what they are doing.
The string is base64 encoded. Not a big problem.
Padding in general. Both passphrase and the encrypted string.
For the passphrase they do this:
for (var i=0; i<nBytes; i++) {
pwBytes[i] = isNaN(password.charCodeAt(i)) ? 0 : password.charCodeAt(i);
}
Then I did this in python
l = 32
key = key + (chr(0)*(l-len(key)%l))
But this did not help. I still get a weird string ?
A???B??d9= ,?h????' with the following code
l = 32
key = 'myPassphrase'
key = key + (chr(0)*(l-len(key)%l))
ciphertext = "bQJdJ1F2Y0+uILADqEv+/SCDV1jAb7jwUBWk"
ctr = Counter.new(nbits=128)
encryptor = AES.new(key, AES.MODE_CTR, counter=ctr)
origtext = encryptor.decrypt(base64.b64decode(ciphertext))
print origtext
Then I read more about the Javascript Implementation and it says
[...] In this implementation, the initial block holds the nonce in the first 8 bytes, and the block count in the second 8 bytes. [...]
I think this could be the key to the solution. So I tested what happens when I encrypt an empty string in Javascript:
origtext = ""
var ciphertext =Aes.Ctr.encrypt(origtext, password, 256);
alert(ciphertext)
The alert box shows /gEKb+N3Y08= (12 characters). But why 12? Shouldn't it be 8+8 = 16Bytes? Well anyway, I tried a bruteforce method on the python decryption by testing the decryption with for i in xrange(0,20): and ciphertext[i:] or base64.b64decode(ciphertext)[i:]. I know this is a very embarrassing try, but I got more and more desperate. And it didn't work either.
The future prospects are also to implement the encryption in the same way.
additional information
The encrypted string was not originally encrypted with this Javascript implementation, It's from another source. I just recognized, that the Javascript code does the right thing. So I affirm that this kind of implementation is something like a "standard".
The question
What can I do, that the encryption and decryption from a string with PyCrypto is the same like in the Javascript implementation, so that I can exchange data between Javascript and Python?
I also would switch to another crypto library in python, if you can suggest another one.
Furthermore I'm happy with any kind of tips and feedback.
And I think, all comes down to How can I include the nonce and block count to the encrypted string? and How can I extract this information for decryption?
We are still dealing with a bunch of questions here.
How can I extract the nonce and the counter for decryption?
This is easy. In the Javascript implementation (which does not follow a particular
standard in this respect) the 8-byte nonce is prepended to the encrypted result.
In Python, you extract it with:
import base64
from_js_bin = base64.decode(from_js)
nonce = from_js_bin[:8]
ciphertext = from_js_bin[8:]
Where from_js is a binary string you received.
The counter cannot be extracted because the JS implementation does not transmit it.
However, the initial value is (as typically happens) 0.
How can I use the nonce and counter to decrypt the string in Python?
First, it must be established how nonce and counter are combined to get the counter block.
It seems that the JS implementation follows the NIST 800-38A standard, where the
left half is the nonce, and the right half is the counter. More precisely, the counter
is in big endian format (LSB is the rightmost byte). This is also what Wikipedia shows:
.
Unfortunately, CTR mode is poorly documented in PyCrypto (a well-known problem).
Basically, the counter parameter must be a callable object that returns
the correct 16-byte (for AES) counter block, for each subsequent call.
Crypto.Util.Counter does that, but in an obscure way.
It is there only for performance purposes though. You can easily implement it yourself like this:
from Crypto.Cipher import AES
import struct
class MyCounter:
def __init__(self, nonce):
"""Initialize the counter object.
#nonce An 8 byte binary string.
"""
assert(len(nonce)==8)
self.nonce = nonce
self.cnt = 0
def __call__(self):
"""Return the next 16 byte counter, as binary string."""
righthalf = struct.pack('>Q',self.cnt)
self.cnt += 1
return self.nonce + righthalf
cipher_ctr = AES.new(key, mode=AES.MODE_CTR, counter=MyCounter(nonce))
plaintext = cipher_ctr.decrypt(ciphertext)
How long is the key for AES?
The key length for AES-128 is 16 bytes.
The key length for AES-192 is 24 bytes.
The key length for AES-256 is 32 bytes.
Each algorithm is different, but much of the implementation is shared.
In all cases, the algorithm operate on 16 byte blocks of data.
For simplicity, stick to AES-128 (nBits=128).
Will your code work?
I have the feeling it won't, because the way you compute the AES key seems incorrect.
The JS code encodes the password in UTF-8 and encrypts it with itself.
The result is the actual key material. It is 16 byte long, so for AES-192 and -256, the implementation copies part of it at the back. Additionally, the plaintext is also UTF-8 encoded before encryption.
In general, I suggest you follow this approach:
Make your JS implementation reproducible (right now encryption depends on the current time, which changes quite often ;-) ).
Print the value of the keys and data at each step (or use a debugger).
Try to reproduce the same algorithm in Python, and print the values too.
Investigate where they start to differ.
Once you have duplicated the encryption algorithm in Python, the decryption one should be easy.

Categories

Resources