Can't decrypt using CryptoJS (works in Java, Python) - javascript

I have a file that I encrypt using AES. Historically, I've had a set of tools (Java, Python) each of which is capable to both encrypt and decrypt these files. However, I've been having problems decrypting these files using CryptoJS.
The encrypted file has IV stored in the first 16 bytes, the rest is payload. During encryption key is formed by using hashing the password string via SHA-1 and using first 32 characters from the hex digest. I've gotten to the point where I can confirm that both IV and key used by CryptoJS is byte-wise identical to the ones used by other tools yet AES.decrypt() produces a buffer that I can't convert back to text.
Here's the decryption code. content and iv are binary strings read directly from file. password is a string with textual password. The code fails trying to convert the result to UTF8 (which I assume is due to the fact that decryption did not succeed).
function string2bytes(s) {
var bytes = [];
for (var i = 0; i < s.length; i++) {
bytes.push(s.charCodeAt(i));
}
return bytes;
}
function decryptData(content, ivx, password) {
// build a key out of text password
var key = CryptoJS.SHA1(password).toString(CryptoJS.enc.Hex).substring(0, 32);
console.log("key0: ", key);
key = string2bytes(key)
console.log(key);
// Convert IV from binary string to WordArray
var iv = CryptoJS.enc.Latin1.parse(ivx);
console.log("IV: ", iv.toString(CryptoJS.enc.Hex));
var decrypted = CryptoJS.AES.decrypt(content, key, { iv: iv });
console.log("raw decrypted: ", decrypted);
console.log("decrypted: ", iv.toString(CryptoJS.enc.Latin1));
console.log("decrypted: ", iv.toString(CryptoJS.enc.Utf8));
}
Any help would be appreciated.

Found a solution by sticking to WordArrays (even for the arguments where a binary string is ostensibly OK): The following function does work. The arguments are as follows:
data is a base64 string with encrypted text
password is a regular string with password
iv is a base64 string with an IV.
so
function decryptData(data, password, iv) {
var data = CryptoJS.enc.Base64.parse(data);
var key = CryptoJS.SHA1(password).toString(CryptoJS.enc.Hex).substring(0, 32);
key = CryptoJS.enc.Base64.parse(btoa(key));
var iv = CryptoJS.enc.Base64.parse(iv);
var decrypted = CryptoJS.AES.decrypt({ciphertext: data}, key, {iv: iv});
return decrypted.toString(CryptoJS.enc.Utf8);
}

Related

Decrypt AES in JavaScript

I am encrypting a text with AES256 in swift language and outputting it as hex. I want to decrypt this code I received with JS, but I could not reach the result. I tried the CryptoJS library but still couldn't get the result I wanted. All I want is the js code that will give me the decoded version when I enter the IV, password and ciphertext.
const crypto = require("crypto");
var key = "";
const iv = "";
const token = "";
function decrypt(token, iv, key) {
const decrypter = crypto.createDecipheriv("aes-256-cbc", key, iv);
let decrypted = decrypter.update(token, "hex", "utf8");
decrypted += decrypter.final("utf8");
return decrypted
}
console.log(decrypt(token, iv, key));
With the Node.js code above, I achieve what I want, but I want to do it with normal JS code, not using node. I don't want to mess with the server. I would be very happy if you help.
EDIT:
I am using CryptoSwift library in Swift language.
func encryption(uuid: String, token: String) -> String {
do {
let aes = try AES(key: String(uuid.prefix(32)), iv: String(uuid.prefix(16)))
let ciphertext = try aes.encrypt(Array(token.utf8))
let encrypttext = ciphertext.toHexString()
return encrypttext
}
catch {
return "error"
}
}
I tried to do something with CryptoJS with the codes from the site below, but it didn't work like the codes in Node.js.
https://embed.plnkr.co/0VPU1zmmWC5wmTKPKnhg/
EDIT2:
I've been trying different things but couldn't quite figure it out. I get an error when I add PBKDF2. I don't fully understand the problem.
var password = "6268890F-9B58-484C-8CDC-34F9C6A9";
var iv = "6268890F-9B58-48";
var cipher = "79a247e48ac27ed33ca3f1919067fa64";
/*
var key = CryptoJS.PBKDF2(password, {
keySize: 32
});
*/
var dec= CryptoJS.enc.Hex.parse(cipher);
const decrypted = CryptoJS.AES.decrypt({
ciphertext: dec
},
password, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypted.toString(CryptoJS.enc.Utf8));
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/aes.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/pbkdf2.js"></script>
CryptoJS uses WordArrays, so that key, IV and ciphertext have to be converted accordingly. For this purpose the appropriate encoders have to be applied. Furthermore decrypt() expects the ciphertext as CipherParams object.
This results in the following possible CryptoJS implementation:
var ciphertext = "79a247e48ac27ed33ca3f1919067fa64";
var key = "6268890F-9B58-484C-8CDC-34F9C6A9";
var iv = "6268890F-9B58-48";
var ciphertextWA = CryptoJS.enc.Hex.parse(ciphertext);
var keyWA = CryptoJS.enc.Utf8.parse(key);
var ivWA = CryptoJS.enc.Utf8.parse(iv);
var ciphertextCP = { ciphertext: ciphertextWA };
var decrypted = CryptoJS.AES.decrypt(
ciphertextCP,
keyWA,
{ iv: ivWA }
);
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Apple
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
which is functionally identical to the posted NodeJS code that also successfully decrypts the test data.
Regarding the question asked in the comment about the encodings:
In general, the decryption side must have knowledge of the encodings used for encryption. However, in this case the encodings can be derived from the posted NodeJS code:
For decryption, the input encoding of the ciphertext is specified as 'hex', see decipher.update().
key and iv are defined as strings which are UTF-8 encoded, see crypto.createDecipheriv().
Also, the data used is consistent with these conclusions.
Note that for security reasons a static IV may not be used. Instead, a random IV must be generated for each encryption.
Also, no password may be applied as key, even if it has the right length. If a password is to be used, a key derivation is necessary, e.g. with PBKDF2.
For test purposes, the data is of course enough.

