Convert AES/ECB/NoPadding in java to Nodejs code - javascript

I want the below java code to be converted into nodejs
JAVA
SecretKeySpec skeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
byte[] buf = data.getBytes("UTF-8");
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
baos.write(buf);
int len = 16 - (baos.size() % 16);
baos.write(new byte[len]);
buf = baos.toByteArray();
}
buf = cipher.doFinal(buf);
final String data2 = Base64.encodeBase64String(buf);
I have done it using crypto but due to the padding issue i am not able to execute cipher.final. Any ideas to resolve this issue?
NodeJS
const crypto = require('crypto');
var keybase64 = Buffer.from(app_secret, 'utf8');
var data1 = Buffer.from(data, 'utf8');
let cipher = crypto.createCipheriv('aes-256-ecb', app_secret, '');
cipher.setAutoPadding(false);
let result = cipher.update(data1,'utf8','base64');
result += cipher.final('base64')
console.log('result', result)

Related

C# .NET AES interoperability with JavaScript

I've been trying to encrypt and decrypt strings in by AES and C# in an interoperable way. My client application is a Node server that communicates with vendor's API is in dot NET.
The vendor uses these methods for encrypting and decrypting the strings:
public static string Encrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] array;
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream())
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
{
streamWriter.Write(data);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
public static string Decrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] buffer = Convert.FromBase64String(data);
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream(buffer))
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
I tried for example crypto-js for decrypting the strings, but I cannot make it work:
const encryptedText = CryptoJS.enc.Base64.parse(base64Value)
const encrypted2 = encryptedText.toString(CryptoJS.enc.Base64);
const decrypt2 = CryptoJS.AES.decrypt(encrypted2, key, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
});
console.log(decrypt2.toString(CryptoJS.enc.Utf8)) // (also tried various other encodings, Utf16, Utf16LE and others)
The following Node.js code should work correctly with your .NET code.
We're using the algorithm aes-256-cbc to match the mode used in the C# example.
const crypto = require("crypto");
const Algorithm = "aes-256-cbc";
function encrypt(plainText, key, iv, outputEncoding = "base64") {
const cipher = crypto.createCipheriv(Algorithm, key, iv);
const output = Buffer.concat([cipher.update(plainText), cipher.final()]).toString(outputEncoding);
return output.replace('+', '-').replace('/', '_').replace('=', '');
}
function decrypt(cipherText, key, iv, outputEncoding = "utf8") {
cipherText = Buffer.from(cipherText, "base64");
const cipher = crypto.createDecipheriv(Algorithm, key, iv);
return Buffer.concat([cipher.update(cipherText), cipher.final()]).toString(outputEncoding);
}
const KEY = 'KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m';
const IV = KEY.slice(0,16);
// Taking the output from our C# sample...
const encrypted = 'SORoNS48u0KniiANU3Y9Mw==';
console.log("Encrypted (base64):", encrypted);
const decrypted = decrypt(encrypted, KEY, IV)
console.log("Decrypted:", decrypted);
The equivalent C# code is below:
using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;
public class Program
{
public static void Main()
{
var str = Encrypt("test", "KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m");
Console.WriteLine("Encrypted: " + str);
Console.WriteLine("Decrypted: " + Decrypt(str, "KFmnMAPzP!g#6Dy5HD?JSgYC9obE&m#m"));
}
public static string Encrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] array;
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream())
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
{
using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
{
streamWriter.Write(data);
}
array = memoryStream.ToArray();
}
}
}
return Convert.ToBase64String(array);
}
public static string Decrypt(string data, string key)
{
string IV = key.Substring(0, 16);
byte[] iv = Encoding.UTF8.GetBytes(IV);
byte[] buffer = Convert.FromBase64String(data);
using(Aes aes = Aes.Create())
{
aes.Key = Encoding.UTF8.GetBytes(key);
aes.IV = iv;
ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
using(MemoryStream memoryStream = new MemoryStream(buffer))
{
using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
{
using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
}
}
The output for the C# code is:
Encrypted: SORoNS48u0KniiANU3Y9Mw==
Decrypted: test
The Node.js code then decrypts this (using the same key and IV):
Encrypted (base64): SORoNS48u0KniiANU3Y9Mw=
Decrypted: test
Since you are using NodeJS, it makes sense to use the crypto module of NodeJS instead of CryptoJS (see the other answer). CryptoJS is of course also possible. Then the following must be considered:
In the JavaScript code, CBC mode has to be used, and key and IV must be passed as WordArray to CryptoJS.AES.decrypt(). The ciphertext can be passed Base64 encoded, CryptoJS implicitly converts this to a CipherParams object.
CryptoJS applies CBC and PKCS#7 padding by default, so these do not need to be explicitly specified (but may of course).
The ciphertext in the following example was generated with the C# code and can be decrypted with the following CryptoJS code:
const ciphertext = 'yKiV9TBw3eNt2QvK1kdXaw==';
const keyStr = "01234567890123456789012345678901"; // 32 bytes -> AES-256
const key = CryptoJS.enc.Utf8.parse(keyStr);
const IV = CryptoJS.enc.Utf8.parse(keyStr.substr(0, 16));
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv: IV}); // apply default: CBC and PKCS#7 padding
console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Hello world!
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>
Regarding security:
It's insecure to use the key (or a part of it) as IV. Instead, a random IV should be generated for each encryption. This is not secret and is passed together with the ciphertext, in general concatenated.
Also the generation of the key from a text reduces security (even with correct size the value range is reduced). More secure would be to use a reliable key derivation function like PBKDF2 if a text/passphrase is involved.

