RNCryptor IOS + Javascript encryption / decryption AES 256 - javascript

I am new to encryption and i am trying to get some symmetric encryption usign AES256 going from a mobile app to a webpage through websockets.
I Encrypt the data using RNCryptor default settings
IOS CODE
NSString* message = #"testmessage";
NSData* pubData = [message dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedData = [RNEncryptor encryptData:pubData
withSettings:kRNCryptorAES256Settings
password:#"test"
error:&error];
if(error) {
NSLog(#"Error encrypting %#", [error localizedDescription]);
}
NSString* encryptedString = [encryptedData base64Encoding];
NSLog(#"Sending message %#", encryptedString);
[self.session publishData:[encryptedString dataUsingEncoding:NSUTF8StringEncoding] onTopic:#"test12345"];
Here is the output going out over websockets
AgEBnXPPvAkJb7YVapwCVNd5SQw4JwqU7BfLsEXNZyKy9SazfJT8w16Y/hYY7aKxuz3Kuy2tAXXX/cHCc3PMhvG+fzSfrslRVMKvD6L+oWvXLg==
JAVASCRIPT CODE - I receive the message and i try to parse it and display it
function onMessageArrived(message) {
var rawData = base64.decode(message.payloadString);
var encryptionSalt = rawData.substr(2,8);
var hmacSalt = rawData.substr(10,8);
var iv = rawData.substr(18, 16);
var ciphertext = rawData.substr(34, rawData.length-34-32);
var hmac = rawData.substr(rawData.length-32, 32);
var password = "test";
var key = CryptoJS.PBKDF2(password, encryptionSalt, { keySize: 256 / 32, iterations: 10000});
var plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Utf8.parse(ciphertext) },
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Latin1.parse(iv) }
);
showScreen('<span style="color: blue;">User: ' + CryptoJS.enc.Latin1.stringify(plaintextArray) + '</span>');
};
For some reason the code gets stuck on generating the key (maybe 10k iterations are too much for CryptoJS??? thats the iterations used on IOS )
I have tried a bunch of different things with this and the output is garbage im not actually getting the message decrypted. Any help would be greatly appreciated. If you are going to recommend SJCL please provide some code . RNCryptor uses its own message format. I use it because of the randomized iv it provides . Please recommend a different library if you know of any as well.
THanks for reading.

Please follow the documentation for the RNCryptor file format: https://github.com/RNCryptor/RNCryptor-Spec/blob/master/RNCryptor-Spec-v3.md
You should be able to derive all the necessary data from file and add your shared secret...
Specification for RNCryptor data format version 3
Byte: | 0 | 1 | 2-9 | 10-17 | 18-33 | <- ... -> | n-32 - n |
Contents: | version | options | encryptionSalt | HMACSalt | IV | ... ciphertext ... | HMAC |
version (1 byte): Data format version. Currently 3.
options (1 byte):
bit 0 - uses password
encryptionSalt (8 bytes): iff option includes
"uses password"
HMACSalt (8 bytes): iff options includes "uses
password"
IV (16 bytes) ciphertext (variable) -- Encrypted in CBC
mode HMAC (32 bytes)
All data is in network order (big-endian).

Related

When encrypting, decrypting, then re-encrypting a string with node-forge's AES impl, why are the encrypted and re-encrypted strings different?

I am trying to use node-forge to decrypt strings encrypted by another application. After decrypting I am not getting the original strings back, so I decided to put together the following SSCCE that encrypts a string, decrypts it, then re-encrypts it. The results I get don't make sense.
Original String: hi (hex equivalent would be 6869)
Encrypted Hex String: 7457
Decrypted Hex String: 2b0a684b
Re-Encrypted Hex String: 2e5c6d1dc7cfa554
Questions:
First and foremost, what am I doing wrong? i.e. why is the decrypted hex different from the original hex, and why is the re-encrypted hex different from the encrypted hex?
All of the code examples in the node-forge docs get the decrypted output as hex. What's up with this? I want plain text back i.e. 'hi'. How do I ask the library to give me text instead (calling decypher.output.toString() results in an error.)
My ultimate goal is to be able to decrypt the output of: echo -n "hi" | openssl enc -aes-256-ctr -K $(echo -n redacted12345678 | openssl sha256) -iv 1111111111111111 -a -A -nosalt using a javascript library. Any advice on how to do that would be greatly appreciated.
SSCCE:
var forge = require('node-forge'); //npm install node-forge
//Inital data
var data = 'hi';
var iv = '1111111111111111';
var password = 'redacted12345678';
var md = forge.md.sha256.create();
md.update(password)
var keyHex = md.digest().toHex();
var key = Buffer.from(keyHex, 'hex').toString()
var cipher = forge.cipher.createCipher('AES-CTR', key);
cipher.start({iv: iv});
cipher.update(forge.util.createBuffer(data));
cipher.finish();
var encrypted = cipher.output.toHex()
console.log("encrypted: " + encrypted) //encrypted: 7457
var decipher = forge.cipher.createDecipher('AES-CTR', key)
decipher.start({iv: iv});
decipher.update(forge.util.createBuffer(encrypted));
decipher.finish();
var decrypted = decipher.output.toHex()
console.log("decrypted: " + decrypted) //decrypted: 2b0a684b
var recipher = forge.cipher.createCipher('AES-CTR', key);
recipher.start({iv: iv});
recipher.update(forge.util.createBuffer(decrypted));
recipher.finish();
var reencrypted = recipher.output.toHex()
console.log("reencrypted: " + reencrypted) //reencrypted: 2e5c6d1dc7cfa554
I've rewritten the OpenSSL command you're trying to mimic as follows:
echo -n "hi" | openssl enc -aes-256-ctr \
-K $(echo -n redacted12345678 | openssl sha256 -binary | xxd -p -c 256) \
-iv $(echo -n 1111111111111111 | xxd -p) -a -A -nosalt
The changes I made are due to the following:
The hex ouput that the openssl sha256 command generates, is prefixed with (stdin)= (at least on my distribution), so I just ran the binary hash of your password through xxd to get the key as a clean hex string.
The openssl enc command expects the IV to be in hex format, so I also ran that through xxd. (Since 16 bytes are required for the AES IV, your command would have resulted in an IV value of 111111111111111100000000000000, which I assume is not what you were aiming for.)
Executing this yields the following base64 output for the encrypted string:
JAA=
To replicate the same, I modified your JavaScript code as follows:
const forge = require('node-forge');
const data = 'hi', iv = '1111111111111111', password = 'redacted12345678';
const key = forge.md.sha256.create().update(password).digest().getBytes();
const cipher = forge.cipher.createCipher('AES-CTR', key);
cipher.start({ iv });
cipher.update(forge.util.createBuffer(data));
cipher.finish();
const encryptedBytes = cipher.output.getBytes();
const encryptedBase64 = forge.util.encode64(encryptedBytes);
console.log("encrypted: " + encryptedBase64);
const decipher = forge.cipher.createDecipher('AES-CTR', key)
decipher.start({ iv });
decipher.update(forge.util.createBuffer(encryptedBytes));
decipher.finish();
const decryptedBytes = decipher.output.getBytes();
const decryptedString = forge.util.encodeUtf8(decryptedBytes);
console.log("decrypted: " + decryptedString);
const recipher = forge.cipher.createCipher('AES-CTR', key);
recipher.start({ iv });
recipher.update(forge.util.createBuffer(decryptedBytes));
recipher.finish();
const reencryptedBytes = recipher.output.getBytes();
const reencryptedBase64 = forge.util.encode64(reencryptedBytes);
console.log("reencrypted: " + reencryptedBase64);
Which generates matching output:
encrypted: JAA=
decrypted: hi
reencrypted: JAA=
In essence, everything works correctly when the entire encryption/decryption operation is done using raw bytes, and only converting from/to hex, base64 or UTF-8 string when processing input or presenting output.

Is there a behavioral equivalent to the AES256TextEncryptor Class of the Jasypt-Library in CryptoJS?

As a newbie to Cryptography, I'm trying to reproduce the same default behavior of the AES256TextEncryptor Class of the jasypt-library with the CrpytoJS library. This is my Java method, that basically takes in two arguments - the message that I want to encrypt as well as my secret paraphrase:
private String encryptWithAes256(String messageToBeEncrypted, String encryptorSecret) {
AES256TextEncryptor encryptor = new AES256TextEncryptor();
encryptor.setPassword(encryptorSecret);
return encryptor.encrypt(messageToBeEncrypted);
}
When encrypting the messageToBeEncrypted with this code, the resulting encrypted message is fine. What I found out is that the AES256TextEncryptor, which internally uses the StandardPBEStringEncryptor as a encryptor, seems to use the PBEWithHMACSHA512AndAES_256 algorithm as a default.
How can I reproduce the same encryption behavior with CrpytoJS? When I'm trying to encrypt the message with CryptoJS in the way it's documented here, the result is totally different from what I expect it to be.
Based on Topaco's comment, I came up with the following JavaScript Code to mimic the Java code:
function encryptWithAes256(messageToEncrypt, encryptorKey){
// Generate random 16 bytes salt
var salt = CryptoJS.lib.WordArray.random(128/8);
// Derive key
var key = CryptoJS.PBKDF2(encryptorKey, salt, { keySize: 256/32, iterations: 1000 });
console.log("derived key: " + key);
// Generate random 16 bytes init vector (iv)
var iv = CryptoJS.lib.WordArray.random(128/8);
var cipherText = CryptoJS.AES.encrypt(messageToEncrypt, key, {iv: iv});
console.log("aes encrypted text: "+ salt.toString() + iv.toString() + cipherText.toString());
}
The generated result still seems not be as expected though, as it's length is 88 characters, whereas the Java code generates a 64 character long encrypted message.
The posted code is close to the required result. The following still needs to be corrected:
PBKDF2 applies SHA1 by default, which means SHA512 must be explicitly specified.
The concatenation must be done on a binary level and not with the hex and Base64 encoded data.
If this is fixed, a possible implementation is:
function encryptWithAes256(messageToEncrypt, encryptorKey){
// Generate random 16 bytes salt
var salt = CryptoJS.lib.WordArray.random(128/8);
// Derive key
var key = CryptoJS.PBKDF2(
encryptorKey,
salt,
{ keySize: 256/32, iterations: 1000, hasher: CryptoJS.algo.SHA512 } // Apply SHA512
);
console.log("derived key:\n" + key);
// Generate random 16 bytes init vector (iv)
var iv = CryptoJS.lib.WordArray.random(128/8);
// Encrypt
var cipherText = CryptoJS.AES.encrypt(messageToEncrypt, key, {iv: iv});
// Concatenate
var encryptedData = salt.clone().concat(iv).concat(cipherText.ciphertext); // Concatenate on binary level
var encryptedDataB64 = encryptedData.toString(CryptoJS.enc.Base64); // Base64 encode the result
console.log("aes encrypted text:\n", encryptedDataB64.replace(/(.{56})/g,'$1\n'));
}
encryptWithAes256('The quick brown fox jumps over the lazy dog', 'my passphrase');
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
Since because of the random salt and IV always different data is generated, a test of the implementation is not possible by comparing the data. Instead, it must be checked whether the data generated with the CryptoJS code is decryptable with the Jasypt counterpart for decryption:
private static String decryptWithAes256(String ciphertextToBeDecrypted, String encryptorSecret) {
AES256TextEncryptor encryptor = new AES256TextEncryptor();
encryptor.setPassword(encryptorSecret);
return encryptor.decrypt(ciphertextToBeDecrypted);
}
which is indeed the case with the above CryptoJS implementation.

How to decrypt password from JavaScript CryptoJS.AES.encrypt(password, passphrase) in Python

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.

Node.js/javascript encrypt AES-128 like mcrypt_ecb in PHP

I have a PHP code that encrypt in AES-128 an ip address:
$ip = "MY_IP";
$secret = "MY_KEY";
$ip = #mcrypt_ecb(MCRYPT_RIJNDAEL_128, $secret, $ip, MCRYPT_ENCRYPT);
$encrypted = bin2hex($ip); // encrypted: 2854edb405cb7230ba1f4b87acddba8a
What I need to do is to have the same piece of code but using javascript/node.js. I've searched in the crypto node.js native module but I wasn't able to reproduce the same result:
var crypto = require('crypto');
var ip = "MY_IP";
var secret = "MY_KEY";
var cipher = crypto.createCipher("AES-128-ECB", secret);
var encrypted = cipher.update(ip, 'utf8', 'hex');
encrypted += cipher.final('hex'); // encrypted: e84c06888696edda0139e98fc2c0a8cc
Does someone have an idea ?
I've posted too quickly, found the solution:
$> npm install mcrypt
And then the code:
var MCrypt = require('mcrypt').MCrypt;
var ip = "MY_IP";
var secret = "MY_KEY"
var desEcb = new MCrypt('rijndael-128', 'ecb');
desEcb.open(secret);
var cipherText = desEcb.encrypt(ip); // cipherText: 2854edb405cb7230ba1f4b87acddba8a
MCrypt github for more encryption tool: https://github.com/tugrul/node-mcrypt
The problem here is that there are some things that PHP's mcrypt extension (and node's createCipher()) does behind the scenes that you're probably not aware of.
First off, createCipher() accepts a 'password' that is hashed with MD5 to derive the actual key. So instead what you should be using is createCipheriv(), which allows you to pass the key (and IV) directly, like PHP's mcrypt accepts. In ECB mode, IVs are ignored, so you can just pass in an empty string for the IV parameter.
Secondly, PHP's mcrypt will magically pad both your input and your key with null bytes if they are less than the cipher's block size and key size, respectively.
So for AES-128-ECB, we need to make sure the input and key lengths are a multiple of 16 bytes. With all of this knowledge we then find that appropriate code for the built-in crypto module might look something like:
var crypto = require('crypto');
function makePadded(str) {
var buf;
var len = str.length;
if (str.length % 16)
len = str.length + (16 - str.length % 16);
buf = new Buffer(len);
buf.fill(0);
buf.write(str, 0, str.length);
return buf;
}
var ip = makePadded('MY_IP');
var secret = makePadded('MY_KEY');
var cipher = crypto.createCipheriv("AES-128-ECB", secret, '');
var encrypted = cipher.update(ip, 'binary', 'hex');
encrypted += cipher.final('hex');
// Slice off at 16 bytes to match the input length
encrypted = encrypted.slice(0, 32);
console.log(encrypted);
One last thing that might be worth mentioning is that MCRYPT_RIJNDAEL_128 in PHP can be used to do 128, 192, or 256 bit encryption. So in PHP if 0 < keylen <= 16 then 128-bit encryption will be used, 192-bit encryption will be used if 16 < keylen <= 24 and 256-bit encryption will be used if 24 < keylen <= 32. However in node, you will need to adjust the cipher name appropriately, as node does not do the kind of "automatic adjustment" that PHP does.
in nodejs - password must be a 'binary' encoded string or a buffer. in PHP, the deprecated #mcrypt_ecb expects a key to be a string

Decoding Rijndael with node.js

I have to decode a rijndael 128 string
the string can be successfully using this online tool http://www.tools4noobs.com/online_tools/decrypt/ with theses parameters :
Algorythm rijndael 128
Mode : CBC
Decode the output using base64
I have to decode this using node.js and crypto module
Here is my code
function Token(TokenBase64 )
{
var crypto = require('crypto');
this.TokenToCheck = new Buffer(TokenBase64,'base64').toString();
this.GameKey = 'xxxxxxxxxxxxxxxxx';
var cryptKey = crypto.createHash('sha256').update(this.GameKey).digest()
this.decipher = crypto.createDecipheriv('aes-128-cbc', cryptKey, '12345678901234561234567890123456');
var dec = this.decipher.update( this.TokenToCheck);
dec += this.decipher.final();
return dec;
}
module.exports = Token;
The error output by this code when called is :
Error: DecipherInitIv error at new Decipheriv (crypto.js:360:17) at Object.Decipheriv (crypto.js:357:12) at new Token
The size of your IV is 32 characters (which will probably be used as 32 bytes). AES always uses a 128 bit block size and the IV for CBC is always the size of a single block. So you've got 16 characters too many.

Categories

Resources