CryptoJS AES pattern always ends with = - javascript

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.

Related

Decryption of TripesDES algorithm in CryptoJS returns nothing

I tried encrypting a data using TripleDES in CryptoJS library. I created an encryption example using this tool https://www.devglan.com/online-tools/triple-des-encrypt-decrypt and generated a string encryption data of NbU4PoYHR9IJtSLmHRubpg==
Now I want to decrypt this NbU4PoYHR9IJtSLmHRubpg== generated from the site using javascript code with CryptoJS library.
I have generated also a SharedKey with this string 36fd14ddcd755bb37879cbe99ca26c92
Here is my code:
export const decryptData = (params) => {
const {ClientSecret, ApiKey} = params;
const SharedKey = `${ClientSecret}:${ApiKey}`;
const data = 'NbU4PoYHR9IJtSLmHRubpg==';
let CryptoSharedKey = CryptoJS.MD5(SharedKey).toString();
const ct = CryptoJS.enc.Base64.parse(data);
console.log('decrypt',CryptoJS.TripleDES.decrypt(ct, CryptoSharedKey).toString(CryptoJS.enc.Utf8))
}
now the problem is when i console log the result, it gives me nothing but an empty string.
The web tool simply UTF-8 encodes the key, so in the CryptoJS code the key derivation via MD5 must be removed.
Also, the web tool automatically truncates too long keys to 24 bytes, the key length used by TripleDES in the 3TDEA variant. Since the key you apply is 32 bytes in size, it is truncated accordingly. CryptoJS also shortens keys that are too long implicitly (though it is more transparent to shorten them explicitly).
Furthermore, in the CryptoJS code, the key material must be passed as WordArray to be interpreted as key (if passed as string, it will be interpreted as password and a key derivation function will be applied). For conversion to a WordArray the key has to be parsed with the Utf8 encoder.
In addition, the ciphertext must be passed as CipherParams object or Base64 encoded (the latter is implicitly converted to a CipherParams object).
Moreover, since the ECB mode was used for encryption in the web tool, this mode must also be applied in the CryptoJS code. For this, ECB must be explicitly set, since CBC is the CryptoJS default.
Hence, the code is to be changed as follows:
var key = CryptoJS.enc.Utf8.parse('36fd14ddcd755bb37879cbe99ca26c92'.substr(0, 24)); // Explicitly shorten the key to 24 bytes; parse the key into a WordArray
var data = 'NbU4PoYHR9IJtSLmHRubpg==';
var decrypted = CryptoJS.TripleDES.decrypt(data, key, {mode: CryptoJS.mode.ECB}); // Pass data Base64 encoded; apply ECB mode (default is CBC)
console.log('decrypt: ', decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Note that TripleDES is outdated and that ECB is insecure.

Crypto-js local side to php server side

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

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.

How to decrypt AES with SJCL

I want to decrypt AES by given cipher and key with the Stanford Javascript Crypto Library (SJCL), but i can't pass the key:
var key = 'key';
var cipher = 'abjslö';
var aes = new sjcl.cipher.aes(key);
var plaintext = aes.decrypt(cipher);
alert(plaintext);
This dosen't work. Referred to the documentation, the key has to be "an array of 4, 6 or 8 words".
How could this be done?
The key has to be an AES key, which is 128, 192 or 256 bits. The SJCL library however operates on 32 bit machine "words". Check out the source of the open source library or one of the tests to find out what to pass. Note that a password is not a key, you need a password based key derivation function such as PBKDF2 to convert a password into a key.
Encryption
Create an array of random words which will
serve as our IV(initialisation vector).
Then you need to create a bit array using a random key(size depends upon level
of encryption and the type)
Then you create a cypher using the key array.
And finally encode your data using the cypher and the IV. (you can
also add meta data to check the authenticity)
Now just concat the IV and the encrypted bit array and finally
convert it to a base64 string and pass.
Please note that you cannot decrypt a AES-GCM without IV.
Decryption
While decrypting slice the IV from the encryted string.
Now using the key create a cypher and use that to decrypt the string.
You can find the complete code here.

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