Can I encrypt a file multiple times without an exponential file increase? - javascript

The below results in an exponential increase in size for encrypted:
let original = 'something'
let passphrase = 'whatever'
let times = 100
let i = 0
let encrypted = CryptoJS.AES.encrypt(original, passphrase).toString()
while (i < times) {
encrypted = CryptoJS.AES.encrypt(encrypted, passphrase).toString()
i++
}
Is there some other CryptoJS algorithm/method/approach I can use that will not result in an exponential size increase?
Or is this not possible?
NOTE: If I don't use toString() it breaks when I try to re-encrypt what has already been encrypted. I get a UnhandledPromiseRejectionWarning: RangeError: Invalid array length.

Running your code would timeout for me. The encryption string apparently getting very long as it was base64 encoded.
We can reduce how much it increases by encrypting the wordarray instead of the base64 encoded version of the wordarray:
let original = 'something'
let passphrase = 'whatever'
let times = 100
let i = 0
let encrypted = CryptoJS.AES.encrypt(original, passphrase).toString()
encrypted = CryptoJS.enc.Base64.parse(encrypted)
while (i < times) {
encrypted = CryptoJS.AES.encrypt(encrypted, passphrase).toString()
i++
encrypted = CryptoJS.enc.Base64.parse(encrypted)
}
http://jsfiddle.net/dwvxua96/
This runs fast and creates a string that grows by only a few bytes each iteration. You can probably reduce that more by setting padding options, or passing in a key/iv pair which may prevent the addition of a salt parameter.
the decryption would look like:
i = 0
while (i <= times) {
encrypted = CryptoJS.AES.decrypt(encrypted, passphrase)
encrypted = CryptoJS.enc.Base64.stringify(encrypted);
i++
}
encrypted = CryptoJS.enc.Base64.parse(encrypted);
encrypted = CryptoJS.enc.Utf8.stringify(encrypted)

Related

How to use crypto-js to save the binary data, of an encryption-output, to a file?

I'm trying to assist a mobile developer with encrypting an image and uploading it to Azure blob storage. The mobile app uses expo and crypto js. I'm not super familiar with mobile dev or expo but it looks like expo gives you access to a base64 encoded version of the image.
My goal is to encrypt that image data, using crypto js, and upload it to Azure blob storage.
The specifics of expo or Azure aren't really that important to my question, but I figure they're worth mentioning. What is important, I think, is that I'm using crypto js to AES encrypt that image data.
I'm starting with a base64 string of image data and so I use crypto js to parse that like follows ...
const words = CryptoES.enc.Base64.parse(data);
This gives me a WordArray representing the image data, I think (from the base64 string which the mobile API gives me).
Next I can encrypt that image data like so ...
const encrypted = CryptoES.AES.encrypt(words, AES_KEY, { iv: AES_IV });
Now that I have the encrypted data, I would like to write it out to a file in just a binary hex format or whatever. I don't want base64 text in the file and I don't want a hex-string in the file - I'd like it to contain the literal encrypted data byte for byte.
I'm not sure how to get this data.
I guess it's just "toString" but when I do that it says invalid utf8. This is a JPG file that is being dealt with.
How can I get just the actual byte data and write that to a file with crypto js?
CryptoJS.AES.encrypt() returns a CipherParams object that encapsulates, among others, the ciphertext as WordArray. One possibility is to convert this WordArray to a Uint8Array with a custom method. In the following code this conversion is done by convertWordArrayToUint8Array():
function convertWordArrayToUint8Array(wordArray) {
var arrayOfWords = wordArray.hasOwnProperty("words") ? wordArray.words : [];
var length = wordArray.hasOwnProperty("sigBytes") ? wordArray.sigBytes : arrayOfWords.length * 4;
var uInt8Array = new Uint8Array(length), index=0, word, i;
for (i=0; i<length; i++) {
word = arrayOfWords[i];
uInt8Array[index++] = word >> 24;
uInt8Array[index++] = (word >> 16) & 0xff;
uInt8Array[index++] = (word >> 8) & 0xff;
uInt8Array[index++] = word & 0xff;
}
return uInt8Array;
}
var AES_KEY = CryptoJS.enc.Utf8.parse('0123456789012345');
var AES_IV = CryptoJS.enc.Utf8.parse('5432109876543210');
var plaintext = 'The quick brown fox jumps over the lazy dog';
var ciphertextCP = CryptoJS.AES.encrypt(plaintext, AES_KEY, { iv: AES_IV }); // CipherParams object
var ciphertextWA = ciphertextCP.ciphertext; // WordArray
var ciphertextArr = convertWordArrayToUint8Array(ciphertextWA); // Uint8Array
This Uint8Array can now be stored in a file, e.g. with:
var fileName = "encdata.bin";
saveByteArray([ciphertextArr], fileName);
using saveByteArray() from here.
Another approach is to convert the WordArray to a binary string using the Latin1 encoder:
var ciphertextBinStr = ciphertextWA.toString(CryptoJS.enc.Latin1);
which can then easily be converted to a Uint8Array, e.g. with:
function str2Uint8Array(str) {
const arr = new Uint8Array(new ArrayBuffer(str.length));
for (let i = 0, strLen = str.length; i < strLen; i++)
arr[i] = str.charCodeAt(i);
return arr;
}
var ciphertextArr = str2Uint8Array(ciphertextBinStr);

