Crypto++ pbkdf2 输出不同于 Rfc2898DeriveBytes (C#) 和 crypto.pbkdf2 (JavaScript)

Posted

技术标签:

【中文标题】Crypto++ pbkdf2 输出不同于 Rfc2898DeriveBytes (C#) 和 crypto.pbkdf2 (JavaScript)【英文标题】:Crypto++ pbkdf2 output is different than Rfc2898DeriveBytes (C#) and crypto.pbkdf2 (JavaScript) 【发布时间】:2015-01-30 09:44:58 【问题描述】:

所以我正在尝试使用 PBKDF2 来派生一个给定 256 位的 base64 字符串的密钥。我可以使用 C# 的 Rfc2898DeriveBytes 和 node-crypto 的 pbkdf2 来派生相同的密钥,但是,我不能对 C++ 说同样的话。不知道是不是我转换错了,还是函数使用不当,给大家看看。

C++

/* 256bit key */
string key = "Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ=";
string decodedKey;
StringSource(key, true, new Base64Decoder(new StringSink(decodedKey)));
const byte* keyByte = (const byte*) decodedKey.data();

/* Generate IV */
/*
    AutoSeededRandomPool prng;
    byte iv[AES::BLOCKSIZE];
    prng.GenerateBlock(iv, sizeof(iv));
*/

/* FOR TESTING PURPOSES, HARDCODE IV */
string iv = "5iFv54dCRq5icQbD7QHQzg==";
string decodedIv;
StringSource(iv, true, new Base64Decoder(new StringSink(decodedIv)));
const byte* ivByte = (const byte *) decodedIv.data();

byte derivedKey[32];
PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf2;
pbkdf2.DeriveKey(derivedKey, 32, 0, keyByte, 32, ivByte, 16, 100);

/* 
 * derivedKey: 9tRyXCoQLTbUOLqm3M4OPGT6N25g+o0K090fVp/hflk=
 */

C#

// string key = "Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ="; // need to convert it to byte data
string key = Convert.FromBase64String("Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ="); // change above to this
RijndaelManaged symKey = new RijndaelManaged();
symKey.GenerateIV(); /* Assume hardcoded IV same as above */
Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes (key, symKey.IV, 100);

/*
 * derivedKey: dZqBpZKyUPKn8pU4pyyeAw7Rg8uYd6yyj3WI1MIJSyc=
 */

JS

// var key = "Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ="; // need to convert it to byte data
var key = new Buffer("Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ=", "base64"); // changed above to this
var iv = crypto.randomBytes(16);
iv = "5iFv54dCRq5icQbD7QHQzg=="; /* HARDCODE IV */
crypto.pbkdf2(key, iv, 100, 32, function(err, derivedKey)  

/*
 * derivedKey: dZqBpZKyUPKn8pU4pyyeAw7Rg8uYd6yyj3WI1MIJSyc=
 */

主要问题是,我在 C++ 的 CryptoPP 上做错了什么 库,它没有派生相同的值。

解决方案:我当时很愚蠢……在查看了我在 JavaScript 和 C# 上的原始实现后,我意识到我错过了一个关键步骤,由于某种原因我没有收到编译器的抱怨。基本上问题是我没有在我的 C# 和 JS 实现的算法之前将使用的密钥转换为字节数据......

无论如何,建议的解决方案是:不要在凌晨 4 点编码,并确保数据转换保持一致...

我猜 TL;DR 是 C# 和 JS 将我的 256 位密钥转换为 ASCII 字节数据而不是 base64 转换。

【问题讨论】:

您已经陈述了一个观察结果,但您还没有提出任何问题。你的问题是什么?顺便说一句,您是否针对实现运行了测试向量/已知答案测试以查看哪些给出了正确和错误的答案? 嗯,主要问题是,我在 C++ 的 CryptoPP 库上做错了什么,它没有得到相同的值。我还没有设置环境来单独测试库。然而,在每种语言上,我也有等效的解密方法,它将获取这些字节值并解密消息。唯一没有返回正确值的是 C++,但是如果我从任何其他语言传递 derivedKey 并继续解密,则输出是正确的。现在cryptopp库正在android平台上与Cocos2dx c++结合使用,调试起来很烦 【参考方案1】:

主要问题是,我在 C++ 的 CryptoPP 库上做错了什么,它没有得到相同的值。

好吧,我不认为你在 C++ 中使用 Crypto++ 和 PBKDF2 做错了什么。我认为其他库设置参数的方式不同,或者它们有点不标准。

我能够使用 Crypto++ 到达 IETF 的 test vectors for PBKDF2:

// From https://www.ietf.org/rfc/rfc6070.txt
//   PKCS #5: Password-Based Key Derivation Function 2 (PBKDF2) Test Vectors
//
//      Input:
//       P = "password" (8 octets)
//       S = "salt" (4 octets)
//       c = 1
//       dkLen = 20
//
//     Output:
//       DK = 0c 60 c8 0f 96 1f 0e 71
//            f3 a9 b5 24 af 60 12 06
//            2f e0 37 a6    (20 octets)

int main(int argc, char* argv[])

    byte password[] ="password";
    size_t plen = strlen((const char*)password);

    byte salt[] = "salt";
    size_t slen = strlen((const char*)salt);

    int c = 1;
    byte derived[20];

    PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf2;
    pbkdf2.DeriveKey(derived, sizeof(derived), 0, password, plen, salt, slen, c);

    string result;
    HexEncoder encoder(new StringSink(result));

    encoder.Put(derived, sizeof(derived));
    encoder.MessageEnd();

    cout << "Derived: " << result << endl;

    return 0;

然后运行程序:

$ ./cryptopp-test.exe
Derived: 0C60C80F961F0E71F3A9B524AF6012062FE037A6

我认为您应该做的第一件事是验证 C# 和 javascript 实现是否使用与 Crypto++ 和 IETF 相同的字符编码。

如果不是,则检查 C# 和 Javascript 是否使用目的字节。 Crypto++ 没有,你可以在pwdbased.h 看到实现。


不幸的是,当我输入你的参数时,我得到了一些不同的东西:

int main(int argc, char* argv[])

    string t1 = "Y1Mjycd0+O+AendY5pB58JMlmS0EmBWgjdj2r2KW6qQ=";
    string t2 = "5iFv54dCRq5icQbD7QHQzg==";

    string pw, iv;

    Base64Decoder b1(new StringSink(pw));
    b1.Put((const byte*)t1.data(), t1.size());
    b1.MessageEnd();

    Base64Decoder b2(new StringSink(iv));
    b2.Put((const byte*)t2.data(), t2.size());
    b2.MessageEnd();

    int c = 100;
    byte derived[32];

    cout << "pw size: " << pw.size() << endl;
    cout << "iv size: " << iv.size() << endl;

    PKCS5_PBKDF2_HMAC<CryptoPP::SHA1> pbkdf2;
    pbkdf2.DeriveKey(derived, sizeof(derived), 0, (byte*)pw.data(), pw.size(), (byte*)iv.data(), iv.size(), c);

    string result;
    HexEncoder encoder(new StringSink(result));

    encoder.Put(derived, sizeof(derived));
    encoder.MessageEnd();

    cout << "Derived: " << result << endl;

    return 0;

运行结果:

$ ./cryptopp-test.exe
pw size: 32
iv size: 16
Derived: F6D4725C2A102D36D438BAA6DCCE0E3C64FA376E60FA8D0AD3DD1F569FE17E59

【讨论】:

感谢您的帮助。如果您不建议针对 IETF 的测试向量测试我的代码,我就不会研究我的原始实现,并且会一直假设 CryptPP 对我来说太疯狂了。我仍然对 C# 编译器和节点没有像 C++ 那样抱怨感到惊讶。再次感谢! @Jww:当您在整个示例中使用 new 运算符而不使用 delete 时,您是否会造成少量内存泄漏? @ahmd0 - 否。请参阅 Crypto++ wiki 上的 Pipelining | Ownership。 哦,好的。这只是用 C++ 编程的一种非正统方式。几乎看起来像 Java 代码。

以上是关于Crypto++ pbkdf2 输出不同于 Rfc2898DeriveBytes (C#) 和 crypto.pbkdf2 (JavaScript)的主要内容,如果未能解决你的问题,请参考以下文章

crypto.pbkdf2 是异步的,如何将其视为同步?

使用 Rfc2898DeriveBytes 在 C# 中实现 PBKDF2

Rfc2898DeriveBytes + PBKDF2 + SecureString 是不是可以使用安全字符串而不是字符串?

PBKDF2 Python 密钥与 .NET Rfc2898

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

python RFC6070的测试和python中pbkdf2用法的演示