[英]Encryption in C# and Decryption in javascript with AES Algorithm
[英]Compatible AES encryption and decryption for C# and javascript
我正在嘗試用C#和Javascript編寫兩個類,我可以在整個項目中使用它來在交換數據時使用AES加密或解密數據。
使用AES我在加密結果中嵌入Salt(32字節)和IV(16字節),這在測試時單獨適用於兩個類。 將Salt和IV添加到混合中並沒有引出很多參考,以使這兩個平台之間的工作。
對於C#,我使用標准的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;
}
對於Javascript解決方案,我使用CryptoJS,基於此參考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);
}
使用密碼: Secret Password
C#結果: r7Oi1vMXZ5mYJay8i+slbJZEiT3CxV/1zOYntbZIsS5RuasABJKQQQVvAe50U1deIIqyQiwzQWYelMJ48WWpMQ==
Javascript結果: 72ff8e7b653efbe3101d2c4ca7d7fe1af06652b907a90281aafa5ae09b45c9af091571b08d3d39cbad129939488319b2pprMQFFEJZR5JlrDsMqT8w==
結果應該是Hello World
這兩種解決方案都適用於自己的環境,但是C#或Javascript哈希值無法交換,它們不會解密。 我的猜測是字符編碼與它有關,因此base64大小差異如此之大。 有沒有人有想法讓這個合作? 謝謝!
錯誤發生在Javascript代碼中,第一部分是Hex,而結尾是Base64中的加密結果。
以下Javascript代碼使AES結果可與上面提供的C#解決方案互換。 我遇到了一些困難,確保所有結果在Hex中正確編碼和解碼,因此有一些新功能。
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("");
}
您使用密碼塊鏈接(CBC)模式與隨機IV(正確的方式)。
在加密之前,IV將間接地影響每個明文塊。 因此,比較加密數據的內容對您沒有幫助。
加密數據的長度也不同。 我假設這是因為CryptoJS.lib.WordArray
將以十六進制打印。 因此,您將獲得十六進制編碼的seed
和IV
以及base64編碼的加密消息。
在C#端,只有一個包含所有內容的base64編碼結果。
通常,普通CBC模式不再是最先進的加密技術(例如,對於TLS1.3,已刪除了具有AES-CBC的所有密碼)。 在某些條件下,它可能允許某些攻擊(例如填充oracle攻擊)。 因此,我建議使用像GCM模式這樣的身份驗證密碼模式。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.