CryptoJS to Pycrypto AES [duplicate]

I have a password which is encrypt from JavaScript via
var password = 'sample'
var passphrase ='sample_passphrase'
CryptoJS.AES.encrypt(password, passphrase)
Then I tried to decrypt the password comes from JavaScript in Python:
from Crypto.Cipher import AES
import base64
PADDING = '\0'
pad_it = lambda s: s+(16 - len(s)%16)*PADDING
key = 'sample_passphrase'
iv='11.0.0.101' #------> here is my question, how can I get this iv to restore password, what should I put here?
key=pad_it(key) #------> should I add padding to keys and iv?
iv=pad_it(iv) ##
source = 'sample'
generator = AES.new(key, AES.MODE_CFB,iv)
crypt = generator.encrypt(pad_it(source))
cryptedStr = base64.b64encode(crypt)
print cryptedStr
generator = AES.new(key, AES.MODE_CBC,iv)
recovery = generator.decrypt(crypt)
print recovery.rstrip(PADDING)
I checked JS from browser console, it shows IV in CryptoJS.AES.encrypt(password, passphrase) is a object with some attributes( like sigBytes:16, words: [-44073646, -1300128421, 1939444916, 881316061]). It seems generated randomly.
From one web page, it tells me that JS has two way to encrypt password
(reference link ):
a. crypto.createCipher(algorithm, password)
b. crypto.createCipheriv(algorithm, key, iv)
What I saw in JavaScript should be option a. However, only option b is equivalent to AES.new() in python.
The questions are:
How can I restore this password in Python without changing JavaScript code?
If I need IV in Python, how can I get it from the password that is used in JavaScript?
You will have to implement OpenSSL's EVP_BytesToKey, because that is what CryptoJS uses to derive the key and IV from the provided password, but pyCrypto only supports the key+IV type encryption. CryptoJS also generates a random salt which also must be send to the server. If the ciphertext object is converted to a string, then it uses automatically an OpenSSL-compatible format which includes the random salt.
var data = "Some semi-long text for testing";
var password = "some password";
var ctObj = CryptoJS.AES.encrypt(data, password);
var ctStr = ctObj.toString();
out.innerHTML = ctStr;
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<div id="out"></div>
Possible output:
U2FsdGVkX1+ATH716DgsfPGjzmvhr+7+pzYfUzR+25u0D7Z5Lw04IJ+LmvPXJMpz
CryptoJS defaults to 256 bit key size for AES, PKCS#7 padding and CBC mode. AES has a 128 bit block size which is also the IV size. This means that we have to request 32+16 = 48 byte from EVP_BytesToKey. I've found a semi-functional implementation here and extended it further.
Here is the full Python (tested with 2.7 and 3.4) code, which is compatible with CryptoJS:
from Cryptodome import Random
from Cryptodome.Cipher import AES
import base64
from hashlib import md5
BLOCK_SIZE = 16
def pad(data):
length = BLOCK_SIZE - (len(data) % BLOCK_SIZE)
return data + (chr(length)*length).encode()
def unpad(data):
return data[:-(data[-1] if type(data[-1]) == int else ord(data[-1]))]
def bytes_to_key(data, salt, output=48):
# extended from https://gist.github.com/gsakkis/4546068
assert len(salt) == 8, len(salt)
data += salt
key = md5(data).digest()
final_key = key
while len(final_key) < output:
key = md5(key + data).digest()
final_key += key
return final_key[:output]
def encrypt(message, passphrase):
salt = Random.new().read(8)
key_iv = bytes_to_key(passphrase, salt, 32+16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
return base64.b64encode(b"Salted__" + salt + aes.encrypt(pad(message)))
def decrypt(encrypted, passphrase):
encrypted = base64.b64decode(encrypted)
assert encrypted[0:8] == b"Salted__"
salt = encrypted[8:16]
key_iv = bytes_to_key(passphrase, salt, 32+16)
key = key_iv[:32]
iv = key_iv[32:]
aes = AES.new(key, AES.MODE_CBC, iv)
return unpad(aes.decrypt(encrypted[16:]))
password = "some password".encode()
ct_b64 = "U2FsdGVkX1+ATH716DgsfPGjzmvhr+7+pzYfUzR+25u0D7Z5Lw04IJ+LmvPXJMpz"
pt = decrypt(ct_b64, password)
print("pt", pt)
print("pt", decrypt(encrypt(pt, password), password))
Similar code can be found in my answers for Java and PHP.
JavaScript AES encryption in the browser without HTTPS is simple obfuscation and does not provide any real security, because the key must be transmitted alongside the ciphertext.
[UPDATE]:
You should use pycryptodome instead of pycrypto because pycrypto(latest pypi version is 2.6.1) no longer maintained and it has vulnerabilities CVE-2013-7459 and CVE-2018-6594 (CVE warning reported by github). I choose pycryptodomex package here(Cryptodome replace Crypto in code) instead of pycryptodome package to avoid conflict name with Crypto from pycrypto package.

Encrypt in python - decrypt in Javascript

I have need to simply encrypt some text in python and being able to decrypt in JavaScrypt.
So far I have in python:
from Crypto import Random
from Crypto.Cipher import AES
import base64
BLOCK_SIZE = 16
key = "1234567890123456" # want to be 16 chars
textToEncrypt = "This is text to encrypt"
def encrypt(message, passphrase):
# passphrase MUST be 16, 24 or 32 bytes long, how can I do that ?
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV)
return base64.b64encode(aes.encrypt(message))
def decrypt(encrypted, passphrase):
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV)
return aes.decrypt(base64.b64decode(encrypted))
print encrypt( textToEncrypt, key )
this is producing text: ZF9as5JII5TlqcB5tAd4sxPuBXd5TrgE
in JavaScript:
<script src="http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/aes.js"></script>
<script>
var decrypted = CryptoJS.AES.decrypt( "ZF9as5JII5TlqcB5tAd4sxPuBXd5TrgE", "1234567890123456");
console.log ( decrypted.toString( CryptoJS.enc.Utf8 ) );
</script>
however it does not produce original string (empty string instead).
What I am doing wrong ?
Is it focusing on AES is a best idea - I will be happy if I have some kind of encryption that will blur data.
There are many problems with your Python code and CryptoJS code:
You use a random IV to encrypt some plaintext in Python. If you want to retrieve that plaintext, you need to use the same IV during decryption. The plaintext cannot be recovered without the IV. Usually the IV is simply prepended to the ciphertext, because it doesn't have to be secret. So you need to read the IV during decryption and not generate a new one.
You use CBC mode in CryptoJS (default) instead of CFB mode. The mode has to be the same. The other tricky part is that CFB mode is parametrized with a segment size. PyCrypto uses by default 8-bit segments (CFB8), but CryptoJS is only implemented for fixed segments of 128-bit (CFB128). Since the PyCrypto version is variable, you need to change that.
The CryptoJS decrypt() function expects as ciphertext either an OpenSSL formatted string or a CipherParams object. Since you don't have an OpenSSL formatted string, you have to convert the ciphertext into an object.
The key for CryptoJS is expected to be a WordArray and not a string.
Use the same padding. PyCrypto doesn't pad the plaintext if CFB8 is used, but padding is needed when CFB128 is used. CryptoJS uses PKCS#7 padding by default, so you only need to implement that padding in python.
Python code (for version 2):
def pad(data):
length = 16 - (len(data) % 16)
return data + chr(length)*length
def unpad(data):
return data[:-ord(data[-1])]
def encrypt(message, passphrase):
IV = Random.new().read(BLOCK_SIZE)
aes = AES.new(passphrase, AES.MODE_CFB, IV, segment_size=128)
return base64.b64encode(IV + aes.encrypt(pad(message)))
def decrypt(encrypted, passphrase):
encrypted = base64.b64decode(encrypted)
IV = encrypted[:BLOCK_SIZE]
aes = AES.new(passphrase, AES.MODE_CFB, IV, segment_size=128)
return unpad(aes.decrypt(encrypted[BLOCK_SIZE:]))
JavaScript code:
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/aes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/components/mode-cfb-min.js"></script>
<script>
var base64ciphertextFromPython = "...";
var ciphertext = CryptoJS.enc.Base64.parse(base64ciphertextFromPython);
// split iv and ciphertext
var iv = ciphertext.clone();
iv.sigBytes = 16;
iv.clamp();
ciphertext.words.splice(0, 4); // delete 4 words = 16 bytes
ciphertext.sigBytes -= 16;
var key = CryptoJS.enc.Utf8.parse("1234567890123456");
// decryption
var decrypted = CryptoJS.AES.decrypt({ciphertext: ciphertext}, key, {
iv: iv,
mode: CryptoJS.mode.CFB
});
console.log ( decrypted.toString(CryptoJS.enc.Utf8));
</script>
Other considerations:
It seems that you want to use a passphrase as a key. Passphrases are usually human readable, but keys are not. You can derive a key from a passphrase with functions such as PBKDF2, bcrypt or scrypt.
The code above is not fully secure, because it lacks authentication. Unauthenticated ciphertexts may lead to viable attacks and unnoticed data manipulation. Usually the an encrypt-then-MAC scheme is employed with a good MAC function such as HMAC-SHA256.
(1 Year later but I hope this works for someone)
First of all, thanks Artjom B. your post helps me a lot. And Like OP, I have the same same problem Python server endonding and Javascript client decoding. This was my solution:
Python 3.x (Server)
I used an excplicit PKCS7 encode for padding, why? because I want to be sure Im using the same padding enconding and decoding, this is the link where I found it http://programmerin.blogspot.com.co/2011/08/python-padding-with-pkcs7.html .
Then, like Artjom B. said, be sure about your segment size, IV size and AES mode (CBC for me),
This is the code:
def encrypt_val(clear_text):
master_key = '1234567890123456'
encoder = PKCS7Encoder()
raw = encoder.encode(clear_text)
iv = Random.new().read( 16 )
cipher = AES.new( master_key, AES.MODE_CBC, iv, segment_size=128 )
return base64.b64encode( iv + cipher.encrypt( raw ) )
Note than your are enconding on base64 the concatenation of IV and encryption data.
Javascript (client)
function decryptMsg (data) {
master_key = '1234567890123456';
// Decode the base64 data so we can separate iv and crypt text.
var rawData = atob(data);
// Split by 16 because my IV size
var iv = rawData.substring(0, 16);
var crypttext = rawData.substring(16);
//Parsers
crypttext = CryptoJS.enc.Latin1.parse(crypttext);
iv = CryptoJS.enc.Latin1.parse(iv);
key = CryptoJS.enc.Utf8.parse(master_key);
// Decrypt
var plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: crypttext},
key,
{iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7}
);
// Can be Utf8 too
output_plaintext = CryptoJS.enc.Latin1.stringify(plaintextArray);
console.log("plain text : " + output_plaintext);
}
One of my main problem was keep in mind all kind of encoding and decoding data, for example, I didn't know that the master_key on client side was to be parse with Utf8.
//First pip install pycryptodome -- (pycrypto is obsolete and gives issues)
// pip install pkcs7
from Crypto import Random
from Crypto.Cipher import AES
import base64
from pkcs7 import PKCS7Encoder
from app_settings.views import retrieve_settings # my custom settings
app_secrets = retrieve_settings(file_name='secrets');
def encrypt_data(text_data):
#limit to 16 bytes because my encryption key was too long
#yours could just be 'abcdefghwhatever'
encryption_key = app_secrets['ENCRYPTION_KEY'][:16];
#convert to bytes. same as bytes(encryption_key, 'utf-8')
encryption_key = str.encode(encryption_key);
#pad
encoder = PKCS7Encoder();
raw = encoder.encode(text_data) # Padding
iv = Random.new().read(AES.block_size ) #AES.block_size defaults to 16
# no need to set segment_size=BLAH
cipher = AES.new( encryption_key, AES.MODE_CBC, iv )
encrypted_text = base64.b64encode( iv + cipher.encrypt( str.encode(raw) ) )
return encrypted_text;

