无法从 Java BouncyCastle ECDH 中的 MbedTLS 读取 public 密钥

Cannot read public key from MbedTLS in Java BouncyCastle ECDH

我正在尝试使用 BouncyCastle 在嵌入式设备 运行 mbedTLS 和 Java 之间执行 ECDH。当我比较生成的密钥长度时,我得到了 mbedTLS 生成的 66 字节密钥和 BC 生成的 65 字节密钥。附加伪代码:

KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", "BC");
kpg.initialize(256);
KeyPair localKp = kpg.generateKey();

ASN1Sequence sequence = DERSequence.getInstance(localKp.getPublic().getEncoded());
DERBitString subjectPublicKey = (DERBitString) sequence.getObjectAt(1);
byte[] encodedLocalPublicKey = subjectPublicKey.getBytes();
// encodedLocalPublicKey.length -> 65

MbedTLS:

mbedtls_ecdh_context ecdh;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_context entropy;
const char pers[] = "ecdh";

mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_entropy_init(&entropy);
mbedtls_ecdh_init(&ecdh);

int ret = 0;
if((ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
                               (const unsigned char *) pers,
                               sizeof pers )) != 0) {
    mbedtls_printf( " failed\n  ! mbedtls_ctr_drbg_seed returned %d\n", ret );
}

ret = mbedtls_ecp_group_load(&ecdh.grp, MBEDTLS_ECP_DP_SECP256R1);
if (ret != 0) {
    mbedtls_printf( " failed\n  ! mbedtls_ecp_group_load returned %d\n", ret );
}

size_t olen;
unsigned char buf[1024];
ret = mbedtls_ecdh_make_public(&ecdh, &olen, buf, sizeof(buf), mbedtls_ctr_drbg_random, &ctr_drbg);
// ret is 0, olen is 66

当我将 MbedTLS 密钥加载到 Java 时,它会抛出 java.lang.IllegalArgumentException:无效的点编码 0x41:

byte[] publicKeyBytes = ... FROM MbedTLS
log.info("Public key length: {}", publicKeyBytes.length); // Shows 66
ECParameterSpec ecSpec = ECNamedCurveTable.getParameterSpec("P-256");
ECPoint point = ecSpec.getCurve().decodePoint(publicKeyBytes); // This line throws

我尝试在 Java 之间执行 ECDH Java,在 MbedTLS 之间执行 MbedTLS。两个测试都成功了,但不知为何不能跨平台交换。

我做错了什么?抱歉可能是明显的问题,但我正在尝试处理它。我将不胜感激任何帮助。

谢谢。

来自 API documentation 对于 mbedtls_ecdh_make_public:

This function generates a public key and exports it as a TLS ClientKeyExchange payload.

所以这是一个特定于 TLS 的函数。来自例如RFC 8442,我们看到一个椭圆曲线点的编码是:

struct {
    opaque point <1..2^8-1>;
} ECPoint;

这需要对 TLS 表示语言有一定的了解,但最终它相当于:TLS 在通常点编码的开头添加一个额外的(无符号)字节,它将包含余数的长度。

在您的示例中,您可以看到 mbedtls 输出 66 个字节。第一个字节包含值 65,这是输出其余部分的长度,末尾的 65 个字节与 BC 使用的格式相同。您将需要提取最后 65 个字节,或以某种方式忽略第一个字节。

P.S。在 Java 中生成 KeyPair 的方式非常脆弱,因为您只指定了大小而不是确切的曲线。 BC 确实默认为 256 位的 P-256,但这不是必需的行为,其他提供商(或某些未来的 BC 版本)可能 选择其他曲线。最好显式生成 P-256 密钥对。我还建议使用更清晰的代码来获取 Java 版本中的 public 键编码:

byte[] spkiEnc = localKp.getPublic().getEncoded();
SubjectPublicKeyInfo spki = SubjectPublicKeyInfo.getInstance(spkiEnc);
byte[] encodedLocalPublicKey = spki.getPublicKeyData().getOctets();