【问题标题】:Generate Rfc2898DeriveBytes key in JavaScript from C#从 C# 在 JavaScript 中生成 Rfc2898DeriveBytes 键
【发布时间】:2021-03-10 11:01:34
【问题描述】:

我有一个使用Rfc2898DeriveBytes 加密和解密C# 中的字段的应用程序。我一直在尝试使用 CryptoJS PBKDF2 在 JavaScript 中编写解密方法来制定跨平台解决方案。但是,我无法弄清楚以下两个问题:

  • 我找不到要匹配的密钥
  • 我无法弄清楚 JavaScript 中的 IV 应该是什么。

密钥生成

在 C# 中生成密钥(不幸的是,我对此没有太多控制,因为它支持很多遗留系统)

private readonly RijndaelManaged _alg = new RijndaelManaged();

public EncryptionManager()
{
    var secret = 'D2s1d_5$_t0t3||y_4c3$0m3!1!1!!';
    var salt = 'o6805542kcM7c5';

    var saltBytes = Encoding.ASCII.GetBytes(salt);
    using (var keyDeriver = new Rfc2898DeriveBytes(secret, saltBytes))
    {
        _alg.Key = keyDeriver.GetBytes(_alg.KeySize / 8); // _alg.KeySize = 256
    }
}

我用于密钥生成的 JS 代码是:

const secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3$0m3!1!1!!');

// Encoding the Salt in from UTF8 to byte array
const salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');

// Creating the key in PBKDF2 format to be used during the decryption
const key = CryptoJS.PBKDF2(secret.toString(CryptoJS.enc.Utf8), salt, {
    keySize: 128 / 32, 
    iterations: 1000,
});

根据我的研究,这在理想情况下应该可以工作,但是两个代码中生成的密钥永远不会相同。我为自己做错了什么而苦恼不已,但我不明白为什么。

解密

C#中的解密方法如下:

public string Decrypt(string ciphertext)
{
    var cipherTextBytes = Convert.FromBase64String(ciphertext);
    var ivSize = BitConverter.ToInt32(cipherTextBytes, 0);
    var iv = new byte[ivSize];
    var offset = sizeof(int);
    Array.Copy(cipherTextBytes, offset, iv, 0, ivSize);
    offset += ivSize;

    using (var msDecrypt = new MemoryStream(cipherTextBytes, offset, cipherTextBytes.Length - offset))
    {
        lock (_syncLock)
        {
            using (var decryptor = _alg.CreateDecryptor(_alg.Key, iv))
            using (var decryptStream = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
            using (var reader = new StreamReader(decryptStream))
            {
                return reader.ReadToEnd();
            }
        }
    }
}

我在JS中解密的解决方案是:

const decrypt = (encryptedData: string): string => {

    // Enclosing the test to be decrypted in a CipherParams object as supported by the CryptoJS libarary
    const cipherParams = CryptoJS.lib.CipherParams.create({
        ciphertext: CryptoJS.enc.Base64.parse(encryptedData),
    });

    // What should be the IV be here?
    const iv = CryptoJS.enc.Hex.parse(encryptedData);

    // Decrypting the string contained in cipherParams using the PBKDF2 key
    const decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
        mode: CryptoJS.mode.CBC,
        // iv,
        padding: CryptoJS.pad.Pkcs7,
    });
    decryptedText = decrypted.toString(CryptoJS.enc.Utf8);
    return decryptedText;
}

我假设如果我能得到正确的密钥和 IV,我就能解决这个问题。我已经四处寻找兼容的解决方案,但我不知道自己做错了什么。

任何帮助将不胜感激。

【问题讨论】:

  • 我会尝试简化示例,尤其是使用的密码和盐(例如只有小写字母),以确保您没有遇到编码问题。并且不要使用不同的编码,比如在一个平台上使用 ASCII,在另一个平台上使用 UTF-8,就像你对 salt 所做的那样。顺便说一句:AES CBC 的 IV 大小固定为 128 位,因此您不必存储/传输它。
  • C# 代码为您的密码返回并加盐 32 字节密钥(十六进制编码)e3912cec5e9d1ec5a756cf95991c08f8ce174ee2ddad61c13ff7ece89c0e83e1。 CryptoJS 代码返回 16 字节密钥(十六进制编码)e3912cec5e9d1ec5a756cf95991c08f8。如果 CryptoJS 代码中的密钥大小用keySize: 256 / 32 调整,那么密钥是相同的。如果您有其他密钥,您应该发布它们。
  • 如果您可以发布 C# 加密代码和/或示例数据(明文、密钥、密文),也会很有帮助。
  • @Robert 谢谢你的建议,会记住的。不幸的是,我对 C# 代码没有太多控制,但我会尝试修改 JS >
  • @Topaco 太棒了。我更改了keySize: 256/32,瞧,它们在十六进制编码时匹配。谢谢你在这方面帮助我。这里的密码和盐不是我使用的,但出于安全目的稍作修改。我仍然无法弄清楚 JS 上的 IV,而且 C# 解密中的偏移逻辑对我来说也不是很清楚。

标签: javascript c# encryption cryptojs rfc2898


【解决方案1】:

正如我在评论中已经提到的,C# 代码返回以下 32 字节的密钥以及发布的盐和密码(十六进制编码):

e3912cec5e9d1ec5a756cf95991c08f8ce174ee2ddad61c13ff7ece89c0e83e1

CryptoJS 代码仅生成一个 16 字节的密钥。如果使用keySize: 256 / 32 调整密钥大小,CryptoJS 代码将返回 same 32 字节密钥。


以下 Base64 编码的密文可以使用 C# 代码使用发布的密码和盐进行解密:

EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=

如果密文是十六进制编码的,结果是:

10000000 232d901eb81dee29fcc488fb2176036a c95a1c69b2c1ba6f8f19d3526b7db12bbd0d6be868b47ac80299dac1685c4e3c5175f8052ccab514979150e0d9347525

前 4 个字节包含有关 IV 长度的信息。由于 IV 的长度是已知的(AES 为 16 个字节),因此实际上没有必要存储此信息,正如 Robert 的评论中已经提到的那样。以下 16 个字节对应于 IV,其余字节对应于实际密文。这些数据必须分开。之后就可以解密了:

// Key derivation
var secret = CryptoJS.enc.Utf8.parse('D2s1d_5$_t0t3||y_4c3$0m3!1!1!!');
var salt = CryptoJS.enc.Utf8.parse('o6805542kcM7c5');
var key = CryptoJS.PBKDF2(secret, salt, {
    keySize: 256 / 32, 
    iterations: 1000,
});

// Separation of iv size, iv and ciphertext
var encryptedDataB64 = "EAAAACMtkB64He4p/MSI+yF2A2rJWhxpssG6b48Z01JrfbErvQ1r6Gi0esgCmdrBaFxOPFF1+AUsyrUUl5FQ4Nk0dSU=";
var encryptedData = CryptoJS.enc.Base64.parse(encryptedDataB64);
var ivSize = CryptoJS.lib.WordArray.create(encryptedData.words.slice(0, 1));
var iv = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1, 1 + 4));
var ciphertext = CryptoJS.lib.WordArray.create(encryptedData.words.slice(1 + 4));

// Decryption
var cipherParams = CryptoJS.lib.CipherParams.create({
    ciphertext: ciphertext
});
var decrypted = CryptoJS.AES.decrypt(cipherParams, key, {
    mode: CryptoJS.mode.CBC,
    iv: iv,
    padding: CryptoJS.pad.Pkcs7,
});
var decryptedText = decrypted.toString(CryptoJS.enc.Utf8);

// Output
console.log(decryptedText);
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>

【讨论】:

  • 你先生让我很开心。很好解释的答案,iv 和密文的分离是我做错的。这真的很有帮助!
猜你喜欢
  • 2015-04-26
  • 1970-01-01
  • 2014-02-08
  • 2017-12-04
  • 2014-01-08
  • 2015-11-09
  • 2010-11-06
  • 2012-11-03
  • 1970-01-01
相关资源
最近更新 更多