I am struggling with some legacy-code written in PHP 5.5 and mcrypt.
I want to create a backward-compatible functionality in Node.js so in the result I have to port code below to newer standards.
public function decr($hash) {
$decoded = base64_decode($hash);
$decodedShorter = substr($decoded, 0, -8);
$iv = substr($decoded, -8);
$decr = rtrim(#mcrypt_decrypt(MCRYPT_3DES, file_get_contents('some.key'), $decodedShorter, MCRYPT_MODE_CFB, $iv));
return $decr;
}
I've been experimenting with multiple strategies both crypto-js and native crypto out of node engine.
The latest problem I faced:
ERR_CRYPTO_INVALID_IV
const decrypt = (text, secretKey, iv = null) => {
const decipher = crypto.createDecipheriv('des-ede3-cfb8', secretKey, iv);
let decrypted = decipher.update(text, 'utf8');
decrypted += decipher.final();
return decrypted;
};
async function main() {
const decoded = atob(name);
const key = await readFile(
path.resolve(`some.key`)
)
const decodedShorter = decoded.substr(0, decoded.length - 8)
const iv = decoded.substr(-8)
return decrypt(decodedShorter, key, Buffer.from(iv))
}
Any ideas? Is the new openSSL implementation so different from mcrypt one that it is not compatible? Or maybe I messed up with something? I am pretty sure that types of arguments are correct as I was referring to #types/node/crypto, but there is something incorrect with content/logic itself...
The decr() method in the PHP code first Base64 decodes the encrypted data and then separates ciphertext and IV. Here the 8 bytes IV is expected to be appended to the ciphertext.
After that a decryption with AES in CFB mode is performed. There are different CFB variants of different segment sizes, here a segment size of 8 bits is used. CFB is a stream cipher mode, so no padding is needed/applied.
The bug in the posted NodeJS code is that ciphertext and IV are processed as strings using a UTF-8 encoding. This generally corrupts an arbitrary byte sequence (such as a ciphertext or an IV).
Regarding the ciphertext, the corruption happens in decipher.update(text, 'utf8'). Here UTF-8 is explicitly specified as input encoding in the second parameter. Regarding the IV, the corruption happens when reading the IV into the buffer: Buffer.from(iv). Since no encoding is specified in the second parameter, UTF-8 is implicitly used. Both problems can be fixed by using latin1 as encoding.
A more robust solution is to use buffers throughout, so that no encoding is necessary:
var crypto = require('crypto')
const decrypt = (text, secretKey, iv = null) => {
const decipher = crypto.createDecipheriv('des-ede3-cfb8', secretKey, iv);
let decrypted = decipher.update(text, '', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
const name = "OrjgCsq9EkT2TkCZzDOfW492nXQCNIC0BtVJy1FaaTXv2jXAPqx75kaUJVSG/5MCFXXq"
const decoded = Buffer.from(name, 'base64')
const decodedShorter = decoded.slice(0, decoded.length - 8)
const iv = decoded.slice(decoded.length - 8)
const key = Buffer.from('ffa3b5205582d6ea7de6439ec2bafef46a80810003158922', 'hex');
console.log(decrypt(decodedShorter, key, iv))
Test: Both codes decrypt the following ciphertext $ciphertext with the key $key into the given plaintext:
$ciphertext = 'OrjgCsq9EkT2TkCZzDOfW492nXQCNIC0BtVJy1FaaTXv2jXAPqx75kaUJVSG/5MCFXXq';
$key = hex2bin('ffa3b5205582d6ea7de6439ec2bafef46a80810003158922');
// Plaintext: The quick brown fox jumps over the lazy dog
Related
I am doing Encryption and Decryption using both NodeJS Crypto and CryptoJS library in both front-end and backend. I am able to do the encryption using NodeJS Crypto which works perfectly fine and the encrypted string is also consistent with the Java encryption. But when I use, CryptoJS library the encryption string does not as expected. I provide below the standalone Typescript code.
Please help me. I am using aes-256-cfb8 Algorithm. Is it because of this Algorithm which may not be supported by CryptoJS library? Please give me some direction, I am struck now.
import * as crypto from "crypto";
import CryptoJS, { mode } from "crypto-js";
export class Test3 {
private static readonly CRYPTO_ALGORITHM = 'aes-256-cfb8'
private static readonly DEFAULT_IV: string = "0123456789123456";
// NodeJS Crypto - Works fine
public encryptValue(valueToEncrypt: string, secretKey: string): string {
let encryptedValue: string = '';
const md5 = crypto.createHash('md5').update(secretKey).digest('hex');
const key: string = md5;
console.log("MD5 Value : ", md5);
const iv = Buffer.from(Test3.DEFAULT_IV)
console.log("iv: ", iv)
const cipher = crypto.createCipheriv(Test3.CRYPTO_ALGORITHM, key, iv);
let encrypted = cipher.update(valueToEncrypt, 'utf8', 'base64');
console.log("encrypted ::: ", encrypted);
encrypted += cipher.final('base64');
encryptedValue = encrypted;
return encryptedValue;
}
// CryptoJS library - does not work
public check3(valueToEncrypt: string, secretKey: string): void {
const hash = CryptoJS.MD5(secretKey);
const md5Key: string = hash.toString(CryptoJS.enc.Hex)
console.log("MD5 Value: ", md5Key); // Upto correct
const IV11 = CryptoJS.enc.Utf8.parse(Test3.DEFAULT_IV);
const key11 = CryptoJS.enc.Utf8.parse(md5Key);
const data = CryptoJS.enc.Utf8.parse(valueToEncrypt);
const enc1 = CryptoJS.AES.encrypt(data, key11, {
format: CryptoJS.format.OpenSSL,
iv: IV11,
mode: CryptoJS.mode.CFB,
padding: CryptoJS.pad.NoPadding
});
console.log("Cipher text11: ", enc1.toString());
}
}
const text2Encrypt = "A brown lazy dog";
const someSecretKey: string = "projectId" + "~" + "India";
const test = new Test3();
// Using NodeJS Crypto
const enc = test.encryptValue(text2Encrypt, someSecretKey);
console.log("Encrypted Value Using NodeJS Crypto: ", enc); // ===> nJG4Fk27JZ7E5klLqFAt
// Using CryptoJS
console.log("****************** Using CryptoJS ******************")
test.check3(text2Encrypt, someSecretKey);
Please help me for the method check3() of the above class. Also I have checked many links in SO.
You suspect right. Both codes use different variants of the CFB mode. For CFB a segment size must be defined. The segment size of the CFB mode corresponds to the bits encrypted per encryption step, see CFB.
The crypto module of NodeJS supports several segment sizes, e.g. 8 bits (aes-256-cfb8) or 128 bits (aes-256-cfb), while CryptoJS only supports a segment size of 128 bits (CryptoJS.mode.CFB).
If the segment size is changed to 128 bits (aes-256-cfb) in the crypto module, both ciphertexts match.
Edit:
In this post you will find an extension of CryptoJS for the CFB mode, so that also a variable segment size, e.g. 8 bits, is supported.
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.
I have code that encrypts user's data using CryptoJS.AES, stores key, iv and encrypted content in different places.
Also it decrypts encrypted content using stored key and iv by user demand.
I want to use Subtle Crypto browser API for encryption,which is done.
But I also want to have possibility to decrypt old data (that was ecnrypted using CryptoJS.AES) using Subtle Crypto.
old data was generated with following code
var CryptoJS = require("crypto-js/core");
CryptoJS.AES = require("crypto-js/aes");
let encKey = generateRandomString();
let aesEncrypted = CryptoJS.AES.encrypt(content, encKey);
let encrypted = {
key: aesEncrypted.key.toString(),
iv: aesEncrypted.iv.toString(),
content: aesEncrypted.toString()
};
and I've tried to decrypt it as following
let keyArrayBuffer = hexArrayToArrayBuffer(sliceArray(encrypted.key, 2));
let decKey = await importKey(keyArrayBuffer);
let decIv = hexArrayToArrayBuffer(sliceArray(encrypted.iv, 2));
let encContent = stringToArrayBuffer(encrypted.content);
let decryptedByteArray = await crypto.subtle.decrypt(
{ name: "AES-CBC", iv: decIv },
decKey,
encContent
);
let decrypted = new TextDecoder().decode(decrypted);
I receive DOMException error without backtrace on await crypto.subtle.decrypt
complete reproduction can be found at https://codesandbox.io/s/crypto-js-to-subtle-crypto-u0pgs?file=/src/index.js
In the CryptoJS code the key is passed as string. Therefore it is interpreted as a password, from which in combination with a randomly generated 8 bytes salt, a 32 bytes key and a 16 bytes IV are derived, see here. The proprietary (and relatively insecure) OpenSSL key derivation function EVP_BytesToKey is used for this.
CryptoJS.AES.encrypt() returns a CipherParams object that encapsulates various parameters, such as the generated key and IV as WordArray, see here. toString() applied to the key or IV WordArray, returns the data hex encoded. toString() applied to the CipherParams object, returns the ciphertext in OpenSSL format, i.e. the first block (= the first 16 bytes) consists of the ASCII encoding of Salted__, followed by the 8 bytes salt and the actual ciphertext, all together Base64 encoded, see here. This means that the actual ciphertext starts (after Base64 decoding) with the second block.
The following code illustrates how the ciphertext generated with CryptoJS can be decrypted with the WebCrypto API
//
// CryptoJS
//
const content = "The quick brown fox jumps over the lazy dog";
const encKey = "This is my passphrase";
const aesEncrypted = CryptoJS.AES.encrypt(content, encKey);
const encrypted = {
key: aesEncrypted.key.toString(),
iv: aesEncrypted.iv.toString(),
content: aesEncrypted.toString()
};
//
// WebCrypto API
//
// https://stackoverflow.com/a/50868276
const fromHex = hexString => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16)));
// https://stackoverflow.com/a/41106346
const fromBase64 = base64String => Uint8Array.from(atob(base64String), c => c.charCodeAt(0));
async function decryptDemo(){
const rawKey = fromHex(encrypted.key);
const iv = fromHex(encrypted.iv);
const ciphertext = fromBase64(encrypted.content).slice(16);
const key = await window.crypto.subtle.importKey(
"raw",
rawKey,
"AES-CBC",
true,
["encrypt", "decrypt"]
);
const decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv
},
key,
ciphertext
);
const decoder = new TextDecoder();
const plaintext = decoder.decode(decrypted);
console.log(plaintext);
}
decryptDemo();
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>
I have following code in Java.
String secretString = 'AAABBBCCC'
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom securerandom = SecureRandom.getInstance("SHA1PRNG");
securerandom.setSeed(secretString.getBytes());
kgen.init(256, securerandom);
SecretKey secretKey = kgen.generateKey();
byte[] enCodeFormat = secretKey.getEncoded();
SecretKeySpec key = new SecretKeySpec(enCodeFormat, "AES");
Security.addProvider(new BouncyCastleProvider());
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
byte[] byteContent = content.getBytes("utf-8");
byte[] cryptograph = cipher.doFinal(byteContent);
String enc1 = Base64.getEncoder().encodeToString(cryptograph);
return enc1;
I need to implement it in JavaScript/Node.js, however I can only figure out the last half in js like below
'use strict';
const crypto = require('crypto');
const ALGORITHM = 'AES-256-ECB';
const secretString = 'AAABBBCCC'
// missing part in JS (how to convert secretString to key)
function encrypt(plaintext, key) {
const cipher = crypto.createCipheriv(ALGORITHM, key, Buffer.alloc(0));
return cipher.update(plaintext, 'utf8', 'base64') + cipher.final('base64');
}
For the previous Java part( from secretString to key generated by KeyGenerator), I don't know how to implement it in JavaScript, neither do I know whether there is a thing like KeyGenerator in JavaScript world could help me to do the heavy lifting work.
I think this is what you're after:
const crypto = require("crypto-js");
// Encrypt
const ciphertext = crypto.AES.encrypt('SOMETHING SECRET', 'secret key 123');
// Decrypt
const bytes = crypto.AES.decrypt(ciphertext.toString(), 'secret key 123');
const decryptedData = bytes.toString(crypto.enc.Utf8);
console.log(decryptedData);
https://runkit.com/mswilson4040/5b74f914d4998d0012cccdc0
UPDATE
JavaScript does not have a native equivalent for key generation. The answer is to create your own or use a third party module. I would recommend something like uuid for starters.
You can use crypto.randomBytes().
As per its documentation:
Generates cryptographically strong pseudo-random data. The size argument is a number indicating the number of bytes to generate.
Also, it uses openssl's RAND_bytes API behind scenes
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;