How to Derive the Key and Initial Vector in Node.js

I have a shared key that I need to derive an iv from so I can decipher.
The apple business chat docs state:
Generate the Derived Key and Initial Vector
Run the shared key through the X9.63 Key Derivation Function with SHA256 hash function. This results in a 48-byte payload. Your results should be rV3qrszd0PMPgeRhNnlOYA==
Heres what I tried. I used scryptSync and pbkdf2Sync crypto functions with many 'salt' configurations. I'm unsure if these are the correct functions for this job.
const crypto = require('crypto');
const keyLength = 48;
// sharedKey is a base64 string
const sharedKey = "2lvSJsBO2keUHRfvPG6C1RMUmGpuDbdgNrZ9YD7RYnvAcfgq/fjeYr1p0hWABeif";
// publicKey is a base64 string
const publicKey = "BDiRKNnPiPUb5oala31nkmCaXMB0iyWy3Q93p6fN7vPxEQSUlFVsInkJzPBBqmW1FUIY1KBA3BQb3W3Qv4akZ8kblqbmvupE/EJzPKbROZFBNvxpvVOHHgO2qadmHAjHSg=="
const key1 = crypto.scryptSync(sharedKey, 'salt', keyLength);
console.log(key2.toString('base64'));
const key2 = crypto.pbkdf2Sync(sharedKey, 'salt', 10000, keyLength, 'sha256');
console.log(key2.toString('base64'));
// results should be:
// mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZM=
// iv: rV3qrszd0PMPgeRhNnlOYA==
Below is the Apple sample code for Deriving the Key and Initial Vector with a X9.63 Key Derivation Function.
def ITOSP(self, longint, length):
"""ITOSP, short for Integer-to-Octet-String Primitive, converts a non-negative integer
to an octet string of a specified length. This particular function is defined in the
PKCS #1 v2.1: RSA Cryptography Standard (June 14, 2002)
https://www.cryptrec.go.jp/cryptrec_03_spec_cypherlist_files/PDF/pkcs-1v2-12.pdf"""
hex_string = "%X" % longint
assert len(hex_string) <= 2 * length, "ITOSP function: Insufficient length for encoding"
return binascii.a2b_hex(hex_string.zfill(2 * length))
def KDFX963(self, inbyte_x, shared_data, key_length, hashfunct=sha256, hash_len=32):
"""KDFX963 is a key derivation function (KDF) that takes as input byte sequence inbyte_x
and additional shared data shared_data and outputs a byte sequence key of length
key_length. This function is defined in ANSI-X9.63-KDF, and this particular flavor of
KDF is known as X9.63. You can read more about it from:
http://www.secg.org/sec1-v2.pdf"""
assert key_length >= 0, "KDFX963 function: key_length should be positive integer"
k = key_length / float(hash_len)
k = int(ceil(k))
acc_str = ""
for i in range(1, k+1):
h = hashfunct()
h.update(inbyte_x)
h.update(self.ITOSP(i, 4))
h.update(shared_data)
acc_str = acc_str + h.hexdigest()
return acc_str[:key_length * 2]
X9.63 KDF is a key derivation function, described e.g. here and here. scrypt and PBKDF2 are also KDFs, but different ones, so of course the expected result cannot be reproduced with them.
So you need a NodeJS library that supports X.963 KDF. If you can't find one, you could also implement your own.
X9.63 KDF expects a shared secret and a shared info and determines a keysize large key as follows:
Create a 4 byte counter ci which is incremented starting with 0x0000001.
Concatenate the data conci = shared secret | ci | shared info
Hash the result hashi = hash(conci)
Concatenate the hashes hash1 | hash2 | ... until an output of length keysize has been generated.
More formally, including various checks, the algorithm is described in the links above. The Python code posted later in the question also implements this logic.
One possible NodeJS implementation (omitting the checks from the specification) is:
var crypto = require('crypto');
var digest = 'sha256';
var digestLen = 32;
function X963KDF(sharedSecret, sharedInfo, keySize){
var maxCount = Math.ceil(keySize/digestLen);
var result = Buffer.allocUnsafe(0);
for (var count = 1; count < maxCount + 1; count++){
var counter = Buffer.allocUnsafe(4);
counter.writeUInt32BE(count, 0);
var current = Buffer.concat([sharedSecret, counter, sharedInfo]);
var hash = crypto.createHash(digest).update(current).digest();
result = Buffer.concat([result, hash]);
}
return result.slice(0, keySize);
}
Test:
In the question the shared secret is posted, but not the shared info. An internet search reveals that the posted problem is described e.g. here, so that the shared info can also be determined (nevertheless it would be better if you add this information to your question):
var sharedSecret = Buffer.from('2lvSJsBO2keUHRfvPG6C1RMUmGpuDbdgNrZ9YD7RYnvAcfgq/fjeYr1p0hWABeif', 'base64')
var sharedInfo = Buffer.from('04389128d9cf88f51be686a56b7d6792609a5cc0748b25b2dd0f77a7a7cdeef3f111049494556c227909ccf041aa65b5154218d4a040dc141bdd6dd0bf86a467c91b96a6e6beea44fc42733ca6d139914136fc69bd53871e03b6a9a7661c08c74a', 'hex');
var keyiv = X963KDF(sharedSecret, sharedInfo, 48);
var key = keyiv.slice(0,32).toString('base64');
var iv = keyiv.slice(32, 48).toString('base64');
console.log("Key: ", key); // Key: mAzkYatDlz4SzrCyM23NhgL/+mE3eGgfUz9h1CFPhZM=
console.log("IV: ", iv); // IV: rV3qrszd0PMPgeRhNnlOYA==
The generated key and IV are equal to the expected values.

