I would like to nodejs and an encrypted MongoDB database. I am concerned about performance. Consider the following use case:
I have an encrypted DB from which I retrieve a list of encrypted strings (names for example)
[_encrypted_name_1, _encrypted_name_2, ...]
I would like to decrypt all elements from that list
Since I am concerned about performance I did some tests to figure it out.
I observed that encrypting/decrypting lots of small strings is very slow compared to encrypting/decrypting a very large string.
Consider the following example:
var crypto = require('crypto'),
_ = require('lodash'),
encryptedStringArray = [],
decryptedStringArray = [],
encryptedLongString,
NB_ITERATION = 100000,
stringArray = [],
longString = '',
myString = 'Your Name';
function encrypt(text){
var cipher = crypto.createCipher('aes-256-cbc', 'd6F3Efeq');
var crypted = cipher.update(text, 'utf8', 'hex');
crypted += cipher.final('hex');
return crypted;
}
function decrypt(text){
var decipher = crypto.createDecipher('aes-256-cbc', 'd6F3Efeq');
var dec = decipher.update(text, 'hex', 'utf8');
dec += decipher.final('utf8');
return dec;
}
// SLOW: ARRAY OF STRINGS
console.time("slow");
for (var i = 0; i < NB_ITERATION; i += 1) {
stringArray.push(myString);
}
_.forEach(stringArray, function (item) {
encryptedStringArray.push(encrypt(item));
});
_.forEach(encryptedStringArray, function (item) {
decryptedStringArray.push(decrypt(item)); //.toString());
});
console.timeEnd("slow");
// FAST: SUPER LONG STRING
console.time("fast");
for (var i = 0; i < NB_ITERATION; i += 1) {
longString += myString;
}
encryptedLongString = encrypt(longString);
decrypt(encryptedLongString);
console.timeEnd("fast");
// **********************************************************************
// FOR LOOP
// **********************************************************************
//
console.time("for_loop");
stringArray = [];
encryptedStringArray = [];
decryptedStringArray = [];
for (var i = 0; i < NB_ITERATION; i += 1) {
stringArray.push(myString);
}
_.forEach(stringArray, function (item) {
encryptedStringArray.push(myString);
});
_.forEach(encryptedStringArray, function (item) {
decryptedStringArray.push(myString);
});
console.timeEnd("for_loop");
// **********************************************************************
// CREATION OF CIPHER ONLY - NO ENCRYPTION
// **********************************************************************
function noencrypt(text){
var cipher = crypto.createCipher('aes-256-cbc', 'd6F3Efeq');
// var crypted = cipher.update(text, 'utf8', 'hex');
// crypted += cipher.final('hex');
// return crypted;
return text;
}
function nodecrypt(text){
var decipher = crypto.createDecipher('aes-256-cbc', 'd6F3Efeq');
// var dec = decipher.update(text, 'hex', 'utf8');
// dec += decipher.final('utf8');
// return dec;
return text;
}
// SLOW
console.time("slow_nocrypt");
for (var i = 0; i < NB_ITERATION; i += 1) {
stringArray.push(myString);
}
_.forEach(stringArray, function (item) {
encryptedStringArray.push(noencrypt(item));
});
_.forEach(encryptedStringArray, function (item) {
decryptedStringArray.push(nodecrypt(item)); //.toString());
});
console.timeEnd("slow_nocrypt");
// FAST
console.time("fast_nocrypt");
for (var i = 0; i < NB_ITERATION; i += 1) {
longString += myString;
}
encryptedLongString = noencrypt(longString);
nodecrypt(encryptedLongString);
console.timeEnd("fast_nocrypt");
Here are the results:
slow: 2078ms
fast: 20ms
for_loop: 14ms
slow_nocrypt: 1898ms
fast_nocrypt: 1ms
Most of the time is spent creating Cipher objects. Therefore, I would like to use the same cipher object to encrypt/decrypt a list of of strings. In this case one needs to properly deal with the initialisation vector:
How to deal with the initialisation vector?
Once a cipher object is created, is it possible to change its initialisation vector?
The ideal scenario would probably be to use stream objects illustrated by the following pseudo-code:
var myArray = [
{to_encrypt: 'Your Name 1', iv: INIT_VECTOR_1},
{to_encrypt: 'Your Name 2', iv: INIT_VECTOR_2}];
var encrypted_array = [];
streamify(myArray)
.pipe(CIPHER_WITH_IV_UPDATE)
.write(streamify(encrypted_array));
Your code is actually slow because symmetric algorithms work in discrete blocks.
When you encrypt the single string Your Name, the cipher will pad it with random bytes to reach a multiple of the block size (128 bits).
Therefore, your slow version is actually encrypting more data per string.
To speed it up, either use a smaller block size or encrypt more data per block.
Related
So, I have a content which needs to be decrypted with private key and padding AES/ECB/PKCS5Padding.
I tried many libraries, and many examples, but none of them works. Now, this is the one where I managed to finish to the last steps, but im not sure if there is another library that can do this for me.
var absolutePath = path.resolve('./private.txt');
var privateKey = fs.readFileSync(absolutePath, "utf8");
var buffer = new Buffer(toDecrypt, "base64");
var decrypted = crypto.privateDecrypt(privateKey, buffer);
return decrypted.toString("utf8");
This one throws me error:
0D0680A8:asn1 encoding routines:ASN1_CHECK_TLEN:wrong tag
The second example, slighlty different than the first one, but uses padding (thats what I need, I just wanted to try without it to see if it works):
var stringKey = 'BEGIN RSA PRIVATE KEY-----....';
var cipherText = 'ENCRYPTEDTEXT';
// we compute the sha256 of the key
var hash = crypto.createHash("sha256");
hash.update(stringKey, "utf8");
var sha256key = hash.digest();
var keyBuffer = new Buffer(sha256key);
var cipherBuffer = new Buffer(cipherText, 'hex');
var aesDec = crypto.createDecipheriv("aes-256-ecb", keyBuffer, ''); // always use createDecipheriv when the key is passed as raw bytes
var output = aesDec.update(cipherBuffer, 'binary', 'binary');
var final = aesDec.final();
return output + final;
It crashes on the line var final = aesDec.final() and throws error:
digital envelope routines:EVP_DecryptFinal_ex:wrong final block length
Does anybody has knowledge or expirience on how to do this?
We had similar issue.
We receieved encrypted base 64 string from api and needed to decrypt aes key, and then, with decrypted aes key, we needed to decrypt the payload.
What we have done:
var bytes = new Buffer(input, 'base64'); this is the encrypted data from server
var aes = bytes.slice(offset, offset + AES_SIZE); slice byte array to get aes
`var aesString = aes.toString('binary');` convert it to binary string
Use forge library:
var forge = require('node-forge');
var pki = require('node-forge').pki;
// Grab private key from file
var absolutePath = path.resolve('../private-key.txt');
var privateKey = fs.readFileSync(absolutePath, "utf8");
// Generate private key object
var private_key = pki.privateKeyFromPem(privateKey);
var result;
// Decrypt aes key with private key
try {
result = private_key.decrypt(api.apiSecret, 'RSA-OAEP', {
md: forge.md.sha1.create(),
mgf1: {
md: forge.md.sha1.create()
}
});
} catch (err) {
console.error(err.message);
return;
}
// Build byte array from aes key
var base = new Buffer(result, 'binary');
// Generate initialization vector
var iv = forge.random.getBytesSync(api.content.length);
// Create decipher object with AES/ECB/PKCS5 padding
var decipher = forge.cipher.createDecipher('AES-ECB', forge.util.createBuffer(base));
decipher.start({ iv: iv });
// Add content for decrypting
decipher.update(forge.util.createBuffer(api.content));
var result = decipher.finish();
// Get json data from decipher object
var data = decipher.output.data;
Assuming this is my encrypt and decrypt function using native crypto from node.js.
var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';
var encrypt = function(secret){
var cipher = require('crypto').createCipher(algo,algoSecret);
var crypted = cipher.update(secret,'utf8','hex')
crypted += cipher.final('hex');
return crypted;
}
var decrypt = function(text){
var decipher = require('crypto').createDecipher(algo,algoSecret);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
I have to encrypt data of length 20. However, the first 8 chars are always the same and known by everyone. Ex: Always starts with api-key=. Does including or removing the 8 first chars affect the security of the system?
Ex: encrypt('api-key=askjdhaskdhaskd') vs 'api-key=' + encrypt('askjdhaskdhaskd')
Does including or removing the 8 first chars affect the security of the system?
Yes, but only slightly.
CBC mode with a static IV is deterministic, which means that an attacker who only observes ciphertexts can determine if the same plaintext prefix was sent before. Since the first 8 bytes are known to be static, the chance for the next 8 bytes of the first block are much more probable to equal another API key, which does not necessarily match in its entirety. Whether this information is useful to the attacker is a completely different question.
It would be better to always generate an unpredictable (read: random) IV for CBC mode instead of relying on the same IV that is derived from a password. An IV is always 16 bytes or 32 hex-encoded characters long for AES regardless of key size.
Some example code:
var crypto = require('crypto');
var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';
var key = crypto.pbkdf2Sync(algoSecret, 'salt', 1000, 256, 'sha256');
// the key can also be stored in Hex in order to prevent PBKDF2 invocation
var encrypt = function(secret){
var iv = crypto.randomBytes(16);
var cipher = crypto.createCipheriv(algo, key, iv);
var crypted = cipher.update(secret,'utf8','hex')
crypted += cipher.final('hex');
return iv.toString('hex') + crypted;
}
var decrypt = function(text){
var iv = new Buffer(text.slice(0, 32), 'hex');
text = text.slice(32);
var decipher = crypto.createDecipheriv(algo, key, iv);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
This is still not enough, because this code might be vulnerable to the padding oracle attack depending on your communication architecture. You should authenticate the ciphertexts with a message authentication code (MAC). A popular choice is HMAC-SHA256 for "encrypt-then-MAC".
var crypto = require('crypto');
var algo = 'aes-256-cbc';
var algoSecret = 'mySecret';
var key = crypto.pbkdf2Sync(algoSecret, 'salt', 1000, 512, 'sha512');
var keyMac = key.slice(32);
var keyEnc = key.slice(0, 32);
var encrypt = function(secret){
var iv = crypto.randomBytes(16);
var cipher = crypto.createCipheriv(algo, keyEnc, iv);
var crypted = cipher.update(secret,'utf8','hex')
crypted += cipher.final('hex');
var ct = iv.toString('hex') + crypted;
var hmac = crypto.createHmac('sha256', keyMac);
hmac.update(ct);
return ct + hmac.digest('hex');
}
var decrypt = function(text){
var hmac = crypto.createHmac('sha256', keyMac);
hmac.update(text.slice(0, -64));
if (hmac.digest('hex') !== text.slice(-64)) { // TODO: contant-time comparison
// TODO: do some decoy decryption
return false;
}
var iv = new Buffer(text.slice(0, 32), 'hex');
text = text.slice(32, -64);
var decipher = crypto.createDecipheriv(algo, keyEnc, iv);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
I'm trying to implement reCaptcha Secure Tokens in nodejs.
Looked at the examples made in Java and in .NET and created this version for node:
exports.getSecureToken = function() {
var algorithm = 'aes-128-ecb';
var tokenObj = { session_id: 'ab0069ec-3c2c-436c-868b-43c7a10db229'/*uuid.v4()*/, ts_ms: 1446560931992/*(new Date()).getTime()*/ };
var text = JSON.stringify(tokenObj);
var shaHash = new Buffer(crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO'/*config.reCAPTCHASecret*/).digest('hex'), 'hex');
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipher(algorithm, key, key);
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
return result;
};
Problem is that in .NET I get a token that works (using the right key not included in the post), but in node I get a different token for the same input, and it doesn't works:
.NET - LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
node - EGr7drd1JEylwzLGakZ6dpPRSf2nFdpzHOrJlLZlyHYmVRj5obAw7WjPt4W5l0vsywNEqCQ-2_d7qIZOMiOedianfBrQPOBaOmmq44IOB8Q
I got to see that key and input are the same (in .NET and node) right at the moment before encryption, so the problem must(?) be the cipher, any clues?
.NET code for reference:
public static void Main(string[] args)
{
//Your code goes here
Console.WriteLine(EncryptJsonToken(GetJsonToken()));
}
public static string GetJsonToken()
{
//Example: {"session_id": e6e9c56e-a7da-43b8-89fa-8e668cc0b86f,"ts_ms":1421774317718}
string jsonRequest = "{" + string.Format("\"session_id\": {0},\"ts_ms\":{1}", "ab0069ec-3c2c-436c-868b-43c7a10db229", 1446560931992) + "}";
return jsonRequest;
}
public static byte[] getKey()
{
string secretKey = "6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO";
SHA1 sha = SHA1.Create();
byte[] dataToHash = Encoding.UTF8.GetBytes(secretKey);
byte[] shaHash = sha.ComputeHash(dataToHash);
byte[] first16OfHash = new byte[16];
Array.Copy(shaHash, first16OfHash, 16);
return first16OfHash;
}
public static byte[] EncryptStringToBytes_Aes(string plainText, byte[] Key, byte[] IV)
{
// Check arguments.
if (plainText == null || plainText.Length <= 0)
throw new ArgumentNullException("plainText");
if (Key == null || Key.Length <= 0)
throw new ArgumentNullException("Key");
if (IV == null || IV.Length <= 0)
throw new ArgumentNullException("IV");
byte[] encrypted;
// Create an AesManaged object
// with the specified key and IV.
using (AesManaged aesAlg = new AesManaged())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.ECB;
// Create a decrytor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}
}
}
// Return the encrypted bytes from the memory stream.
return encrypted;
}
public static string EncryptJsonToken(string jsonToken)
{
byte[] encrypted = EncryptStringToBytes_Aes(jsonToken, getKey(), getKey());
//Base64 encode the encrypted data
//Also applys the URL variant of base64 encoding, unfortunately the HttpServerUtility.UrlTokenEncode(encrypted) seems to truncate the last value from the string so we can't use it?
return Convert.ToBase64String(encrypted, Base64FormattingOptions.None).Replace("=", String.Empty).Replace('+', '-').Replace('/', '_');
}
To debug in .NET: DEMO
You have two problems:
You're using JSON.stringify() to produce a valid JSON string, but the GetJsonToken() method in the C# code doesn't produce a valid JSON string. There are " missing for the UUID and there is a space between the session_id key and its value for some reason. You have to reflect those differences in JavaScript:
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
There is no such function crypto.createCipher(algorithm, key, key). There is however crypto.createCipheriv(algorithm, key, iv). createCipher(algorithm, password) can be used if one has a password instead of a key which you don't have. Since there is no IV for ECB mode, you can pass in an empty (binary) string as the IV.
Full code:
var crypto = require("crypto");
var algorithm = 'aes-128-ecb';
var uuidToken = "ab0069ec-3c2c-436c-868b-43c7a10db229";
var time = 1446560931992;
var text = "{\"session_id\": "+uuidToken+",\"ts_ms\":"+time+"}";
console.log("Token: " + text);
var shaHash = crypto.createHash('sha1').update('6LeyNOTTVALIDH2RLNaivqrrpm2zh56Y3uHqOjFO').digest();
var key = shaHash.slice(0, 16);
var cipher = crypto.createCipheriv(algorithm, key, "");
var encryptedToken = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
var result = encryptedToken.replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '');
console.log("result: " + result);
console.log("expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI");
Output:
Token: {"session_id": ab0069ec-3c2c-436c-868b-43c7a10db229,"ts_ms":1446560931992}
result: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
expected: LhPTUELia5vc0X6aDGDtqpsbmB7oqm6vUnzk5BL2auactYXRU5TEUzML8gZ_JubXG07rvJxk1Sb5_a-wqVUGEf_UuO1gGi-WO83yJHOxnjI
I am working on migration of code form java to Nodejs. I have one requirement to encrypt the text with private key using "DESede/ECB/NoPadding" algorithm. Currently code is written in Java and now I need to migrate to Nodejs. Since encrypted key is sent to other application therefore I can't change the algorithm or key here. Following is approach used in java
1. Stored the private key in hex string. I.e. 48 chars hex string as below which is equivalent to 24 bytes reuquired for 3des
73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2
2. Following is code written in java
https://github.com/dilipkumar2k6/3des/blob/master/TripleDes.java
import java.security.GeneralSecurityException;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
public class TripleDes {
// Crypto library related keys
private static final String ALGO_NAME = "DESede/ECB/NoPadding";
private static final int PADDING_BLOCK = 8;
// Test Data
private static final String PLAIN_TEXT = "Hello World";
private static final String SHARED_KEY = "73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2";
public static void main(String[] arg) {
try {
// Get Algorithm name
String desAlgoName = getDESAlgorithmName(ALGO_NAME);
// Create Cipher object
Cipher cipher = Cipher.getInstance(ALGO_NAME);
//Actual DES algo needs 56 bits key, which is equivalent to 1byte (0 at 0th position) Get 8*3 byets key
byte [] key = hexFromString(SHARED_KEY);
System.out.println("DES Algorithm shared key size in bytes >> "+key.length);
// Create SecretKeySpec
SecretKeySpec secretKeySpec = new SecretKeySpec(key, desAlgoName);
//Encrypt bytes
byte [] encryptedBytes = encryptIntoBytes(cipher, secretKeySpec, PLAIN_TEXT.getBytes(), 0, PLAIN_TEXT.getBytes().length);
String encryptedString= hexToString(encryptedBytes);
System.out.println(encryptedString);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static byte[] encryptIntoBytes(Cipher cipher, SecretKeySpec secretKeySpec, byte[] dct, int offset, int len) throws GeneralSecurityException {
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ect = cipher.doFinal(addPadding(dct, offset, len));
return ect;
}
public static String getDESAlgorithmName(String algoName) {
System.out.println("getDESAlgorithmName algoName >> "+algoName);
String desAlgoName = null;
int i = algoName.indexOf("/");
if (i != -1)
desAlgoName = algoName.substring(0, i);
else
desAlgoName = algoName;
return desAlgoName;
}
/**
* Adds padding characters to the data to be encrypted. Also adds random
* Initial Value to the beginning of the encrypted data when using Triple
* DES in CBC mode (DES-EDE3/CBC).
*
* #param inData
* Array of bytes to be padded
* #param offset
* Offset to starting point within array
* #param len
* Number of bytes to be encrypted
* #return Padded array of bytes
*/
public static byte[] addPadding(byte[] inData, int offset, int len) {
System.out.println("addPadding offset >> "+offset+", len >> "+len);
byte[] bp = null;
int padChars = PADDING_BLOCK; // start with max padding value
int partial = (len + 1) % padChars; // calculate how many extra bytes
// exist
if (partial == 0) {
padChars = 1; // if none, set to only pad with length byte
} else {
padChars = padChars - partial + 1; // calculate padding size to
// include length
}
System.out.println("addPadding >> Add padding of "+padChars);
/*
* Create a byte array large enough to hold data plus padding bytes The
* count of padding bytes is placed in the first byte of the data to be
* encrypted. That byte is included in the count.
*/
bp = new byte[len + padChars];
bp[0] = Byte.parseByte(Integer.toString(padChars));
System.arraycopy(inData, offset, bp, 1, len);
return bp;
}
public static byte[] hexFromString(String hex) {
int len = hex.length();
byte[] buf = new byte[((len + 1) / 2)];
int i = 0, j = 0;
if ((len % 2) == 1)
buf[j++] = (byte) fromDigit(hex.charAt(i++));
while (i < len) {
buf[j++] = (byte) ((fromDigit(hex.charAt(i++)) << 4) | fromDigit(hex
.charAt(i++)));
}
return buf;
}
public static int fromDigit(char ch) {
if (ch >= '0' && ch <= '9')
return ch - '0';
if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
if (ch >= 'a' && ch <= 'f')
return ch - 'a' + 10;
throw new IllegalArgumentException("invalid hex digit '" + ch + "'");
}
public static String hexToString(byte[] ba) {
return hexToString(ba, 0, ba.length);
}
public static final char[] hexDigits = { '0', '1', '2', '3', '4', '5',
'6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
public static String hexToString(byte[] ba, int offset, int length) {
char[] buf = new char[length * 2];
int j = 0;
int k;
for (int i = offset; i < offset + length; i++) {
k = ba[i];
buf[j++] = hexDigits[(k >>> 4) & 0x0F];
buf[j++] = hexDigits[k & 0x0F];
}
return new String(buf);
}
}
I need to migrate this code to Nodejs and facing multiple issues. I refeered http://mygo.iteye.com/blog/2018882 to get the basic idea on nodejs way to do the encryption in des3. However I see following difference in JAVA way and Nodejs way.
1. JAVA is using Hex string of 48 lenght as key, since one char in hex is 4 bits therfore final size is equivalent to 24 bytes length which meets DES3 requirement.
2. In Java code, final key is being used as bytes (as needed by DES) which made indpendent of the way we store the key
3. In node js, key is stored as character i.e. to use des3 I have to use 24 bytes which is equivalent to 24 chars key as 73AD9CEC99816AA6A4D82FB2. Here this is string of 24 chars and since one char is one byte thereofore total length is 24 bytes which meets DES3 requirement.
4. Following is nodejs code for reference
https://github.com/dilipkumar2k6/3des/blob/master/Crypto.js
'use strict';
/*
* Offers related services.
*/
var crypto = require("crypto");
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: true,
key: '73AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = param.plaintext;
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return txt;
}
Following is my problem.
1. How can i convert my existing hex code into string? I used "hexToString" method (please check the java code)to convert hex into string. However getting weired character (this is also expected but problem is how i can use this transformed key in nodejs.
2. Can I pass byte array as key to Nodejs? It will make problem easy as I can easily convert my hex key into bytes array and I store my bytes array key in nodejs code.
3. In my javacode, I have custom padding logic, how can i write same logic in nodejs?
4. Most importantly, can I achieve same encryption logic in nodejs (similar to java)?
Artjom B. helped me to get the insight of nodejs and des3 algorithm. I have edited my post to clarify my exact requirement.
I think my main problem is, how can i feed byte[] as key to nodejs crypto for DES3?
I am kind of stuck. Please help.
Running crypto.getCiphers() shows you the available ciphers. Triple DES (EDE) in ECB mode with two keys (16 byte key) can be used as des-ede. If you have three part key (24 byte key) you should use des-ede3. ecb probably does not appear in the cipher description, because it's the most basic form.
Triple DES-EDE has different ways to use a key. EDE means encrypt-decrypt-encrypt with three different keys. If you only have for example one 8 byte key, this suggests that you use the same key for every phase of EDE. It's clear from your Java code that you have a 24 byte key (48 hex encoded chars). You have to use the same key.
The crypto module uses PKCS7 padding by default, so you will need to set the auto padding to false and do the padding yourself. I leave that task up to you.
module.exports = {
encrypt: function (plainText) {
return encrypt({
alg: 'des-ede3', //3des-ecb
autoPad: false,
key: '73AD9CEC99816AA6A4D82FB273AD9CEC99816AA6A4D82FB2',
plaintext: 'Hello World',
iv: null
});
}
};
function mypad(buf){
// TODO: do the padding
// replicate padding as in Java
return buf;
}
function myunpad(buf){
// TODO: do the unpadding
// read the first *byte* and remove as many trailing *bytes*
return buf;
}
function encrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0);
var plaintext = mypad(new Buffer(param.plaintext));
var alg = param.alg;
var autoPad = param.autoPad;
//encrypt
var cipher = crypto.createCipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad); //default true
var ciph = cipher.update(plaintext, 'utf8', 'hex');
ciph += cipher.final('hex');
console.log(alg, ciph);
return ciph;
}
function decrypt(param) {
var key = new Buffer(param.key);
var iv = new Buffer(param.iv ? param.iv : 0)
var alg = param.alg;
var autoPad = param.autoPad;
//decrypt
var decipher = crypto.createDecipheriv(alg, key, iv);
cipher.setAutoPadding(autoPad);
var txt = decipher.update(ciph, 'hex', 'utf8');
txt += decipher.final('utf8');
console.log(alg, txt);
return myunpad(new Buffer(txt, 'hex'));
}
Word of caution:
Don't use (3)DES especially with only one 8 byte key! Don't use ECB mode! Don't use NoPadding for block modes! Use AES-256 with GCM mode (no padding since it's a streaming mode).-
This is my decrypt function based on Artjom's answer. use 'des-ede3' if you have a 24byte key.
internals.decrypt = function (message, key) {
var message = Buffer.from(message, 'base64');
var decipher = crypto.createDecipher('des-ede', key);
var decryptedMessage = decipher.update(message, 'hex', 'utf8');
decryptedMessage += decipher.final('utf8');
return decryptedMessage.toString();
}
Using this Gist I was able to successfully decrypt AES256 in Node.js 0.8.7. Then when I upgraded to Node.js 0.10.24, I now see this error:
TypeError: error:0606506D:digital envelope
routines:EVP_DecryptFinal_ex:wrong final block length
at Decipheriv.Cipher.final (crypto.js:292:27)
Here is the decrypt code from the Gist (shown here for convenience):
var crypto = require('crypto');
var AESCrypt = {};
AESCrypt.decrypt = function(cryptkey, iv, encryptdata) {
encryptdata = new Buffer(encryptdata, 'base64').toString('binary');
var decipher = crypto.createDecipheriv('aes-256-cbc', cryptkey, iv),
decoded = decipher.update(encryptdata);
decoded += decipher.final();
return decoded;
}
AESCrypt.encrypt = function(cryptkey, iv, cleardata) {
var encipher = crypto.createCipheriv('aes-256-cbc', cryptkey, iv),
encryptdata = encipher.update(cleardata);
encryptdata += encipher.final();
encode_encryptdata = new Buffer(encryptdata, 'binary').toString('base64');
return encode_encryptdata;
}
var cryptkey = crypto.createHash('sha256').update('Nixnogen').digest(),
iv = 'a2xhcgAAAAAAAAAA',
buf = "Here is some data for the encrypt", // 32 chars
enc = AESCrypt.encrypt(cryptkey, iv, buf);
var dec = AESCrypt.decrypt(cryptkey, iv, enc);
console.warn("encrypt length: ", enc.length);
console.warn("encrypt in Base64:", enc);
console.warn("decrypt all: " + dec);
Ok, so there was a change to Crypto in the switch from 0.8 to 0.10 Crypto methods return Buffer objects by default, rather than binary-encoded strings
This means the above code needs to specify encodings.
These four lines:
decoded = decipher.update(encryptdata);
decoded += decipher.final();
encryptdata = encipher.update(cleardata);
encryptdata += encipher.final();
Are changed to:
decoded = decipher.update(encryptdata, 'binary', 'utf8');
decoded += decipher.final('utf8');
encryptdata = encipher.update(cleardata, 'utf8', 'binary');
encryptdata += encipher.final('binary');
This worked for me, but I am open to other suggestions.
As your answer states, those functions work with Buffers now unless you specify an encoding. That said, you'd be better off avoiding binary encoded strings entirely and treat everything as Buffers until you strictly need a string for something. This way you can also use your encryption helpers to process non-text content.
var crypto = require('crypto');
var AESCrypt = {};
AESCrypt.decrypt = function(cryptkey, iv, encryptdata) {
var decipher = crypto.createDecipheriv('aes-256-cbc', cryptkey, iv);
return Buffer.concat([
decipher.update(encryptdata),
decipher.final()
]);
}
AESCrypt.encrypt = function(cryptkey, iv, cleardata) {
var encipher = crypto.createCipheriv('aes-256-cbc', cryptkey, iv);
return Buffer.concat([
encipher.update(cleardata),
encipher.final()
]);
}
var cryptkey = crypto.createHash('sha256').update('Nixnogen').digest(),
iv = new Buffer('a2xhcgAAAAAAAAAA'),
buf = new Buffer("Here is some data for the encrypt"), // 32 chars
enc = AESCrypt.encrypt(cryptkey, iv, buf);
var dec = AESCrypt.decrypt(cryptkey, iv, enc);
console.warn("encrypt length: ", enc.length);
console.warn("encrypt in Base64:", enc.toString('base64'));
console.warn("decrypt all: " + dec.toString('utf8'));
My issue was that the string I was passing to my decrypt function was empty. I built in a check for empty strings and I did not receive the message again.
decrypt: function(text){
if(text.length == 0){
return text;
}
return this.decipher.update(text, 'hex', 'utf8') + this.decipher.final('utf8');
}