簡體   English   中英

JS中的AES加密相當於C#

[英]AES encryption in JS equivalent of C#

我需要使用AES加密來加密字符串。 這種加密是在C#中發生的,但它需要轉換為JavaScript(將在瀏覽器上運行)。

C#中用於加密的當前代碼如下 -

public static string EncryptString(string plainText, string encryptionKey)
{
    byte[] clearBytes = Encoding.Unicode.GetBytes(plainText);

    using (Aes encryptor = Aes.Create())

    {
        Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(encryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 });
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))

            {
                cs.Write(clearBytes, 0, clearBytes.Length);
                cs.Close();
            }
            plainText = Convert.ToBase64String(ms.ToArray());
        }
    }
    return plainText;
}

我曾嘗試使用CryptoJS來復制相同的功能,但它沒有給我相同的加密base64字符串。 這是我的CryptoJS代碼 -

function encryptString(encryptString, secretKey) {
    var iv = CryptoJS.enc.Hex.parse('Ivan Medvedev');
    var key = CryptoJS.PBKDF2(secretKey, iv, { keySize: 256 / 32, iterations: 500 });

    var encrypted = CryptoJS.AES.encrypt(encryptString, key,{iv:iv);
    return encrypted;
}

必須將加密的字符串發送到能夠解密它的服務器。 服務器能夠解密從C#代碼生成的加密字符串,但不能解密從JS代碼生成的加密字符串。 我試圖比較兩個代碼生成的加密字符串,發現C#代碼生成更長的加密字符串。 例如,將'Example String'保持為plainText並將'Example Key'作為鍵,我得到以下結果 -

C# - eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=
JS - 9ex5i2g+8iUCwdwN92SF+A==

JS加密字符串的長度始終短於C#。 有什么我做錯了嗎? 我只需要將C#代碼復制到JS代碼中。

更新:
Zergatul回答后我現在的代碼是 -

function encryptString(encryptString, secretKey) {
    var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
    console.log(keyBytes.toString());

    // take first 32 bytes as key (like in C# code)
    var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
    // skip first 32 bytes and take next 16 bytes as IV
    var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);

    console.log(key.toString());
    console.log(iv.toString());

    var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv });
    return encrypted;
}

正如他/她的回答所示,如果C#代碼使用ASCII而不是Unicode將plainText轉換為字節,則C#和JS代碼都會產生精確的結果。 但由於我無法修改解密代碼,因此我必須將代碼轉換為使用Unicode的原始C#代碼。

所以,我試着看看,C#中ASCII和Unicode字節轉換之間的字節數組有什么區別。 這是我發現的 -

ASCII Byte Array: [69,120,97,109,112,108,101,32,83,116, 114, 105, 110, 103]
Unicode Byte Array: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0, 114,0, 105,0, 110,0, 103,0]

因此,C#中的每個字符都有一些額外的字節可用(因此Unicode為每個字符分配的字節數是ASCII的兩倍)。

以下是Unicode和ASCII轉換的區別 -

ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==

Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

因此,由於生成的key和iv在Unicode和ASCII方法中都具有完全相同的字節數組,所以它不應該生成不同的輸出,但不知何故它正在這樣做。 我認為這是因為clearBytes的長度,因為它使用它的長度來寫入CryptoStream。

我試圖看看JS代碼中生成的字節的輸出是什么,並發現它使用需要使用toString()方法轉換為字符串的單詞。

keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7

因為,我無法影響JS代碼中生成的加密字符串的長度(無法直接訪問寫入流),因此仍然困在這里。

以下是如何在C#CryptoJS之間重現相同的密文的CryptoJS

static void Main(string[] args)
{
    byte[] plainText = Encoding.Unicode.GetBytes("Example String"); // this is UTF-16 LE
    string cipherText;
    using (Aes encryptor = Aes.Create())
    {
        var pdb = new Rfc2898DeriveBytes("Example Key", Encoding.ASCII.GetBytes("Ivan Medvedev"));
        encryptor.Key = pdb.GetBytes(32);
        encryptor.IV = pdb.GetBytes(16);
        using (MemoryStream ms = new MemoryStream())
        {
            using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write))
            {
                cs.Write(plainText, 0, plainText.Length);
                cs.Close();
            }
            cipherText = Convert.ToBase64String(ms.ToArray());
        }
    }

    Console.WriteLine(cipherText);
}

和JS:

var keyBytes = CryptoJS.PBKDF2('Example Key', 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
// take first 32 bytes as key (like in C# code)
var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
// skip first 32 bytes and take next 16 bytes as IV
var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
// use the same encoding as in C# code, to convert string into bytes
var data = CryptoJS.enc.Utf16LE.parse("Example String");
var encrypted = CryptoJS.AES.encrypt(data, key, { iv: iv });
console.log(encrypted.toString());

兩個代碼都返回: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

TL; DR最終代碼如下所示 -

function encryptString(encryptString, secretKey) {
    encryptString = addExtraByteToChars(encryptString);
    var keyBytes = CryptoJS.PBKDF2(secretKey, 'Ivan Medvedev', { keySize: 48 / 4, iterations: 1000 });
    console.log(keyBytes.toString());
    var key = new CryptoJS.lib.WordArray.init(keyBytes.words, 32);
    var iv = new CryptoJS.lib.WordArray.init(keyBytes.words.splice(32 / 4), 16);
    var encrypted = CryptoJS.AES.encrypt(encryptString, key, { iv: iv, });
    return encrypted;
}

function addExtraByteToChars(str) {
    let strResult = '';
    for (var i = 0; i < str.length; ++i) {
        strResult += str.charAt(i) + String.fromCharCode(0);
    }
    return strResult;
}

說明:

Zergatul的答案中的C#代碼(感謝他/她)使用ASCII將plainText轉換為字節,而我的C#代碼使用Unicode。 Unicode為結果字節數組中的每個字符分配了額外的字節,這不會影響key和iv字節的生成,但會影響結果,因為encryptedString的長度取決於plainText生成的字節長度。
如下面的字節所示,每個字節使用“Example String”和“Example Key”分別作為plainText和secretKey生成 -

ASCII
clearBytes: [69,120,97,109,112,108,101,32,83,116,114,105,110,103,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eQus9GLPKULh9vhRWOJjog==

Unicode:
clearBytes: [69,0,120,0,97,0,109,0,112,0,108,0,101,0,32,0,83,0,116,0,114,0,105,0,110,0,103,0,]
encryptor.Key: [123,213,18,82,141,249,182,218,247,31,246,83,80,77,195,134,230,92,0,125,232,210,135,115,145,193,140,239,228,225,183,13,]
encryptor.IV: [101,74,46,177,46,233,68,252,83,169,211,13,249,61,118,167,]
Result: eAQO+odxOdGlNRB81SHR2XzJhyWtz6XmQDko9HyDe0w=

JS結果也是類似的,這證實了它使用的是ASCII字節轉換 -

keyBytes: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d654a2eb12ee944fc53a9d30df93d76a7
key: 7bd512528df9b6daf71ff653504dc386e65c007de8d2877391c18cefe4e1b70d
iv: 654a2eb12ee944fc53a9d30df93d76a7  

因此,我只需要增加plainText的長度,使其使用Unicode等效字節生成(對不起,不熟悉該術語)。 由於Unicode為byteArray中的每個字符分配了2個空格,將第二個空格保持為0,我基本上在plainText的字符中創建了間隙,並使用addExtraByteToChars()函數填充了ASCII值為0的addExtraByteToChars() 它使一切變得不同。

這肯定是一種解決方法,但開始為我的方案工作。 我想這可能會或可能不會對其他人有用,從而分享調查結果。 如果有人可以建議更好地實現addExtraByteToChars()函數(可能是這個轉換的一個術語而不是ASCII到Unicode或更好,更高效,而不是hacky方式),請提出建議。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM