Reproduce AES decryption method from C# in JavaScript - javascript

I am trying to reproduce the following C# decryption method in JavaScript.
This method is used to decrypt short strings: names, addresses, email addresses, etc.
It feels tantalisingly close, because the strings I have been able to "successfully" decrypt seem partially decrypted.
For instance, some of the emails look like this: x"R�Îd¹1gtWÈ2)web#example.com
CSharp
public static readonly byte[] INIT_VECTOR = { 0x00, 0x00, ... };
public static string Decrypt(string cipherText) {
string EncryptionKey = "Some Encryption Key";
byte[] cipherBytes = Convert.FromBase64String(cipherText);
using (Aes encryptor = Aes.Create())
{
​
Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, INIT_VECTOR);
encryptor.Key = pdb.GetBytes(32);
encryptor.IV = pdb.GetBytes(16);
using (MemoryStream ms = new MemoryStream())
{
using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(cipherBytes, 0, cipherBytes.Length);
cs.Close();
}
cipherText = Encoding.Unicode.GetString(ms.ToArray());
}
}
return cipherText;
}
JavaScript
import atob from 'atob';
import forge from 'node-forge';
const InitVector = [0x00, ...];
const EncryptionKey = 'Some Encryption Key';
const iv = Buffer.from(InitVector).toString();
const convertBase64StringToUint8Array = input => {
const data = atob(input);
const array = Uint8Array.from(data, b => b.charCodeAt(0));
return array;
};
const decrypt = cipher => {
const cipherArray = convertBase64StringToUint8Array(cipher);
const key = forge.pkcs5.pbkdf2(EncryptionKey, iv, 1000, 32);
const decipher = forge.cipher.createDecipher('AES-CBC', key);
decipher.start({ iv });
decipher.update(forge.util.createBuffer(cipherArray, 'raw'));
const result = decipher.finish();
if (result) {
return decipher.output.data;
} else {
return false;
}
};

Thanks to kelalaka I managed to figure this out!
This was the code I ended up with.
import atob from 'atob';
import forge from 'node-forge';
const InitVector = [0x00, ...];
const EncryptionKey = 'Some Encryption Key';
const initKey = Buffer.from(InitVector).toString(); // Changed this to `initKey`
const convertBase64StringToUint8Array = input => {
const data = atob(input);
const array = Uint8Array.from(data, b => b.charCodeAt(0));
return array;
};
const decrypt = cipher => {
const cipherArray = convertBase64StringToUint8Array(cipher);
const key = forge.pkcs5.pbkdf2(EncryptionKey, iv, 1000, 32);
/**
* Added the following
* Note the key size = 48
* This was due to the fact that the C# dictated that
* the IV was 16 bytes, starting at the end of the key.
*/
const keyAndIV = forge.pkcs5.pbkdf2(encryptionKey, initKey, 1000, 32 + 16);
/**
* Therefore, we cut the iv from the new string
*/
const iv = keyAndIV.slice(32, 32 + 16); // 16 bytes
const decipher = forge.cipher.createDecipher(
'AES-CBC',
forge.util.createBuffer(key)
);
decipher.start({ iv });
decipher.update(forge.util.createBuffer(cipherArray, 'raw'));
const result = decipher.finish();
if (result) {
return decipher.output.data;
} else {
return false;
}
};

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

Why AEC encryption in .NET yields different result than JavaScript?

I'm going crazy about this one. Spend whole day and still can't understand what is going on. I'm using AES256CBC encryption both in .Net and JavaScript. For some reason I got different results, despite that I'm using same key an iv. My codes are:
JavaScript:
function convertStringToArrayBuffer(str) {
var length = str.length;
var bytes = new Uint8Array(length);
for(var i = 0; i < length; i++) {
bytes[i] = str.charCodeAt(i);
}
return bytes;
}
var keyB64 ="sy/d1Ddy/9K3p8x6pWMq2P8Qw2ftUjkkrAA7xFC7aK8=";
var viB64 = "t8eI2F+QmlUBWZJVIlTX6Q==";
var dataToEnc = "Test123!"
let dataInBytes = convertStringToArrayBuffer(dataToEnc);
let key = window.atob(keyB64);
let iv = window.atob(viB64);
console.log(key);
console.log(iv);
window.crypto.subtle.importKey("raw", convertStringToArrayBuffer(key).buffer, {name: "AES-CBC", length: 256}, false, ["encrypt"]).then(function(key){
console.log(key);
window.crypto.subtle.encrypt({name: "AES-CBC", iv: convertStringToArrayBuffer(iv).buffer}, key, dataInBytes.buffer).then(function(encrypted){
console.log(encrypted);
});
});
This one produces
.Net:
public static void Test()
{
var dataToEnc = "Test123!";
var keyB64 = "sy/d1Ddy/9K3p8x6pWMq2P8Qw2ftUjkkrAA7xFC7aK8=";
var viB64 = "t8eI2F+QmlUBWZJVIlTX6Q==";
var key = Convert.FromBase64String(keyB64);
var iv = Convert.FromBase64String(viB64);
var data = Encoding.UTF8.GetBytes(dataToEnc);
byte[] encrypted = null;
using (Aes aesAlg = Aes.Create())
{
aesAlg.Key = key;
aesAlg.IV = iv;
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
using (MemoryStream msEncrypt = new MemoryStream())
{
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
swEncrypt.Write(data);
}
encrypted = msEncrypt.ToArray();
}
}
}
}
This one produces
I belive it is something trivial, yet I can't find this. I appreciate any hint here.
If anyone else is having this issue #Topaco's comment is right way to go, I'm pasting it below
The bug is in the C# code. You have to use swEncrypt.Write(dataToEnc) instead of swEncrypt.Write(data). The overload you are currently using implicitly executes data.ToString()

Can't verify signatuire in 2nd program using nodejs crypto

I have two node-js programs. I want to sign a number data in the first program (node1.js) and verify that the in the second program (node2.js). But it is not happening. I think it may be because the way I'm sending the argument in write() is not correct. Please help. Thank You
node1.js
function between(min, max) {
return Math.floor(
Math.random() * (max - min) + min
)
}
var no = between(10, 100);
var crypto = require('crypto');
const node1PrivateKey = "-----BEGIN PRIVATE KEY-----\nMH4CAQAwEAYHKoZIzj0CAQYFK4EEAAMEZzBlAgEBBB4BfZ9QrDb9rRZB+sz5rkcK8VhUAJyJRj6KlOlEVYChQAM+AARylnQB587rYJlx/BvMIdtmLoMfzrCcYzrSMWxSX0tseCpunG03pBnE1mDuIo8lCnxm0kZYC4PUrr9r4f8=\n-----END PRIVATE KEY-----";
//var node1PublicKey = new Buffer("MFIwEAYHKoZIzj0CAQYFK4EEAAMDPgAEGrIPBS4+TSe1rKuuIcGItuYjAgVmIaILaa0MTIcTb7sth05hYtWKwfJA48ZTFQi5iBCH1RqCZyq+nZmB", "hex");
//keypair is correct and generated using other program
const sign = crypto.createSign('SHA256');
sign.write('no');
sign.end();
const signature = sign.sign(node1PrivateKey, 'hex');
module.exports = {
node1_no: no,
node1_signature: signature
}
node2.js
var crypto = require('crypto');
const node1PublicKey = "-----BEGIN PUBLIC KEY-----\nMFIwEAYHKoZIzj0CAQYFK4EEAAMDPgAEGrIPBS4+TSe1rKuuIcGItuYjAgVmIaILaa0MTIcTb7sth05hYtWKwfJA48ZTFQi5iBCH1RqCZyq+nZmB\n-----END PUBLIC KEY-----";
var node1 = require('./node1.js');
console.log(node1.node1_no);
console.log(node1.node1_signature);
const verify = crypto.createVerify('SHA256');
verify.write('node1.node1_no');
verify.end();
console.log(verify.verify(node1PublicKey, node1.node1_signature, 'hex'));
It returns FALSE.
The expected result is TRUE. when I use same variable name ('no') it returns TRUE.
Finally solved. The problem was write() function accepts only string. But I were sending integer value.
Now, I converted integer to string ans pass the value to write() function and it worked.
node1.js
function between(min, max) {
return Math.floor(
Math.random() * (max - min) + min
)
}
var no = between(10, 100);
var no_str = veh.toString();
var crypto = require('crypto');
const node1PrivateKey = "-----BEGIN PRIVATE KEY-----\nMH4CAQAwEAYHKoZIzj0CAQYFK4EEAAMEZzBlAgEBBB4BfZ9QrDb9rRZB+sz5rkcK8VhUAJyJRj6KlOlEVYChQAM+AARylnQB587rYJlx/BvMIdtmLoMfzrCcYzrSMWxSX0tseCpunG03pBnE1mDuIo8lCnxm0kZYC4PUrr9r4f8=\n-----END PRIVATE KEY-----";
//var node1PublicKey = new Buffer("MFIwEAYHKoZIzj0CAQYFK4EEAAMDPgAEGrIPBS4+TSe1rKuuIcGItuYjAgVmIaILaa0MTIcTb7sth05hYtWKwfJA48ZTFQi5iBCH1RqCZyq+nZmB", "hex");
const sign = crypto.createSign('SHA256');
sign.write('no_str');
sign.end();
const signature = sign.sign(node1PrivateKey, 'hex');
module.exports = {
node1_no: no,
node1_signature: signature
}
second.js
var crypto = require('crypto');
const node1PublicKey = "-----BEGIN PUBLIC KEY-----\nMFIwEAYHKoZIzj0CAQYFK4EEAAMDPgAEGrIPBS4+TSe1rKuuIcGItuYjAgVmIaILaa0MTIcTb7sth05hYtWKwfJA48ZTFQi5iBCH1RqCZyq+nZmB\n-----END PUBLIC KEY-----";
var node1 = require('./node1.js');
var no_string = node1.node1_veh
const verify = crypto.createVerify('SHA256');
verify.write('no_string');
verify.end();
console.log(verify.verify(node1PublicKey, node1.node1_signature, 'hex'));

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

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