How to encrypt AES in C# with CryptoJS [duplicate]

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);

AES GCM encrypt in nodejs and decrypt in browser?

I am trying to encrypt a piece of string in nodejs and need to decrypt that in front end javascript. In nodejs I was using the crypto library and in front end using web crypto.
Facing some error while decrypting in the front end.
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'passwordpasswordpasswordpassword';
let text = 'Hello World!';
let cipher = crypto.createCipheriv(algorithm, password, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: tag,
iv: iv
}
Front End
let cipherObj; //GET FROM BE
let aesKey = await crypto.subtle.importKey(
"raw",
Buffer.from('passwordpasswordpasswordpassword'), //password
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: Buffer.from(cipherObj.iv),
tagLength: 128
},
aesKey,
Buffer.concat([Buffer.from(cipherObj.content), Buffer.from(cipherObj.tag)])
);
Decrypt function in the front-end is throwing an error.
ERROR Error: Uncaught (in promise): OperationError
at resolvePromise (zone.js:814)
at zone.js:724
at rejected (main.js:231)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:388)
at Object.onInvoke (core.js:3820)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (zone.js:387)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.run (zone.js:138)
at zone.js:872
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421)
at Object.onInvokeTask (core.js:3811)
at ZoneDelegate.push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420)
at Zone.push../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188)
at drainMicroTaskQueue (zone.js:595)
PS: I am using Angular 7 in front end
I was able to get this working with some changes:
I use SHA-256 to hash the password, so it can be any length. (The OP requires a 32-byte string.)
I added additional helper functions from another answer for converting buffers to/from hex.
I print the output cipherObj in JSON. This is your encrypted message payload.
Helpers - NodeJS and Browser
// look up tables
var to_hex_array = [];
var to_byte_map = {};
for (var ord=0; ord<=0xff; ord++) {
var s = ord.toString(16);
if (s.length < 2) {
s = "0" + s;
}
to_hex_array.push(s);
to_byte_map[s] = ord;
}
// converter using lookups
function bufferToHex2(buffer) {
var hex_array = [];
//(new Uint8Array(buffer)).forEach((v) => { hex_array.push(to_hex_array[v]) });
for (var i=0; i<buffer.length; i++) {
hex_array.push(to_hex_array[buffer[i]]);
}
return hex_array.join('')
}
// reverse conversion using lookups
function hexToBuffer(s) {
var length2 = s.length;
if ((length2 % 2) != 0) {
throw "hex string must have length a multiple of 2";
}
var length = length2 / 2;
var result = new Uint8Array(length);
for (var i=0; i<length; i++) {
var i2 = i * 2;
var b = s.substring(i2, i2 + 2);
result[i] = to_byte_map[b];
}
return result;
}
The backend uses hex2buffer and the frontend uses buffer2hex, but you can just include that code in both.
So the backend code is the above helpers plus:
NodeJS
const crypto = require('crypto');
const iv = crypto.randomBytes(12);
const algorithm = 'aes-256-gcm';
let password = 'This is my password';
let key = crypto.createHash("sha256").update(password).digest();
let text = 'This is my test string, with 🎉 emoji in it!';
let cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update(text, 'utf8', 'hex');
encrypted += cipher.final('hex');
var tag = cipher.getAuthTag();
let cipherObj = {
content: encrypted,
tag: bufferToHex2(tag),
iv: bufferToHex2(iv)
}
console.log(JSON.stringify(cipherObj));
The output changes every run due to random IV, but for example:
{"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
So then the example frontend code is the above helper functions, plus:
Browser
let cipherObj; //GET FROM BACKEND
// For example:
cipherObj = {"content":"22da4796365ac1466f40022dd4510266fa3e24900b816f365e308cf06c95237783d1043c7deeb45d00381f8ff9ed","tag":"b7007905163b2d4890c9452c8edc1821","iv":"eb4758787164f95ac22ee50d"}
let password = 'This is my password';
let enc = new TextEncoder();
let key = await window.crypto.subtle.digest({ name:"SHA-256" }, enc.encode(password));
let aesKey = await crypto.subtle.importKey(
"raw",
key,
"AES-GCM",
true,
["decrypt"]
);
let decrypted = await window.crypto.subtle.decrypt(
{
name: "AES-GCM",
iv: hexToBuffer(cipherObj.iv),
tagLength: 128
},
aesKey,
hexToBuffer(cipherObj.content + cipherObj.tag)
);
let dec = new TextDecoder();
console.log(dec.decode(decrypted));
// This is my test string, with 🎉 emoji in it!
Some crypto reference examples.

Compatible AES encryption and decryption for C# and javascript

I am trying to write two classes in C# and Javascript which I can use throughout my project to encrypt or decrypt data using AES when data is exchanged.
Using AES I am embedding the Salt (32 bytes) and IV (16 bytes) in the encrypted result, this works fine for both classes individually when testing. Adding the Salt and IV to the mix doesn't bring up a lot of references to get this working between the two platforms.
For C# I am using the standard System.Security.Crypthography.AES
private static readonly int iterations = 1000;
public static string Encrypt(string input, string password)
{
byte[] encrypted;
byte[] IV;
byte[] Salt = GetSalt();
byte[] Key = CreateKey(password, Salt);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.Padding = PaddingMode.PKCS7;
aesAlg.Mode = CipherMode.CBC;
aesAlg.GenerateIV();
IV = aesAlg.IV;
var encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (var swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(input);
}
encrypted = msEncrypt.ToArray();
}
}
}
byte[] combinedIvSaltCt = new byte[Salt.Length + IV.Length + encrypted.Length];
Array.Copy(Salt, 0, combinedIvSaltCt, 0, Salt.Length);
Array.Copy(IV, 0, combinedIvSaltCt, Salt.Length, IV.Length);
Array.Copy(encrypted, 0, combinedIvSaltCt, Salt.Length + IV.Length, encrypted.Length);
return Convert.ToBase64String(combinedIvSaltCt.ToArray());
}
public static string Decrypt(string input, string password)
{
byte[] inputAsByteArray;
string plaintext = null;
try
{
inputAsByteArray = Convert.FromBase64String(input);
byte[] Salt = new byte[32];
byte[] IV = new byte[16];
byte[] Encoded = new byte[inputAsByteArray.Length - Salt.Length - IV.Length];
Array.Copy(inputAsByteArray, 0, Salt, 0, Salt.Length);
Array.Copy(inputAsByteArray, Salt.Length, IV, 0, IV.Length);
Array.Copy(inputAsByteArray, Salt.Length + IV.Length, Encoded, 0, Encoded.Length);
byte[] Key = CreateKey(password, Salt);
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = Key;
aesAlg.IV = IV;
aesAlg.Mode = CipherMode.CBC;
aesAlg.Padding = PaddingMode.PKCS7;
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
using (var msDecrypt = new MemoryStream(Encoded))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
using (var srDecrypt = new StreamReader(csDecrypt))
{
plaintext = srDecrypt.ReadToEnd();
}
}
}
}
return plaintext;
}
catch (Exception e)
{
return null;
}
}
public static byte[] CreateKey(string password, byte[] salt)
{
using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iterations))
return rfc2898DeriveBytes.GetBytes(32);
}
private static byte[] GetSalt()
{
var salt = new byte[32];
using (var random = new RNGCryptoServiceProvider())
{
random.GetNonZeroBytes(salt);
}
return salt;
}
For the Javascript solution I am using CryptoJS, based upon this reference http://www.adonespitogo.com/articles/encrypting-data-with-cryptojs-aes/
var keySize = 256;
var ivSize = 128;
var saltSize = 256;
var iterations = 1000;
var message = "Hello World";
var password = "Secret Password";
function encrypt (msg, pass) {
var salt = CryptoJS.lib.WordArray.random(saltSize/8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var iv = CryptoJS.lib.WordArray.random(ivSize/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
// salt, iv will be hex 32 in length
// append them to the ciphertext for use in decryption
var transitmessage = salt + iv + encrypted;
return transitmessage.toString();
}
function decrypt (transitmessage, pass) {
var salt = CryptoJS.enc.Hex.parse(transitmessage.substr(0, 64));
var iv = CryptoJS.enc.Hex.parse(transitmessage.substr(64, 32));
var encrypted = transitmessage.substring(96);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return decrypted.toString(CryptoJS.enc.Utf8);
}
Used password: Secret Password
C# outcome:
r7Oi1vMXZ5mYJay8i+slbJZEiT3CxV/1zOYntbZIsS5RuasABJKQQQVvAe50U1deIIqyQiwzQWYelMJ48WWpMQ==
Javascript outcome: 72ff8e7b653efbe3101d2c4ca7d7fe1af06652b907a90281aafa5ae09b45c9af091571b08d3d39cbad129939488319b2pprMQFFEJZR5JlrDsMqT8w==
The outcome should be Hello World
Both solutions work well within their own environment, however the C# or Javascript hashes can't be exchanged, they will not decrypt. My guess is that the character encoding has something to do with it, hence why the base64 sizes differ so much. Does anyone have a idea to get this working together? Thanks!
The error was in the Javascript code, the first part was Hex while the end was the encrypted result in Base64.
The following Javascript code makes the AES results interchangeable with the C# solution provided above. I had some difficulties making sure that all the results where properly encoded and decoded in Hex, so there are some new functions.
var keySize = 256;
var ivSize = 128;
var saltSize = 256;
var iterations = 1000;
var message = "Does this work?";
var password = "Secret Password";
function encrypt (msg, pass) {
var salt = CryptoJS.lib.WordArray.random(saltSize/8);
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var iv = CryptoJS.lib.WordArray.random(ivSize/8);
var encrypted = CryptoJS.AES.encrypt(msg, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
});
var encryptedHex = base64ToHex(encrypted.toString());
var base64result = hexToBase64(salt + iv + encryptedHex);
return base64result;
}
function decrypt (transitmessage, pass) {
var hexResult = base64ToHex(transitmessage)
var salt = CryptoJS.enc.Hex.parse(hexResult.substr(0, 64));
var iv = CryptoJS.enc.Hex.parse(hexResult.substr(64, 32));
var encrypted = hexToBase64(hexResult.substring(96));
var key = CryptoJS.PBKDF2(pass, salt, {
keySize: keySize/32,
iterations: iterations
});
var decrypted = CryptoJS.AES.decrypt(encrypted, key, {
iv: iv,
padding: CryptoJS.pad.Pkcs7,
mode: CryptoJS.mode.CBC
})
return decrypted.toString(CryptoJS.enc.Utf8);
}
function hexToBase64(str) {
return btoa(String.fromCharCode.apply(null,
str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
);
}
function base64ToHex(str) {
for (var i = 0, bin = atob(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1) tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join("");
}
You are using Cipher Block Chaining (CBC) mode with a random IV (correct way).
Indirectly the IV will affect every plaintext block before the encryption.
Therefore comparing the content of the encrypted data will not help you here.
The length of the encrypted data is also different. I assume this is because the CryptoJS.lib.WordArray will be printed in hex.
Therefore You are getting seed and IV in hex encoding and the encrypted message in base64 encoding.
On C# side there is only one base64 encoded result containing everything.
In general plain CBC mode is no longer state-of-the-art encryption (e.g. for TLS1.3 alls ciphers with AES-CBC has been removed). Under certain conditions it may allows certain attacks (e.g. padding oracle attack). Therefore I would recommend to use an authenticating cipher mode like GCM mode instead.

How to emulate Javascript crypto AES-256-CBC decipher in Java

I have the following Javascript code in a web page:
var decrypt = function (text, password){
var decipher = crypto.createDecipher('aes-256-cbc',password);
var dec = decipher.update(text,'hex','utf8');
dec += decipher.final('utf8');
return dec;
}
, and I'm trying to reproduce it using Java, using the following:
static MessageDigest MD5 = null;
static {
try {
MD5 = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
public static String decrypt(String cipherText, String password)
throws GeneralSecurityException, UnsupportedEncodingException {
byte[] passwordBytes = hexStringToByteArray(password);
byte[] keyBytes = MD5.digest(passwordBytes);
byte[] keyAndPassword = new byte[keyBytes.length + passwordBytes.length];
System.arraycopy(keyBytes, 0, keyAndPassword, 0, keyBytes.length);
System.arraycopy(passwordBytes, 0, keyAndPassword, keyBytes.length, passwordBytes.length);
byte[] ivBytes = MD5.digest(keyAndPassword);
SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
IvParameterSpec iv = new IvParameterSpec(ivBytes);
byte[] encrypted = hexStringToByteArray(cipherText);
Cipher aesCBC = Cipher.getInstance("AES/CBC/PKCS5Padding");
aesCBC.init(Cipher.DECRYPT_MODE, key, iv);
byte[] decryptedData = aesCBC.doFinal(encrypted);
return new String(decryptedData, StandardCharsets.UTF_8);
}
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16));
}
return data;
}
which pieces bits from:
Encrypt with Node.js Crypto module and decrypt with Java (in Android app)
CryptoJS AES encryption and Java AES decryption
, but I get "javax.crypto.BadPaddingException: Given final block not properly padded", on parameters which the JS function decodes correctly.
Note that Given final block not properly padded does not answer this question- as it is obvious that this is a padding problem but the solution is to replicate whatever the JS crypto lib does, which is not well documented.

Categories