Encrypt text using AES in Javascript then Decrypt in C# WCF Service

I am trying to Encrypt a string using AES 128bit encryption. I have code for both Javascript and C#. The main objective is to encrypt the string using Javascript CryptoJS and then take the resultant cipher text and Decrypt it using C# AES AesCryptoServiceProvider.
Javascript Code:
function EncryptText()
{
var text = document.getElementById('textbox').value;
var Key = CryptoJS.enc.Hex.parse("PSVJQRk9QTEpNVU1DWUZCRVFGV1VVT0=");
var IV = CryptoJS.enc.Hex.parse("YWlFLVEZZUFNaWl=");
var encryptedText = CryptoJS.AES.encrypt(text, Key, {iv: IV, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7});
//var decrypted = CryptoJS.AES.decrypt(encrypted, "Secret Passphrase");
var encrypted = document.getElementById('encrypted');
encrypted.value = encryptedText;
}
C# Code:
private String AES_decrypt(string encrypted)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
aes.BlockSize = 128;
aes.KeySize = 256;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Pkcs7;
aes.Key = Key;
aes.IV = IV;
ICryptoTransform crypto = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] secret = crypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
crypto.Dispose();
return System.Text.ASCIIEncoding.ASCII.GetString(secret);
}
When using "hello" as the plain text for javascript i get this ciphertext:
uqhe5ya+mISuK4uc1WxxeQ==
When passing that into the C# application, upon running the Decrypt method i recieve:
Padding is invalid and cannot be removed.
I am stumped here and have tried many solutions resulting in the same error.
When encrypting hello through the C# encryption AES method I receive:
Y9nb8DrV73+rmmYRUcJiOg==
I thank you for your help in advance!
javascript code :
function EncryptText()
{
var text = CryptoJS.enc.Utf8.parse(document.getElementById('textbox').value);
var Key = CryptoJS.enc.Utf8.parse("PSVJQRk9QTEpNVU1DWUZCRVFGV1VVT0="); //secret key
var IV = CryptoJS.enc.Utf8.parse("2314345645678765"); //16 digit
var encryptedText = CryptoJS.AES.encrypt(text, Key, {keySize: 128 / 8,iv: IV, mode: CryptoJS.mode.CBC, padding:CryptoJS.pad.Pkcs7});
var encrypted = document.getElementById('encrypted');
encrypted.value = encryptedText;
//Pass encryptedText through service
}
C# code :
private String AES_decrypt(string encrypted,String secretKey,String initVec)
{
byte[] encryptedBytes = Convert.FromBase64String(encrypted);
AesCryptoServiceProvider aes = new AesCryptoServiceProvider();
//aes.BlockSize = 128; Not Required
//aes.KeySize = 256; Not Required
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.Pkcs7;
aes.Key = Encoding.UTF8.GetBytes(secretKey);PSVJQRk9QTEpNVU1DWUZCRVFGV1VVT0=
aes.IV = Encoding.UTF8.GetBytes(initVec); //2314345645678765
ICryptoTransform crypto = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] secret = crypto.TransformFinalBlock(encryptedBytes, 0, encryptedBytes.Length);
crypto.Dispose();
return System.Text.ASCIIEncoding.ASCII.GetString(secret);
}
Used above code working fine !!!
Try using var Key = CryptoJS.enc.Utf8.parse("PSVJQRk9QTEpNVU1DWUZCRVFGV1VVT0="); instead of HEX.
Because actually the string you are putting in your key (and IV) and parsing is not a hex string. hex is 0 to F.
First, your Key variable in JS contains a string with 32 characters (after the odd-looking parse call). Although this might be interpreted as a 128-bit key, there is a certain chance that CryptoJS takes it as a pass phrase instead (and generates a key from it using some algorithm). So your actual key looks quite different. The string also looks suspiciously like hex-encoded, so there might be some additional confusion about its C# value. You have to make sure that you are using the same key in JS and C#.
Second, the IV variable also, after parsing, looks like a hex-encoded value. So you have to be careful what value you are using on the C# side as well.
FYI, here are the values for Key and IV after parsing:
Key = 00000000000e00000d000c0000010000,
IV = 0000000e000f0a00
Thank you "Uwe" parsing with UTF8 solved everything.
What happens if you use: var Key = CryptoJS.enc.Utf8.parse("PSVJQRk9QTEpNVU1DWUZCRVFGV1VVT0="); instead >of HEX? And what is your Key and IV in C#? Because actually the string you are putting in your key and >parsing is not a hex string. hex is 0 to F
Thank you so much!

