MbedTLS 和 .NET BouncyCastle 与 Curve25519 的互操作性问题
MbedTLS and .NET BouncyCastle interoperability issue with Curve25519
编辑:代码已更改以提供更简单的测试用例
我正在创建一个简单的 client/server 应用程序,它使用 Curve25519 进行密钥交换。客户端使用 mbedtls 在 C 中实现,服务器使用 BouncyCastle.
在 .NET 中实现
不幸的是,生成的共享密钥在客户端和服务器上是不一样的。下面显示了生成 public/private 键的代码的摘录(我硬编码了一些值以方便调试)。
客户端密钥生成(mbedtls 代码,大部分复制自https://github.com/ARMmbed/mbedtls/blob/development/programs/pkey/ecdh_curve25519.c and https://github.com/google/eddystone/blob/bb8738d7ddac0ddd3dfa70e594d011a0475e763d/implementations/mbed/source/EIDFrame.cpp#L144)
void generate_curve25519_keys() {
uint8_t my_pubkey[32] = { 0 };
uint8_t my_privkey[32] = { 0 };
mbedtls_ecdh_context ctx;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// generate the keys and save to buffer
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ctx);
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
0,
0
);
mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_CURVE25519);
mbedtls_ecdh_gen_public(
&ctx.grp,
&ctx.d,
&ctx.Q,
mbedtls_ctr_drbg_random,
&ctr_drbg
);
mbedtls_mpi_write_binary(&ctx.Q.X, my_pubkey, sizeof(my_pubkey));
printf("Pub: ");
for (size_t i = 0; i < sizeof(my_pubkey); i++)
printf("0x%02X, ", my_pubkey[i]);
printf("\n");
mbedtls_mpi_write_binary(&ctx.d, my_privkey, sizeof(my_privkey));
printf("Priv: ");
for (size_t i = 0; i < sizeof(my_privkey); i++)
printf("0x%02X, ", my_privkey[i]);
printf("\n");
}
执行的输出是:
Pub: 0x36, 0x4B, 0x8E, 0x89, 0x31, 0x18, 0xA4, 0x32, 0xE3, 0x5B, 0xB1, 0x70, 0x69, 0x55, 0xFE, 0x42, 0x8C, 0x48, 0x8C, 0xC9, 0x0E, 0x2C, 0xA2, 0x1A, 0x66, 0x6A, 0x26, 0x7B, 0xD0, 0xDA, 0x88, 0x5C,
Priv: 0x6E, 0xCF, 0x6C, 0xBD, 0x9C, 0xDE, 0xDC, 0xBF, 0xD3, 0xB3, 0x82, 0x9A, 0x7D, 0xA7, 0x27, 0x50, 0xA2, 0xA0, 0x47, 0x64, 0x14, 0xC7, 0xD8, 0x90, 0xFC, 0xCD, 0x11, 0xC3, 0x5C, 0x37, 0xFB, 0xB0,
服务器密钥生成(BouncyCastle代码)
// generate public and private key
let keyGenerator = new X25519KeyPairGenerator()
keyGenerator.Init(new X25519KeyGenerationParameters(new SecureRandom()))
let keys = keyGenerator.GenerateKeyPair()
let publicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keys.Public)
let x25519PublicKey = new X25519PublicKeyParameters(publicKey.GetEncoded(), 0)
Console.WriteLine("PUB: {{0x{0}}}", BitConverter.ToString(x25519PublicKey.GetEncoded()).Replace("-", ", 0x"))
let privateKey = ECPrivateKeyStructure.GetInstance(PrivateKeyInfoFactory.CreatePrivateKeyInfo(keys.Private))
let x25519PrivateKey = new X25519PrivateKeyParameters(privateKey.GetEncoded(), 0)
Console.WriteLine("PRIV: {{0x{0}}}", BitConverter.ToString(x25519PrivateKey.GetEncoded()).Replace("-", ", 0x"))
执行的输出是:
PUB: {0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x03, 0x21, 0x00, 0xD2, 0x86, 0xF9, 0x67, 0xAB, 0xF8, 0x8C, 0x8C, 0x4E, 0xC9, 0xF9, 0xFC, 0x29, 0xE2, 0xC2, 0xD2, 0x3B, 0x8E, 0x1E, 0x3D}
PRIV: {0x30, 0x51, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x04, 0x22, 0x04, 0x20, 0x78, 0xF3, 0xC9, 0xBE, 0xB5, 0x74, 0x5A, 0x63, 0x99, 0x5C, 0xCB, 0x82, 0xD7, 0x0C, 0xBC, 0x37}
根据这些信息,我继续使用以下代码生成共享密钥:
客户端共享密钥生成(mbedtls 代码)
void generate_curve25519_shared_secret() {
uint8_t my_privkey[] = { 0x6E, 0xCF, 0x6C, 0xBD, 0x9C, 0xDE, 0xDC, 0xBF, 0xD3, 0xB3, 0x82, 0x9A, 0x7D, 0xA7, 0x27, 0x50, 0xA2, 0xA0, 0x47, 0x64, 0x14, 0xC7, 0xD8, 0x90, 0xFC, 0xCD, 0x11, 0xC3, 0x5C, 0x37, 0xFB, 0xB0 };
uint8_t server_pubkey[] = { 0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x03, 0x21, 0x00, 0xD2, 0x86, 0xF9, 0x67, 0xAB, 0xF8, 0x8C, 0x8C, 0x4E, 0xC9, 0xF9, 0xFC, 0x29, 0xE2, 0xC2, 0xD2, 0x3B, 0x8E, 0x1E, 0x3D };
uint8_t shared_secret[32] = { 0 };
mbedtls_ecdh_context ctx;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// generate the keys and save to buffer
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ctx);
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
0,
0
);
mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_CURVE25519);
// read my private key
mbedtls_mpi_read_binary(&ctx.d, my_privkey, sizeof(my_privkey));
mbedtls_mpi_lset(&ctx.Qp.Z, 1);
// read server key
mbedtls_mpi_read_binary(&ctx.Qp.X, server_pubkey, sizeof(server_pubkey));
// generate shared secret
size_t olen;
mbedtls_ecdh_calc_secret(
&ctx,
&olen,
shared_secret,
32,
0,
0
);
printf("Secret :");
for (size_t i = 0; i < sizeof(shared_secret); i++)
printf("0x%02X, ", shared_secret[i]);
printf("\n");
}
客户端执行的输出为:
Secret :0x3D, 0xF3, 0xD3, 0x88, 0xAB, 0xD7, 0x31, 0xA4, 0x1E, 0x52, 0xFB, 0x9A, 0x28, 0x82, 0xBF, 0x9C, 0xA9, 0x45, 0xB0, 0x6C, 0xC7, 0xD7, 0x20, 0xAC, 0x7E, 0xCB, 0x51, 0x50, 0x84, 0x2C, 0x25, 0x57,
服务器共享密钥生成(BouncyCastle代码)
// compute shared secret
let rawAgentPubKey = [|0x36uy; 0x4Buy; 0x8Euy; 0x89uy; 0x31uy; 0x18uy; 0xA4uy; 0x32uy; 0xE3uy; 0x5Buy; 0xB1uy; 0x70uy; 0x69uy; 0x55uy; 0xFEuy; 0x42uy; 0x8Cuy; 0x48uy; 0x8Cuy; 0xC9uy; 0x0Euy; 0x2Cuy; 0xA2uy; 0x1Auy; 0x66uy; 0x6Auy; 0x26uy; 0x7Buy; 0xD0uy; 0xDAuy; 0x88uy; 0x5Cuy|]
let rawPrivKey = [|0x30uy; 0x51uy; 0x02uy; 0x01uy; 0x01uy; 0x30uy; 0x05uy; 0x06uy; 0x03uy; 0x2Buy; 0x65uy; 0x6Euy; 0x04uy; 0x22uy; 0x04uy; 0x20uy; 0x78uy; 0xF3uy; 0xC9uy; 0xBEuy; 0xB5uy; 0x74uy; 0x5Auy; 0x63uy; 0x99uy; 0x5Cuy; 0xCBuy; 0x82uy; 0xD7uy; 0x0Cuy; 0xBCuy; 0x37uy|]
let agentPubKey = new X25519PublicKeyParameters(rawAgentPubKey, 0)
let secret = Array.zeroCreate<Byte>(32)
let privateKey = new X25519PrivateKeyParameters(rawPrivKey, 0)
privateKey.GenerateSecret(agentPubKey, secret, 0)
Console.WriteLine("SECRET: {{0x{0}}}", BitConverter.ToString(secret).Replace("-", ", 0x"))
在服务器上执行的输出是:
SECRET: {0xE2, 0x2B, 0xC6, 0x3A, 0xA0, 0x75, 0x83, 0x60, 0xB8, 0xE1, 0x47, 0xD6, 0x66, 0x24, 0x14, 0xC2, 0x99, 0x51, 0x05, 0x3C, 0xDC, 0x96, 0x2B, 0xC4, 0xE2, 0x10, 0x7C, 0x77, 0xC0, 0xA2, 0xD1, 0x77}
两次生成的密文明显不同。通过阅读各种示例,可能是由于不同的字节顺序编码。我尝试使用方法 mbedtls_mpi_read_binary_le 和 mbedtls_mpi_write_binary_le,但没有任何运气。
作为替代解决方案,如果此更改可以解决问题,我可以更改 .NET 库并移动到另一个库。不幸的是,此时我无法找到一个好的 .NET 替代品。
Curve25519 表示小端顺序的键。 X25519(带有 Curve25519 的 ECDH)代表小端顺序的共享秘密。这与密码学中使用的大多数标准格式不同,特别是 SECP/NIST 和 Brainpool 曲线上的密钥以及 ECDH 与 Weierstrass 曲线的共享秘密,后者表示大端数字中的数字。因此,将对 mbedtls_mpi_write_binary
的两个调用更改为 mbedtls_mpi_write_binary_le
.
或者,使用 mbedtls_ecp_point_write_binary
导出 public 密钥并使用 mbedtls_ecdh_calc_secret
计算共享密钥:它们负责为每条曲线格式化数字,使其具有正确的字节顺序。
我还没有确认这是唯一的问题。
在 .NET 端,密钥对生成正确,但 public 和私钥导出不正确。它们必须按如下方式确定(在 C# 中):
// generate public and private key
var keyGenerator = new X25519KeyPairGenerator();
keyGenerator.Init(new X25519KeyGenerationParameters(new SecureRandom()));
var keys = keyGenerator.GenerateKeyPair();
var publicKey = (X25519PublicKeyParameters)keys.Public;
Console.WriteLine("PUB: {{0x{0}}}", BitConverter.ToString(publicKey.GetEncoded()).Replace("-", ", 0x"));
var privateKey = (X25519PrivateKeyParameters)keys.Private;
Console.WriteLine("PRIV: {{0x{0}}}", BitConverter.ToString(privateKey.GetEncoded()).Replace("-", ", 0x"));
并且正如在 中已经指出的那样,必须考虑小端顺序,即在 generate_curve25519_keys()
中的 C/C++ 代码中 mbedtls_mpi_write_binary()
必须替换为 mbedtls_mpi_write_binary_le()
。同样,在 generate_curve25519_shared_secret()
中,两个 mbedtls_mpi_read_binary()
都必须替换为 mbedtls_mpi_read_binary_le()
。
通过这些更改,两个代码在我的机器上生成相同的共享密钥。
编辑:代码已更改以提供更简单的测试用例
我正在创建一个简单的 client/server 应用程序,它使用 Curve25519 进行密钥交换。客户端使用 mbedtls 在 C 中实现,服务器使用 BouncyCastle.
在 .NET 中实现不幸的是,生成的共享密钥在客户端和服务器上是不一样的。下面显示了生成 public/private 键的代码的摘录(我硬编码了一些值以方便调试)。
客户端密钥生成(mbedtls 代码,大部分复制自https://github.com/ARMmbed/mbedtls/blob/development/programs/pkey/ecdh_curve25519.c and https://github.com/google/eddystone/blob/bb8738d7ddac0ddd3dfa70e594d011a0475e763d/implementations/mbed/source/EIDFrame.cpp#L144)
void generate_curve25519_keys() {
uint8_t my_pubkey[32] = { 0 };
uint8_t my_privkey[32] = { 0 };
mbedtls_ecdh_context ctx;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// generate the keys and save to buffer
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ctx);
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
0,
0
);
mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_CURVE25519);
mbedtls_ecdh_gen_public(
&ctx.grp,
&ctx.d,
&ctx.Q,
mbedtls_ctr_drbg_random,
&ctr_drbg
);
mbedtls_mpi_write_binary(&ctx.Q.X, my_pubkey, sizeof(my_pubkey));
printf("Pub: ");
for (size_t i = 0; i < sizeof(my_pubkey); i++)
printf("0x%02X, ", my_pubkey[i]);
printf("\n");
mbedtls_mpi_write_binary(&ctx.d, my_privkey, sizeof(my_privkey));
printf("Priv: ");
for (size_t i = 0; i < sizeof(my_privkey); i++)
printf("0x%02X, ", my_privkey[i]);
printf("\n");
}
执行的输出是:
Pub: 0x36, 0x4B, 0x8E, 0x89, 0x31, 0x18, 0xA4, 0x32, 0xE3, 0x5B, 0xB1, 0x70, 0x69, 0x55, 0xFE, 0x42, 0x8C, 0x48, 0x8C, 0xC9, 0x0E, 0x2C, 0xA2, 0x1A, 0x66, 0x6A, 0x26, 0x7B, 0xD0, 0xDA, 0x88, 0x5C,
Priv: 0x6E, 0xCF, 0x6C, 0xBD, 0x9C, 0xDE, 0xDC, 0xBF, 0xD3, 0xB3, 0x82, 0x9A, 0x7D, 0xA7, 0x27, 0x50, 0xA2, 0xA0, 0x47, 0x64, 0x14, 0xC7, 0xD8, 0x90, 0xFC, 0xCD, 0x11, 0xC3, 0x5C, 0x37, 0xFB, 0xB0,
服务器密钥生成(BouncyCastle代码)
// generate public and private key
let keyGenerator = new X25519KeyPairGenerator()
keyGenerator.Init(new X25519KeyGenerationParameters(new SecureRandom()))
let keys = keyGenerator.GenerateKeyPair()
let publicKey = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keys.Public)
let x25519PublicKey = new X25519PublicKeyParameters(publicKey.GetEncoded(), 0)
Console.WriteLine("PUB: {{0x{0}}}", BitConverter.ToString(x25519PublicKey.GetEncoded()).Replace("-", ", 0x"))
let privateKey = ECPrivateKeyStructure.GetInstance(PrivateKeyInfoFactory.CreatePrivateKeyInfo(keys.Private))
let x25519PrivateKey = new X25519PrivateKeyParameters(privateKey.GetEncoded(), 0)
Console.WriteLine("PRIV: {{0x{0}}}", BitConverter.ToString(x25519PrivateKey.GetEncoded()).Replace("-", ", 0x"))
执行的输出是:
PUB: {0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x03, 0x21, 0x00, 0xD2, 0x86, 0xF9, 0x67, 0xAB, 0xF8, 0x8C, 0x8C, 0x4E, 0xC9, 0xF9, 0xFC, 0x29, 0xE2, 0xC2, 0xD2, 0x3B, 0x8E, 0x1E, 0x3D}
PRIV: {0x30, 0x51, 0x02, 0x01, 0x01, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x04, 0x22, 0x04, 0x20, 0x78, 0xF3, 0xC9, 0xBE, 0xB5, 0x74, 0x5A, 0x63, 0x99, 0x5C, 0xCB, 0x82, 0xD7, 0x0C, 0xBC, 0x37}
根据这些信息,我继续使用以下代码生成共享密钥:
客户端共享密钥生成(mbedtls 代码)
void generate_curve25519_shared_secret() {
uint8_t my_privkey[] = { 0x6E, 0xCF, 0x6C, 0xBD, 0x9C, 0xDE, 0xDC, 0xBF, 0xD3, 0xB3, 0x82, 0x9A, 0x7D, 0xA7, 0x27, 0x50, 0xA2, 0xA0, 0x47, 0x64, 0x14, 0xC7, 0xD8, 0x90, 0xFC, 0xCD, 0x11, 0xC3, 0x5C, 0x37, 0xFB, 0xB0 };
uint8_t server_pubkey[] = { 0x30, 0x2A, 0x30, 0x05, 0x06, 0x03, 0x2B, 0x65, 0x6E, 0x03, 0x21, 0x00, 0xD2, 0x86, 0xF9, 0x67, 0xAB, 0xF8, 0x8C, 0x8C, 0x4E, 0xC9, 0xF9, 0xFC, 0x29, 0xE2, 0xC2, 0xD2, 0x3B, 0x8E, 0x1E, 0x3D };
uint8_t shared_secret[32] = { 0 };
mbedtls_ecdh_context ctx;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
// generate the keys and save to buffer
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ctx);
mbedtls_ctr_drbg_seed(
&ctr_drbg,
mbedtls_entropy_func,
&entropy,
0,
0
);
mbedtls_ecp_group_load(&ctx.grp, MBEDTLS_ECP_DP_CURVE25519);
// read my private key
mbedtls_mpi_read_binary(&ctx.d, my_privkey, sizeof(my_privkey));
mbedtls_mpi_lset(&ctx.Qp.Z, 1);
// read server key
mbedtls_mpi_read_binary(&ctx.Qp.X, server_pubkey, sizeof(server_pubkey));
// generate shared secret
size_t olen;
mbedtls_ecdh_calc_secret(
&ctx,
&olen,
shared_secret,
32,
0,
0
);
printf("Secret :");
for (size_t i = 0; i < sizeof(shared_secret); i++)
printf("0x%02X, ", shared_secret[i]);
printf("\n");
}
客户端执行的输出为:
Secret :0x3D, 0xF3, 0xD3, 0x88, 0xAB, 0xD7, 0x31, 0xA4, 0x1E, 0x52, 0xFB, 0x9A, 0x28, 0x82, 0xBF, 0x9C, 0xA9, 0x45, 0xB0, 0x6C, 0xC7, 0xD7, 0x20, 0xAC, 0x7E, 0xCB, 0x51, 0x50, 0x84, 0x2C, 0x25, 0x57,
服务器共享密钥生成(BouncyCastle代码)
// compute shared secret
let rawAgentPubKey = [|0x36uy; 0x4Buy; 0x8Euy; 0x89uy; 0x31uy; 0x18uy; 0xA4uy; 0x32uy; 0xE3uy; 0x5Buy; 0xB1uy; 0x70uy; 0x69uy; 0x55uy; 0xFEuy; 0x42uy; 0x8Cuy; 0x48uy; 0x8Cuy; 0xC9uy; 0x0Euy; 0x2Cuy; 0xA2uy; 0x1Auy; 0x66uy; 0x6Auy; 0x26uy; 0x7Buy; 0xD0uy; 0xDAuy; 0x88uy; 0x5Cuy|]
let rawPrivKey = [|0x30uy; 0x51uy; 0x02uy; 0x01uy; 0x01uy; 0x30uy; 0x05uy; 0x06uy; 0x03uy; 0x2Buy; 0x65uy; 0x6Euy; 0x04uy; 0x22uy; 0x04uy; 0x20uy; 0x78uy; 0xF3uy; 0xC9uy; 0xBEuy; 0xB5uy; 0x74uy; 0x5Auy; 0x63uy; 0x99uy; 0x5Cuy; 0xCBuy; 0x82uy; 0xD7uy; 0x0Cuy; 0xBCuy; 0x37uy|]
let agentPubKey = new X25519PublicKeyParameters(rawAgentPubKey, 0)
let secret = Array.zeroCreate<Byte>(32)
let privateKey = new X25519PrivateKeyParameters(rawPrivKey, 0)
privateKey.GenerateSecret(agentPubKey, secret, 0)
Console.WriteLine("SECRET: {{0x{0}}}", BitConverter.ToString(secret).Replace("-", ", 0x"))
在服务器上执行的输出是:
SECRET: {0xE2, 0x2B, 0xC6, 0x3A, 0xA0, 0x75, 0x83, 0x60, 0xB8, 0xE1, 0x47, 0xD6, 0x66, 0x24, 0x14, 0xC2, 0x99, 0x51, 0x05, 0x3C, 0xDC, 0x96, 0x2B, 0xC4, 0xE2, 0x10, 0x7C, 0x77, 0xC0, 0xA2, 0xD1, 0x77}
两次生成的密文明显不同。通过阅读各种示例,可能是由于不同的字节顺序编码。我尝试使用方法 mbedtls_mpi_read_binary_le 和 mbedtls_mpi_write_binary_le,但没有任何运气。
作为替代解决方案,如果此更改可以解决问题,我可以更改 .NET 库并移动到另一个库。不幸的是,此时我无法找到一个好的 .NET 替代品。
Curve25519 表示小端顺序的键。 X25519(带有 Curve25519 的 ECDH)代表小端顺序的共享秘密。这与密码学中使用的大多数标准格式不同,特别是 SECP/NIST 和 Brainpool 曲线上的密钥以及 ECDH 与 Weierstrass 曲线的共享秘密,后者表示大端数字中的数字。因此,将对 mbedtls_mpi_write_binary
的两个调用更改为 mbedtls_mpi_write_binary_le
.
或者,使用 mbedtls_ecp_point_write_binary
导出 public 密钥并使用 mbedtls_ecdh_calc_secret
计算共享密钥:它们负责为每条曲线格式化数字,使其具有正确的字节顺序。
我还没有确认这是唯一的问题。
在 .NET 端,密钥对生成正确,但 public 和私钥导出不正确。它们必须按如下方式确定(在 C# 中):
// generate public and private key
var keyGenerator = new X25519KeyPairGenerator();
keyGenerator.Init(new X25519KeyGenerationParameters(new SecureRandom()));
var keys = keyGenerator.GenerateKeyPair();
var publicKey = (X25519PublicKeyParameters)keys.Public;
Console.WriteLine("PUB: {{0x{0}}}", BitConverter.ToString(publicKey.GetEncoded()).Replace("-", ", 0x"));
var privateKey = (X25519PrivateKeyParameters)keys.Private;
Console.WriteLine("PRIV: {{0x{0}}}", BitConverter.ToString(privateKey.GetEncoded()).Replace("-", ", 0x"));
并且正如在 generate_curve25519_keys()
中的 C/C++ 代码中 mbedtls_mpi_write_binary()
必须替换为 mbedtls_mpi_write_binary_le()
。同样,在 generate_curve25519_shared_secret()
中,两个 mbedtls_mpi_read_binary()
都必须替换为 mbedtls_mpi_read_binary_le()
。
通过这些更改,两个代码在我的机器上生成相同的共享密钥。