AES encryption in Java unable to decrypt in Javascript - javascript

I am using password based encryption (PBKDF2WithHmacSHA1) using AES-256 (AES/CBC/PKCS5Padding).
In Java, I am using the following encryption and decryption codes and it works fine.
String password = "MyPassword1";
String salt = "MysaltString"; //Will switch to random salt generation.
String plainText = "Quick Brown Fox";
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
KeySpec spec = new PBEKeySpec(password.toCharArray(), salt.getBytes(), 1024, 256);
SecretKey tmp = factory.generateSecret(spec);
SecretKey secret = new SecretKeySpec(tmp.getEncoded(), "AES");
/* Encrypt the message. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secret);
AlgorithmParameters params = cipher.getParameters();
byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
byte[] ciphertext = cipher.doFinal(plainText.getBytes("UTF-8"));
/* Decrypt the message, given derived key and initialization vector. */
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(iv));
String plaintext = new String(cipher.doFinal(ciphertext), "UTF-8");
Now, for interop I need the encrypted string to be decrypted in Javascript. I tried to use forge (https://github.com/digitalbazaar/forge) and I am unable to decrypt.
var password = "MyPassword1";
var salt = "MysaltString"; //Will switch to random salt generation.
var derivedKey = forge.pkcs5.pbkdf2(password, salt, 1024, 32);
input = forge.util.createBuffer(encrypted);
var decipher = forge.cipher.createDecipher('AES-CBC', derivedKey);
decipher.start({iv: iv});
decipher.update(input);
res = decipher.finish();
My guess is it has to do with the format of input supplied to JS (password, salt, IV, encrptedtext), I am just not able to figure it out if it has to be base64, hex etc...

The reason is that Java uses signed bytes. You should use two's complement on the JavaScript side on the IV and the Ciphertext. Your JS code should look like this:
var password = "MyPassword1";
var salt = "MysaltString";
var derivedKey = forge.pkcs5.pbkdf2(password, salt, 1024, 32);
for (var i = 0; i < iv_in.length; i++) {
iv_in[i] = (iv_in[i] & 0xFF);
}
var iv = String.fromCharCode.apply(String, iv_in);
for (var i = 0; i < encrypted.length; i++) {
encrypted[i] = (encrypted[i] & 0xFF);
}
var input_c = String.fromCharCode.apply(String, encrypted)
var input = forge.util.createBuffer(input_c);
var decipher = forge.cipher.createDecipher('AES-CBC', derivedKey);
decipher.start({iv: iv});
decipher.update(input);
var res = decipher.finish();
if (res) {
console.log(decipher.output.toString('utf8'));
}
where iv_in and encrypted are the input byte arrays you receive from Java, for example:
var iv_in = [24, -54, -15, 5, 106, -44, 20, 10, 103, -62, -88, 28, 75, -68, -12, -14];
and
var encrypted = [20, -56, 117, 80, -91, -104, 22, -43, -127, -87, 7, 113, 66, 85, 105, -25]

Related

How to decrypt on frontend which is encrypted on backend using crypto

Data is encrypted with the below code, is there any option to decrypt on the front-end if know the key and iv?
const algorithm = 'aes-256-ctr';
// const algorithm = 'aes-128-cbc';
let inVec = crypto.randomBytes(16);
const secret = 'salted';
const key = crypto.createHash('sha256').update(String(secret)).digest('base64').substr(0, 32);
const cipher = crypto.createCipheriv(algorithm, key, key.substr(0, 16))
let encrypt = cipher.update(name, 'utf8', 'base64')
encrypt = encrypt + cipher.final();

decrypting CyproJS AES encryption in Cryptodome python

I am trying to decrypt message (in python) that has been encoded using CryptoJS in JavaScript. I have created an API in python, to post data I am using postman pre-request script.
The Error I am getting:
ValueError: Data must be padded to 16 byte boundary in CBC mode
JavaScript code for encryption
var data = {"feature_0": 0,
"feature_1": 0,
"feature_2": 0,
"feature_3": 0,
"feature_4": 0,
"feature_5": 0
};
let password = "lazydog";
let salt = "salt";
let iterations = 128;
data = JSON.stringify(data);
let len = 16 - ((data.length) % 16);
data += len.toString().repeat(len); --> removed (as suggested)
let bytes = CryptoJS.PBKDF2(password, salt, { keySize: 48, iterations: iterations });
let iv = CryptoJS.enc.Hex.parse(bytes.toString().slice(0, 32));
let key = CryptoJS.enc.Hex.parse(bytes.toString().slice(32, 96));
let encrypted = CryptoJS.AES.encrypt(data, key, {iv: iv}); //, mode: CryptoJS.mode.CBC
//encrypted = btoa(encrypted); --> removed (as suggested)
encrypted = encrypted.toString() -->added (as suggested)
postman.setGlobalVariable("data", encrypted);
python code for decryption:
def decode(encrypted):
data = b64decode(encrypted)
byte = PBKDF2("lazydog".encode("utf-8"), "salt".encode("utf-8"), 48, 128)
iv = byte[0:16]
key = byte[16:48]
cipher = AES.new(key, AES.MODE_CBC, iv)
text = cipher.decrypt(data) ## error is at this line
text = text[:-text[-1]].decode("utf-8")
return text
As the error said padding problem I added padding in JS code. Still I am not getting good results.
What's wrong I am doing here?
The encrypted string has posted to API which has written in python.
I don't know why but when encrypted passed to python '+' chars are being replaced with ' '(space). By replacing the spaces with '+' char I resolved the problem.
code
var data = {"feature_0": 0,
"feature_1": 0,
"feature_2": 0,
"feature_3": 0,
"feature_4": 0,
"feature_5": 0
};
let password = "lazydog";
let salt = "salt";
let iterations = 128;
data = JSON.stringify(data);
let bytes = CryptoJS.PBKDF2(password, salt, { keySize: 48, iterations:
iterations });
let iv = CryptoJS.enc.Hex.parse(bytes.toString().slice(0, 32));
let key = CryptoJS.enc.Hex.parse(bytes.toString().slice(32, 96));
let encrypted = CryptoJS.AES.encrypt(data, key, {iv: iv});
encrypted = encrypted.toString()
postman.setGlobalVariable("data", encrypted);
python code
def decode(encrypted):
encrypted = encrypted.replace(' ', '+') --> this line is added
data = b64decode(encrypted)
byte = PBKDF2("lazydog".encode("utf-8"), "salt".encode("utf-8"), 48, 128)
iv = byte[0:16]
key = byte[16:48]
cipher = AES.new(key, AES.MODE_CBC, iv)
text = cipher.decrypt(data) ## error is at this line
text = text[:-text[-1]].decode("utf-8")
return text
As CryptoJs pads data implicitly custom padding has been removed. And removed btoa (which is not required). Then encrypted data is converted to String. suggested by #Topaco in the comments

AES Encryption with C# Decryption with crypto-js

I'm triying to Encrypt string with C# and decrypt it using Angular crypto-js library but it's giving me different output.
I tried different c# aes encryption implementations but crypto-js library can't decrypt the encrypted data in c#. Thank you for any help.
Here is my code
Program.cs
static void Main()
{
var r = EncryptString("exampleString", "examplePassword");
Console.Write(r);
}
public static string EncryptString(string plainText, string passPhrase)
{
if (string.IsNullOrEmpty(plainText))
{
return "";
}
// generate salt
byte[] key, iv;
var salt = new byte[8];
var rng = new RNGCryptoServiceProvider();
rng.GetNonZeroBytes(salt);
DeriveKeyAndIv(passPhrase, salt, out key, out iv);
// encrypt bytes
var encryptedBytes = EncryptStringToBytesAes(plainText, key, iv);
// add salt as first 8 bytes
var encryptedBytesWithSalt = new byte[salt.Length + encryptedBytes.Length + 8];
Buffer.BlockCopy(Encoding.ASCII.GetBytes("Salted__"), 0, encryptedBytesWithSalt, 0, 8);
Buffer.BlockCopy(salt, 0, encryptedBytesWithSalt, 8, salt.Length);
Buffer.BlockCopy(encryptedBytes, 0, encryptedBytesWithSalt, salt.Length + 8, encryptedBytes.Length);
// base64 encode
return Convert.ToBase64String(encryptedBytesWithSalt);
}
private static void DeriveKeyAndIv(string passPhrase, byte[] salt, out byte[] key, out byte[] iv)
{
// generate key and iv
var concatenatedHashes = new List<byte>(48);
var password = Encoding.UTF8.GetBytes(passPhrase);
var currentHash = new byte[0];
var md5 = MD5.Create();
bool enoughBytesForKey = false;
// See http://www.openssl.org/docs/crypto/EVP_BytesToKey.html#KEY_DERIVATION_ALGORITHM
while (!enoughBytesForKey)
{
var preHashLength = currentHash.Length + password.Length + salt.Length;
var preHash = new byte[preHashLength];
Buffer.BlockCopy(currentHash, 0, preHash, 0, currentHash.Length);
Buffer.BlockCopy(password, 0, preHash, currentHash.Length, password.Length);
Buffer.BlockCopy(salt, 0, preHash, currentHash.Length + password.Length, salt.Length);
currentHash = md5.ComputeHash(preHash);
concatenatedHashes.AddRange(currentHash);
if (concatenatedHashes.Count >= 48)
enoughBytesForKey = true;
}
key = new byte[32];
iv = new byte[16];
concatenatedHashes.CopyTo(0, key, 0, 32);
concatenatedHashes.CopyTo(32, iv, 0, 16);
md5.Clear();
}
static byte[] EncryptStringToBytesAes(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");
// Declare the stream used to encrypt to an in memory
// array of bytes.
MemoryStream msEncrypt;
// Declare the RijndaelManaged object
// used to encrypt the data.
RijndaelManaged aesAlg = null;
try
{
// Create a RijndaelManaged object
// with the specified key and IV.
aesAlg = new RijndaelManaged { Mode = CipherMode.CBC, KeySize = 256, BlockSize = 128, Key = key, IV = iv };
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
msEncrypt = new MemoryStream();
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
swEncrypt.Flush();
swEncrypt.Close();
}
}
}
finally
{
// Clear the RijndaelManaged object.
aesAlg?.Clear();
}
// Return the encrypted bytes from the memory stream.
return msEncrypt.ToArray();
}
Simply decrypting it using crypto-js
let CryptoJS = require('crypto-js');
let r = CryptoJS.AES.decrypt('exampleString', 'examplePassword').toString();
The example code is attempting to decrypt the original unencrypted string, which looks to be a mistake perhaps created when trying to simplify the example code for posting the question? Either way the steps required are not too difficult, but the toString() call needs to be replaced.
var data = "U2FsdGVkX1/Zvh/5BnLfUgfbg5ROSD7Aohumr9asPM8="; // Output from C#
let r2 = CryptoJS.enc.Utf8.stringify(CryptoJS.AES.decrypt(data, 'examplePassword'));
console.log(r2);

Crypto++ and node.js combination

I'm having problems with getting data encrypted in c++, sent to Node.js server and decrypt it there. I'm using Crypto++ which works fine if I encrypt it and decrypt it. I tried various modes but nothing seemed to help.
I set key as 32x 'A' and IV as 16x '\0' just for getting consistent data
This is code in c++
AutoSeededRandomPool rand;
// Generate a random key
SecByteBlock key(0x00, AES::MAX_KEYLENGTH);
//rand.GenerateBlock(key, key.size());
memset(key.BytePtr(), 'A', key.size());
// Generate a random IV
byte iv[AES::BLOCKSIZE];
//rand.GenerateBlock(iv, AES::BLOCKSIZE);
memset(iv, 0, AES::BLOCKSIZE);
char plainText[] = "AAAAAAAAAAAAAAA";
int messageLen = (int)strlen(plainText) + 1;
CFB_Mode<AES>::Encryption cfbEncryption(key, key.size(), iv);
cfbEncryption.ProcessData((byte*)plainText, (byte*)plainText, messageLen);
/*CFB_Mode<AES>::Decryption cfbDecryption(key, key.size(), iv);
cfbDecryption.ProcessData((byte*)plainText, (byte*)plainText, messageLen);*/
unsigned int messageLength = messageLen + key.size();
const auto testData = std::vector<byte>(sizeof(unsigned int) + messageLength);
memcpy((void*)&testData[0], reinterpret_cast<void*>(&messageLength), sizeof(unsigned int));
memcpy((void*)&testData[4], (void*)key.BytePtr(), key.size());
memcpy((void*)&testData[4+key.size()], (void*)plainText, messageLen);
testClient.Send(testData);
testClient.Disconnect();
And this is the code in Node.js
socket.on('data', (data) => {
var messageSizeBuffer = data.slice(0, 4);
var messageKeyBuffer = data.slice(4, 36);
var messageDataBuffer = data.slice(36);
var decipher = crypto.createDecipher('AES-256-CFB', messageKeyBuffer)
var dec = Buffer.concat([decipher.update(messageDataBuffer) , decipher.final()]);
console.log(dec.toString());
});
I needed to use createDecipheriv and provide the same Initialization Vector as used in encryption. Beware of hardcoded key and iv, since this is used only for getting consistent data on the other side. Use random generated key and iv.
Code looks like this now
C++
AutoSeededRandomPool rand;
// Generate a random key
SecByteBlock key(0x00, AES::MAX_KEYLENGTH);
//rand.GenerateBlock(key, key.size());
memset(key.BytePtr(), 'A', key.size());
// Generate a random IV
byte iv[AES::BLOCKSIZE];
//rand.GenerateBlock(iv, AES::BLOCKSIZE);
memset(iv, 0, AES::BLOCKSIZE);
char plainText[] = "AAAAAAAAAAAAAAA";
int messageLen = (int)strlen(plainText) + 1;
CFB_Mode<AES>::Encryption cfbEncryption(key, key.size(), iv);
cfbEncryption.ProcessData((byte*)plainText, (byte*)plainText, messageLen);
unsigned int messageLength = messageLen + key.size() + AES::BLOCKSIZE;
const auto testData = std::vector<byte>(sizeof(unsigned int) + messageLength);
auto currentIndex = 0;
memcpy((void*)&testData[currentIndex], reinterpret_cast<void*>(&messageLength), sizeof(unsigned int));
currentIndex += sizeof(unsigned int);
memcpy((void*)&testData[currentIndex], (void*)key.BytePtr(), key.size());
currentIndex += key.size();
memcpy((void*)&testData[currentIndex], iv, AES::BLOCKSIZE);
currentIndex += AES::BLOCKSIZE;
memcpy((void*)&testData[currentIndex], (void*)plainText, messageLen);
testClient.Send(testData);
testClient.Disconnect();
Node.js
socket.on('data', (data) => {
var messageSizeBuffer = data.slice(0, 4);
var messageKeyBuffer = data.slice(4, 36);
var messageIvBuffer = data.slice(36, 52);
var messageDataBuffer = data.slice(52);
var decipher = crypto.createDecipheriv('AES-256-CFB', messageKeyBuffer, messageIvBuffer)
var dec = Buffer.concat([decipher.update(messageDataBuffer) , decipher.final()]);
console.log(dec.toString());
});

Converting Java's PBEWithMD5AndDES to JavaScript

I'm trying to replicate the Java code in JavaScript.
below is my Java code:
public static String encrypt(String input)
final byte[] SALT= { (byte) 0x21, (byte) 0x21, (byte) 0xF0, (byte) 0x55, (byte) 0xC3, (byte) 0x9F, (byte) 0x5A, (byte) 0x75 };
final int ITERATION_COUNT = 31;
{
if (input == null)
{
throw new IllegalArgumentException();
}
try
{
KeySpec keySpec = new PBEKeySpec(null, SALT, ITERATION_COUNT);
AlgorithmParameterSpec paramSpec = new PBEParameterSpec(SALT, ITERATION_COUNT);
SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES").generateSecret(keySpec);
Cipher ecipher = Cipher.getInstance(key.getAlgorithm());
ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
byte[] enc = ecipher.doFinal(input.getBytes());
String res = new String(Base64.encodeBase64(enc));
// escapes for url
res = res.replace('+', '-').replace('/', '_').replace("%", "%25").replace("\n", "%0A");
LOGGER.info("String Encrypted Successfully");
return res;
}
catch (Exception e)
{
LOGGER.error("encrypt Exception: "+e.getMessage());
}
return "";
}
and the JavaScript code, so far hammed up is below:
var encrypt = function(){
var iterations = 31;
var key = CryptoJS.MD5("PBEWithMD5AndDES");
var salt = CryptoJS.enc.Hex.parse('0021002100f0005500C3009F005A0075');
var options = {
mode: CryptoJS.mode.CBC,
iv: salt
};
var hashedPassword = CryptoJS.MD5($scope.data.webPassword);
var encryptedPassword = CryptoJS.DES.encrypt(hashedPassword, key,options).toString();
var result = encryptedPassword.toString(CryptoJS.enc.Base64);
}
but with both the encryption the encoded string I'm getting is different.
PBEwithMD5andDES is obsolete technology and should not be used nowadays. This answer is only provided for demonstration purposes.
PBEwithMD5andDES is defined in PKCS#5 v1.5 which is nothing more than deriving key+IV using PBKDF1 (with MD5) and encrypting with DES.
var password = CryptoJS.enc.Utf8.parse("test");
var salt = CryptoJS.enc.Hex.parse("2121F055C39F5A75");
var iterations = 31;
// PBE according to PKCS#5 v1.5 (in other words: PBKDF1)
var md5 = CryptoJS.algo.MD5.create();
md5.update(password);
md5.update(salt);
var result = md5.finalize();
md5.reset();
for(var i = 1; i < iterations; i++) {
md5.update(result);
result = md5.finalize();
md5.reset();
}
// splitting key and IV
var key = CryptoJS.lib.WordArray.create(result.words.slice(0, 2));
var iv = CryptoJS.lib.WordArray.create(result.words.slice(2, 4));
var encrypted = CryptoJS.DES.encrypt("test", key, {
iv: iv
});
enchex.innerHTML = encrypted.ciphertext.toString();
encbase64.innerHTML = encrypted.ciphertext.toString(CryptoJS.enc.Base64);
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/tripledes.js"></script>
<script src="https://cdn.rawgit.com/CryptoStore/crypto-js/3.1.2/build/rollups/md5.js"></script>
<div>Hex: <span id="enchex"></span></div>
<div>Base64: <span id="encbase64"></span></div>
Here is a jsFiddle to experiment with and here is the example Java code. Both produce the same result in Hex: aa8101a7d63093c6.
Security considerations:
PBEwithMD5andDES should not be used and there are better alternatives like PBEWithHmacSHA256AndAES_128 which require a slightly different approach.
The number of iterations must be large (a thousand to a million) in order to make it hard to brute-force the password. DES only provides 56 bits of security, so it is even possible to brute-force the key directly with today's means.
The salt must be randomly generated in order to achieve semantic security. The salt itself doesn't need to be secret. Since it has a known length it can be simply prepended to the ciphertext and sliced off before decryption.

Categories

Resources