TripleDES Java Encryprion to Javascript Decryption

I am using Java to encrypt a text payload with Triple DES. First I create an ephemeral key that I will use for encrypting the payload:
private byte[] createEphemeralKey() throws Exception {
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
return keygen.generateKey().getEncoded();
}
Then I encrypt my payload with said key:
private String encryptTripleDES(byte[] ephemeralKey, String payload) throws Exception {
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(ephemeralKey, "DESede"));
byte[] plainTextBytes = payload.getBytes();
byte[] cipherText = cipher.doFinal(plainTextBytes);
return Base64.getEncoder().encodeToString(cipherText);
}
Also need a padding function to ensure the data length is divisable by 8:
private String adjustPadding(String input, int blockSize) {
int len = input.length() % blockSize;
int paddingLength = (len == 0) ? 0 : (blockSize - len);
while (paddingLength > 0) {
input += "F";
paddingLength--;
}
return input;
}
And here is my process end to end:
String data = "Marnus"
byte[] = ephemeralKey = createEphemeralKey();
String adjustedData = adjustPadding (data,8);
String encryptedPayload = encryptTripleDES(ephemeralKey, adjustedData);
String encodedKey = Base64.getEncoder().encodeToString(ephemeralKey)
So I take the 2 variables encryptedPayload and encodedKey, that are both Base64 encoded string, and send it off via HTTP to node express app.
In the Javascript side of things, I use node-forge - Here is the part of my express app that does the decryption:
let nodeBuffer = Buffer.from(data, 'base64')
let input = forge.util.createBuffer(nodeBuffer.toString('binary'))
// 3DES key and IV sizes
let keySize = 24;
let ivSize = 8;
let derivedBytes = forge.pbe.opensslDeriveBytes(ephemeralKey, null, keySize + ivSize);
let buffer = forge.util.createBuffer(derivedBytes);
let key = buffer.getBytes(keySize)
let iv = buffer.getBytes(ivSize)
let decipher = forge.cipher.createDecipher('3DES-ECB', key)
decipher.start({iv: iv})
decipher.update(input)
console.log('decipher result', decipher.finish())
let decryptedResult = decipher.output.data;
Here is an Triples DES example in the node-forge docs:
A few notes:
I create a node-forge buffer from a regular buffer since I don't have a input file like the examples gives. Here is how the docs states one should create one buffer from the other:
*I use base64 as that is what I used in the java side to encode the data that was sent.
Then, I dont have a salt so I left the 2'nd param null in opensslDeriveBytes as specified in the docs I should do.
Thirdly, I am also not sure if my keysize of 24 is correct?
My results
So doing an end to end test yields the following:
In my Java app, the test data was "Marnus", the encryptedPayload was ez+RweSAd+4= and the encodedKey was vCD9mBnWHPEBiQ0BGv7gc6GUCOoBgLCu.
Then in my javascript code data was obviously ez+RweSAd+4=(encryptedPayload) and the ephemeralKey was vCD9mBnWHPEBiQ0BGv7gc6GUCOoBgLCu(encodedKey).
After the decryption ran, the value of decryptedResult was ©ýÕ?µ{', which is obviously just garbage since it was not encoded yet, but I cant figure out which encoding to use?
I tried using forge.util.encode64(decipher.output.data), but that just gave me qf3VP7UYeyc=, which is not right.
For what it's worth, here is the type that decipher.output
With a lot more tweaking and testing different options, I got it working - and the good news is I managed to get it all working with the built in crypto library in nodejs (v12.18.4).
First things first, the JAVA side just needs a change to the key size (from 168 to 112), the rest remains the same - see below example as one single method (should be split up in final implementation of course for testability and usability):
//Some data:
String payload = "{\"data\":\"somedata\"}";
// Create Key
KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(112);
byte[] ephemeralKey = keygen.generateKey().getEncoded();
// Adjust the data, see adjustPadding method in the question for details.
String data = adjustPadding (payload,8);
// Wil now be "{"data":"somedata"}FFFFF", can just chop off extra in JS if need be. When sending JSON one knows the end of the object will always be "}"
// Do Encrypt
Cipher cipher = Cipher.getInstance("DESede/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(ephemeralKey, "DESede"));
byte[] plainTextBytes = data.getBytes();
byte[] cipherText = cipher.doFinal(plainTextBytes);
String encryptedPayload = Base64.getEncoder().encodeToString(cipherText);
//Lastly, Base64 the key so you can transport it too
String encodedKey = Base64.getEncoder().encodeToString(ephemeralKey)
on the Javascript side of things we keep it simple:
// I'm using TS, so change the import if you do plain JS
import crypto = require('crypto')
//need bytes from the base64 payload
let buff = Buffer.from(ephemeralKey, 'base64')
const decipher = crypto.createDecipheriv('des-ede3', buff, null)
decipher.setAutoPadding(false)
let decrypted = decipher.update(data, 'base64', 'utf8')
decrypted += decipher.final('utf8')
console.log(decrypted)
//{"data":"somedata"}FFFFF"

Incorrect decryption output when using CryptoJS library for AES

I am trying to encrypt and decrypt some random data using AES. I can successfully encrypt data using the following code,
function padString(source) {
var paddingChar = 'x';
var size = 16;
var padLength = size - source.length;
for (var i = 0; i < padLength; i++) source += paddingChar;
return source;
}
var key = CryptoJS.enc.Hex.parse('dassdadsasad');
var iv = CryptoJS.enc.Hex.parse('fedcba9876543210');
var message = "0x72648174091c3f7cd41773f636ca9a15756798";
var padMsg = padString(message);
var encrypted = CryptoJS.AES.encrypt(padMsg, key, {
iv: iv,
padding: CryptoJS.pad.NoPadding,
mode: CryptoJS.mode.CBC
});
but when I try to decrypt it,
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.NoPadding,
mode: CryptoJS.mode.CBC
});
I get the incorrect output for decryption instead of getting back my plain text.
Here is the output for instance,
Message: "0x72648174091c3f7cd41773f636ca9a15756798" 40
Padded message: "0x72648174091c3f7cd41773f636ca9a15756798" 40
Encrypted: 8qCjEtSLhchErbhJu8jo/xy8T5T1eSAFazuWLKwKdAW9F0ZUjJLlZw==
Encrypted text: f2a0a312d48b85c844adb849bbc8e8ff1cbc4f94f57920056b3b962cac0a7405bd1746548c92e567
Decrypted c748c55c0212d1688e79de5f00379eb0d802789501e6cbab3e6255b902eaa528a40d32123bcd0ce1
Can someone please tell me what am I doing wrong or if I am missing something ?
Here is the live demonstration, https://jsfiddle.net/4zb9hrxb/267/
Your key is invalid:
var key = CryptoJS.enc.Hex.parse('dassdadsasad');
does not make much sense in the first place since s does not exist in hexadecimal notation. Either specify a 256 bit key as hex, like this:
key = CryptoJS.enc.Hex.parse("123456789012345678901234567890ab");
(note: only hexadecimal characters) or let CryptoJS derive the key from a passphrase:
var key = 'dassdadsasad';
either follow what phihag said, or you can use some key derivation function such as, PBKDF2 to derive key from passphrase (as phihag suggested).
consider this for example,
import pbkdf2,
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/3.1.2/rollups/pbkdf2.js"></script>
and generate key as follows,
var passphrase = "dassdadsasad"
var keySize = 256;
var iterations = 100;
var salt = CryptoJS.lib.WordArray.random(128/8);
var key = CryptoJS.PBKDF2(passphrase, salt, {
keySize: keySize/32,
iterations: iterations
});
there is a very good example available here,
http://www.adonespitogo.com/articles/encrypting-data-with-cryptojs-aes/

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

Categories

Resources