Encrypt iOS and Decrypt Node.js AES

I have searched high and low for a solution which and encrypt on Node.js server and Objective-C client, and vise versa using AES (or other if appropriate)
I am relatively new to cryptography, and understanding why my encrypted text is different in each language is beyond my knowledge.
This is what I have so far:
Node.js crypto methods Using this CryptoJS Library - node-cryptojs-aes
var node_cryptojs = require("node-cryptojs-aes");
var CryptoJS = node_cryptojs.CryptoJS;
var textToEncrypt = 'Hello';
var key_clear = 'a16byteslongkey!';
//encrypted + decrypted
var encrypted = CryptoJS.AES.encrypt(clearText, key_clear, { iv: null });
var decrypted = CryptoJS.AES.decrypt(encrypted, key_clear, { iv: null });
//Outputs
console.log("encrypted: " + encrypted); //encrypted: U2FsdGVkX1/ILXOjqIw2Vvz6DzRh1LMHgEQhDm3OunY=
console.log("decrypted: " + decrypted.toString(CryptoJS.enc.Utf8)); // decrypted: Hello
Objective-C crypto methods Using AESCrypt library
NSString* textToEncrypt = #"Hello";
// encrypt
NSString* encryptedText = [AESCrypt encrypt:textToEncrypt password:#"a16byteslongkey!"];
// decrypt
NSString* decryptedText = [AESCrypt decrypt:encryptedText password:#"a16byteslongkey!"];
// output
NSLog(#"Text to encrypt: %#", textToEncrypt); // Text to encrypt: Hello
NSLog(#"Encrypted text: %#", encryptedText); // Encrypted text: wY80MJyxRRJdE+eKw6kaIA==
NSLog(#"Decrypted text: %#", decryptedText); // Decrypted text: Hello
I've been scratching my head for ages and tried everything I can think of. Can show underlying crypto methods from the libraries if required. There is SHAR256 hash applied to the key in AESCrypt library but I have removed this, and think there is some missmatch with the string encoding.
I'm posting this here because there are bound to be others trying to interop Node.js and iOS. Everyone seems to get hung up on keeping everything in the correct structures, buffers, strings etc. I know I did. So here is a step-by-step process to creating a key, creating an iv, encrypting, decrypting and placing in base64 for easy transfer:
JavaScript (Node.js using the CryptoJS module)
// Generate key from password and salt using SHA256 to hash and PDKDF2 to harden
var password = "1234567890123456";
var salt = "gettingsaltyfoo!";
var hash = CryptoJS.SHA256(salt);
var key = CryptoJS.PBKDF2(password, hash, { keySize: 256/32, iterations: 1000 });
console.log("Hash :",hash.toString(CryptoJS.enc.Base64));
console.log("Key :",key.toString(CryptoJS.enc.Base64));
// Generate a random IV
var iv = CryptoJS.lib.WordArray.random(128/8);
console.log("IV :",iv.toString(CryptoJS.enc.Base64));
// Encrypt message into base64
var message = "Hello World!";
var encrypted = CryptoJS.AES.encrypt(message, key, { iv: iv });
var encrypted64 = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
console.log("Encrypted :",encrypted64);
// Decrypt base64 into message again
var cipherParams = CryptoJS.lib.CipherParams.create({ ciphertext: CryptoJS.enc.Base64.parse(encrypted64) });
var decrypted = CryptoJS.AES.decrypt(cipherParams, key, { iv: iv }).toString(CryptoJS.enc.Utf8);
console.log("Decrypted :",decrypted);
iOS SDK using CommonCrypto
// Generate key from password and salt using SHA256 to hash and PDKDF2 to harden
NSString* password = #"1234567890123456";
NSString* salt = #"gettingsaltyfoo!";
NSMutableData* hash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
NSMutableData* key = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH];
CC_SHA256(salt.UTF8String, (CC_LONG)strlen(salt.UTF8String), hash.mutableBytes);
CCKeyDerivationPBKDF(kCCPBKDF2, password.UTF8String, strlen(password.UTF8String), hash.bytes, hash.length, kCCPRFHmacAlgSHA1, 1000, key.mutableBytes, key.length);
NSLog(#"Hash : %#",[hash base64EncodedStringWithOptions:0]);
NSLog(#"Key : %#",[key base64EncodedStringWithOptions:0]);
// Generate a random IV (or use the base64 version from node.js)
NSString* iv64 = #"ludWXFqwWeLOkmhutxiwHw=="; // Taken from node.js CryptoJS IV output
NSData* iv = [[NSData alloc] initWithBase64EncodedString:iv64 options:0];
NSLog(#"IV : %#",[iv base64EncodedStringWithOptions:0]);
// Encrypt message into base64
NSData* message = [#"Hello World!" dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData* encrypted = [NSMutableData dataWithLength:message.length + kCCBlockSizeAES128];
size_t bytesEncrypted = 0;
CCCrypt(kCCEncrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
message.bytes, message.length,
encrypted.mutableBytes, encrypted.length, &bytesEncrypted);
NSString* encrypted64 = [[NSMutableData dataWithBytes:encrypted.mutableBytes length:bytesEncrypted] base64EncodedStringWithOptions:0];
NSLog(#"Encrypted : %#",encrypted64);
// Decrypt base 64 into message again
NSData* encryptedWithout64 = [[NSData alloc] initWithBase64EncodedString:encrypted64 options:0];
NSMutableData* decrypted = [NSMutableData dataWithLength:encryptedWithout64.length + kCCBlockSizeAES128];
size_t bytesDecrypted = 0;
CCCrypt(kCCDecrypt,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
key.bytes,
key.length,
iv.bytes,
encryptedWithout64.bytes, encryptedWithout64.length,
decrypted.mutableBytes, decrypted.length, &bytesDecrypted);
NSData* outputMessage = [NSMutableData dataWithBytes:decrypted.mutableBytes length:bytesDecrypted];
NSString* outputString = [[NSString alloc] initWithData:outputMessage encoding:NSUTF8StringEncoding];
NSLog(#"Decrypted : %#",outputString);
The output should be the same
/*
Hash : AEIHWLT/cTUfXdYJ+oai6sZ4tXlc4QQcYTbI9If/Moc=
Key : WdHhJ19dSBURBA25HZSpbCJ4KnNEEgwzqjgyTBqa3eg=
IV : ludWXFqwWeLOkmhutxiwHw==
Encrypted : D3JpubesPMgQTiXbaoxAIw==
Decrypted : Hello World!
*/
Hopefully this saves someone else the time I've wasted :)
Are you sure the same key is being used in both libraries? You say you took out the SHA-256 part in AESCrypt, how is the library using the password parameter now? The AES algorithm can only use keys of 16, 24, or 32 bytes in length. Your password is 16 bytes long, but did you change the corresponding parameter to 128 (instead of 256) in the encrypt function?
Do you know how CryptoJS is using the key parameter? Are you sure it's being used directly, or might there be some processing (for example, hashing) before it's passed to the underlying primitive AES encryption function?
What mode of encryption is the CryptoJS library using? Its documentation doesn't say. Given that it asks for an IV, it's probably CBC, but you would have to look at the source to know for sure.
AESCrypt's documentation claims to use CBC mode, but you don't give it an IV anywhere. That must mean that it generates it own somewhere, or always uses a fixed one. (Which half defeats the purpose of CBC mode, but that's another story). So you need to figure out what the IV actually is.
TL;DR: unless you make sure that the same key and key length, the same mode, and the same IV are used across both libraries, then you will have different cipher text.

Categories