简体   繁体   English

针对C#和javascript的兼容AES加密和解密

[英]Compatible AES encryption and decryption for C# and javascript

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. 我正在尝试用C#和Javascript编写两个类,我可以在整个项目中使用它来在交换数据时使用AES加密或解密数据。

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. 使用AES我在加密结果中嵌入Salt(32字节)和IV(16字节),这在测试时单独适用于两个类。 Adding the Salt and IV to the mix doesn't bring up a lot of references to get this working between the two platforms. 将Salt和IV添加到混合中并没有引出很多参考,以使这两个平台之间的工作。

For C# I am using the standard System.Security.Crypthography.AES 对于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;
    }

For the Javascript solution I am using CryptoJS, based upon this reference http://www.adonespitogo.com/articles/encrypting-data-with-cryptojs-aes/ 对于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);
}

Used password: Secret Password 使用密码: Secret Password

C# outcome: r7Oi1vMXZ5mYJay8i+slbJZEiT3CxV/1zOYntbZIsS5RuasABJKQQQVvAe50U1deIIqyQiwzQWYelMJ48WWpMQ== C#结果: r7Oi1vMXZ5mYJay8i+slbJZEiT3CxV/1zOYntbZIsS5RuasABJKQQQVvAe50U1deIIqyQiwzQWYelMJ48WWpMQ==

Javascript outcome: 72ff8e7b653efbe3101d2c4ca7d7fe1af06652b907a90281aafa5ae09b45c9af091571b08d3d39cbad129939488319b2pprMQFFEJZR5JlrDsMqT8w== Javascript结果: 72ff8e7b653efbe3101d2c4ca7d7fe1af06652b907a90281aafa5ae09b45c9af091571b08d3d39cbad129939488319b2pprMQFFEJZR5JlrDsMqT8w==

The outcome should be Hello World 结果应该是Hello World

Both solutions work well within their own environment, however the C# or Javascript hashes can't be exchanged, they will not decrypt. 这两种解决方案都适用于自己的环境,但是C#或Javascript哈希值无法交换,它们不会解密。 My guess is that the character encoding has something to do with it, hence why the base64 sizes differ so much. 我的猜测是字符编码与它有关,因此base64大小差异如此之大。 Does anyone have a idea to get this working together? 有没有人有想法让这个合作? Thanks! 谢谢!

The error was in the Javascript code, the first part was Hex while the end was the encrypted result in Base64. 错误发生在Javascript代码中,第一部分是Hex,而结尾是Base64中的加密结果。

The following Javascript code makes the AES results interchangeable with the C# solution provided above. 以下Javascript代码使AES结果可与上面提供的C#解决方案互换。 I had some difficulties making sure that all the results where properly encoded and decoded in Hex, so there are some new functions. 我遇到了一些困难,确保所有结果在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("");
}

You are using Cipher Block Chaining (CBC) mode with a random IV (correct way). 您使用密码块链接(CBC)模式与随机IV(正确的方式)。

Indirectly the IV will affect every plaintext block before the encryption. 在加密之前,IV将间接地影响每个明文块。 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. 我假设这是因为CryptoJS.lib.WordArray将以十六进制打印。 Therefore You are getting seed and IV in hex encoding and the encrypted message in base64 encoding. 因此,您将获得十六进制编码的seedIV以及base64编码的加密消息。

On C# side there is only one base64 encoded result containing everything. 在C#端,只有一个包含所有内容的base64编码结果。

In general plain CBC mode is no longer state-of-the-art encryption (eg for TLS1.3 alls ciphers with AES-CBC has been removed). 通常,普通CBC模式不再是最先进的加密技术(例如,对于TLS1.3,已删除了具有AES-CBC的所有密码)。 Under certain conditions it may allows certain attacks (eg padding oracle attack). 在某些条件下,它可能允许某些攻击(例如填充oracle攻击)。 Therefore I would recommend to use an authenticating cipher mode like GCM mode instead. 因此,我建议使用像GCM模式这样的身份验证密码模式。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM