PBKDF2 Python 密钥与 .NET Rfc2898

Posted

技术标签:

【中文标题】PBKDF2 Python 密钥与 .NET Rfc2898【英文标题】:PBKDF2 Python keys vs .NET Rfc2898 【发布时间】:2014-11-27 21:33:02 【问题描述】:

我正在尝试编写一个 Python 模块来加密我们现有的 .NET 类可以解密的文本。据我所知,我的代码行向上但没有解密(我在 C# 端收到“无效填充长度”错误)。我的 pkcs7 代码看起来不错,但研究表明无效的密钥可能会导致同样的问题。

这两种设置有什么不同? 蟒蛇:

derived_key = PBKDF2(crm_key, salt, 256 / 8, iterations)
iv = PBKDF2(crm_key, salt, 128 / 8, iterations)

encoder = pkcs7.PKCS7Encoder()

cipher = AES.new(derived_key, AES.MODE_CBC, iv)
decoded = cipher.decrypt(encoded_secret)

#encode - just stepped so i could debug. 
padded_secret = encoder.encode(secret)              # 1
encodedtext = cipher.encrypt(padded_secret)         # 2
based_secret = base64.b64encode(encodedtext)        # 3

我认为 based_secret 可以传递到 C# 并在那里解码。但它失败了。同样的加密c#代码是:

var rfc = new Rfc2898DeriveBytes(key, saltBytes);


        // create provider & encryptor
        using (var cryptoProvider = new AesManaged())
        
            // Set cryptoProvider parameters
            cryptoProvider.BlockSize = cryptoProvider.LegalBlockSizes[0].MaxSize;
            cryptoProvider.KeySize = cryptoProvider.LegalKeySizes[0].MaxSize;

            cryptoProvider.Key = rfc.GetBytes(cryptoProvider.KeySize / 8);
            cryptoProvider.IV = rfc.GetBytes(cryptoProvider.BlockSize / 8);

            using (var encryptor = cryptoProvider.CreateEncryptor())
            
                // Create a MemoryStream.
                using (var memoryStream = new MemoryStream())
                
                    // Create a CryptoStream using the MemoryStream and the encryptor.
                    using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write))
                    
                        // Convert the passed string to a byte array.
                        var valueBytes = Encoding.UTF8.GetBytes(plainValue);

                        // Write the byte array to the crypto stream and flush it.
                        cryptoStream.Write(valueBytes, 0, valueBytes.Length);
                        cryptoStream.FlushFinalBlock();

                        // Get an array of bytes from the
                        // MemoryStream that holds the
                        // encrypted data.
                        var encryptBytes = memoryStream.ToArray();

                        // Close the streams.
                        cryptoStream.Close();
                        memoryStream.Close();

                        // Return the encrypted buffer.
                        return Convert.ToBase64String(encryptBytes);
                    
                
            

我正在使用的 Python pkcs7 实现是: https://gist.github.com/chrix2/4171336

【问题讨论】:

我首先尝试检查生成的密钥在 C# 和 Python 中是否相同。 C# 的关键输出是一个字节数组,它在 Python 中是一个 UTF-8 字符串。为了正确比较这些,我可以 Convert.ToBase64(csharpKey) 和 base64.encode(pythonkey) 吗?这应该让我得到可比较的项目吗? 经过大量研究后,我发现了一些有用的东西: Rfc2898DeriveBytes.GetBytes 的初始实例和对 PBKDF2 的调用产生了相同的密钥。但是,根据 MSDN,GetBytes 正在复合调用以生成新密钥。我第二次调用 GetBytes(获取 IV)时,它的输出完全不同。 哎呀,真不敢相信我错过了那个,除了 NullUserException 的评论之外没有进一步阅读。是的,如果您执行两次GetBytes,您将只获得更多字节的 PBKDF2 生成的 stream。如果你在PasswordDeriveBytes 上这样做,你会得到完全的陷阱,但这完全是另一个问题。最好将其发布为您自己问题的答案,很高兴您解决了问题。 【参考方案1】:

首先,我验证了 Rfc2898 和 PBKDF2 是同一个东西。然后,如上所述,问题似乎是 .net 主义。我在msdn 上找到了

Rfc2898DeriveBytes 内部的 GetBytes 实现在每次调用时都会发生变化,即。它拥有状态。 (见半页左右的备注)

Python 中的示例(伪输出):

derived_key = PBKDF2(key, salt, 32, 1000)
iv = PBKDF2(key, salt, 16, 1000)
print(base64.b64encode(derived_key))
print(base64.b64encode(iv))
$123456789101112134==
$12345678==

.NET 中的相同(ish)代码(再次,伪输出):

var rfc = new Rfc2898DeriveBytes(key, saltBytes);
    using (var cryptoProvider = new AesManaged())
    
        // Set cryptoProvider parameters
        cryptoProvider.BlockSize = cryptoProvider.LegalBlockSizes[0].MaxSize;
        cryptoProvider.KeySize = cryptoProvider.LegalKeySizes[0].MaxSize;

        cryptoProvider.Key = rfc.GetBytes(cryptoProvider.KeySize / 8);
        cryptoProvider.IV = rfc.GetBytes(cryptoProvider.BlockSize / 8);
    
Console.Writeline(Convert.ToBase64(cryptoProvider.Key));
Console.Writeline(Convert.ToBase64(cryptoProvider.IV));

$123456789101112134==
$600200300==

对 rfc.GetBytes 的后续调用总是产生不同的结果。 MSDN 表示,它会在呼叫中增加密钥大小。因此,如果您调用 GetBytes(20) 两次,则与调用 GetBytes(20+20) 或 GetBytes(40) 相同。从理论上讲,这应该只是增加密钥的大小,而不是完全改变它。

有一些解决方案可以解决此问题,可能是在第一次调用时生成更长的密钥,然后将其分割成派生密钥和 IV,或者随机生成 IV,将其附加到编码的消息中,然后在解密之前将其剥离。

对 python 输出进行切片会产生与 .NET 相同的结果。它看起来像这样:

derived_key = PBKDF2(key, salt, 32, 1000)
iv = PBKDF2(key, salt, 32 + 16, 1000) # We need 16, but we're compensating for .NETs 'already called' awesomeness on the GetBytes method
split_key = iv[32:]

print(base64.b64encode(derived_key))
print(base64.b64encode(iv))
print(base64.b64encode(split_key))

$ 123456789101112134==   # matches our derived key
$ 12345678== # doesn't match
$ 600200300== # matches. this is the base 64 encoded version of the tailing 16 bytes.

享受,

【讨论】:

以上是关于PBKDF2 Python 密钥与 .NET Rfc2898的主要内容,如果未能解决你的问题,请参考以下文章

PBKDF2WithHmacSHA1 密钥生成在 Android 上花费的时间太长

使用 PBKDF2 和 AES256 进行加密和解密 - 需要实际示例 - 如何获取派生密钥

使用 Jasypt 使用 PBKDF2WithHmacSHA1 密钥进行基于密码的 AES 加密

使用PBKDF2密钥派生来正确创建用户可读的salt with dry-crypto

Java - 使用 HMACSHA256 作为 PRF 的 PBKDF2

Android中的PBKDF2函数