web-dev-qa-db-ja.com

C#およびjavascript用の互換性のあるAES暗号化および復号化

プロジェクト全体で使用できるC#とJavascriptの2つのクラスを作成して、データ交換時にAESを使用してデータを暗号化または復号化しようとしています。

AESを使用して、暗号化された結果にSalt(32バイト)とIV(16バイト)を埋め込みます。これは、テスト時に両方のクラスで個別に正常に機能します。 SaltとIVをミックスに追加しても、2つのプラットフォーム間でこれを機能させるための参照は多くありません。

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のサイズが大きく異なるのはなぜですか。誰かがこれを一緒に機能させるアイデアを持っていますか?ありがとう!

7
usselite

エラーはJavascriptコードにあり、最初の部分は16進数で、最後はBase64で暗号化された結果でした。

次のJavascriptコードは、AESの結果を上記のC#ソリューションと互換性のあるものにします。すべての結果が16進数で正しくエンコードおよびデコードされていることを確認するのにいくつかの問題があったため、いくつかの新しい関数があります。

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("");
}
5
usselite