代码之家  ›  专栏  ›  技术社区  ›  0xC0DED00D

JS中的AES加密相当于C#

  •  3
  • 0xC0DED00D  · 技术社区  · 6 年前

    我需要使用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”保持为纯文本,将“example key”保持为键,我得到以下结果-

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

    JS加密字符串的长度始终比C字符串短。我做错什么了吗?我只需要将C代码复制到JS代码中。

    更新:
    之后我的当前代码 Zergatul's answer 是这个吗?

    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将明文转换为字节,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代码中生成的加密字符串的长度(不能直接访问写入流),所以仍然停留在这里。

    2 回复  |  直到 6 年前
        1
  •  2
  •   Zergatul    6 年前

    下面是如何在 C# 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=

        2
  •  2
  •   0xC0DED00D    6 年前

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

    说明:

    中的C代码 Zergatul's answer (多亏他/她)使用ASCII将明文转换成字节,而我的C代码使用的是Unicode。Unicode正在为结果字节数组中的每个字符分配额外的字节,这不影响密钥和IV字节的生成,但会影响结果,因为加密字符串的长度取决于从纯文本生成的字节的长度。
    如下所示,每个字节分别使用“example string”和“example key”作为明文和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  
    

    因此,我只需要增加纯文本的长度,使其使用Unicode等效字节生成(抱歉,不熟悉这个术语)。因为unicode为bytearray中的每个字符分配了2个空间,将第二个空间保持为0,所以我基本上在纯文本的字符中创建了间隙,并使用 addExtraByteToChars() 功能。它让一切都不同。

    当然,这是一个解决方案,但开始为我的场景工作。我想这可能对其他人有用,也可能对其他人不有用,因此可以分享研究结果。如果有人能建议更好地执行 添加外字节数字符() 函数(可能是这个转换的一个术语,而不是从ASCII转换成Unicode,或者是一种更好、高效且不黑客的方法),请推荐它。