admin管理员组文章数量:1193314
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 /
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!
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!
Share Improve this question asked Dec 19, 2017 at 16:22 usseliteusselite 8161 gold badge8 silver badges25 bronze badges 4 |3 Answers
Reset to default 20The 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("");
}
Going into 2023, some modifications to @usselite's answer are required to make it work.
The biggest change is that the default encryption method in cryptojs has changed from SHA1 to SHA256, while the default in c# is still SHA1. Therefore, the CreateKey method portion of the questioner's C# code has gone through this change below before it can be used:
public static byte[] CreateKey(string password, byte[] salt)
{
int iterations = 1000;
using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iterations, HashAlgorithmName.SHA256))
return rfc2898DeriveBytes.GetBytes(32);
}
As you can see, I added this line of code:HashAlgorithmName.SHA256 to specify the encryption method.
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.
本文标签: Compatible AES encryption and decryption for C and javascriptStack Overflow
版权声明:本文标题:Compatible AES encryption and decryption for C# and javascript - Stack Overflow 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.betaflare.com/web/1738484061a2089315.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
A69ACC405144259479265AC3B0CA93F3
. Adjust the encodings to match. – zaph Commented Dec 19, 2017 at 20:21