当前位置: 首页 > 知识库问答 >
问题:

从OpenSSL和BouncyCastle派生的ECDH共享秘密并不总是相同的,尽管两者的常数和域参数相同

戚哲
2023-03-14
    null

但是解密的消息并不总是与客户端发送的消息相同。

由于我也开发了一个Android应用程序,它使用相同的过程,但使用BouncyCastle进行加密,因此我怀疑使用OpenSSL实现代码的正确性,因此在这里展示代码,以帮助其他人解决这个问题。我为计算共享秘密而实现的内容如下

- (void)calculateSharedSecret
{
    BN_CTX* bn_ctx;

    EC_KEY*     client_key_curve = NULL;
    EC_KEY*     server_key_curve = NULL;
    EC_GROUP*   client_key_group = NULL;
    EC_GROUP*   server_key_group = NULL;
    EC_POINT*   client_publicKey = NULL;
    EC_POINT*   server_publicKey = NULL;
    BIGNUM*     client_privatKey = NULL;

    BIGNUM* client_publicK_x = NULL;
    BIGNUM* client_publicK_y = NULL;
    BIGNUM* server_publicK_x = NULL;
    BIGNUM* server_publicK_y = NULL;

    NSException *p = [NSException exceptionWithName:@"" reason:@"" userInfo:nil];

    bn_ctx = BN_CTX_new();
    BN_CTX_start(bn_ctx);

    client_publicK_x = BN_CTX_get(bn_ctx);
    client_publicK_y = BN_CTX_get(bn_ctx);
    client_privatKey = BN_CTX_get(bn_ctx);
    server_publicK_x = BN_CTX_get(bn_ctx);
    server_publicK_y = BN_CTX_get(bn_ctx);

    // client

    if ((client_key_curve = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL)
        @throw p;

    if ((client_key_group = (EC_GROUP *)EC_KEY_get0_group(client_key_curve)) == NULL)
        @throw p;

    if (EC_KEY_generate_key(client_key_curve) != 1)
        @throw p;

    if ((client_publicKey = (EC_POINT *)EC_KEY_get0_public_key(client_key_curve)) == NULL)
        @throw p;

    if (EC_KEY_check_key(client_key_curve) != 1)
        @throw p;

    client_privatKey = (BIGNUM *)EC_KEY_get0_private_key(client_key_curve);

    char *client_public_key = EC_POINT_point2hex(client_key_group, client_publicKey, POINT_CONVERSION_COMPRESSED, bn_ctx);
    char *client_privat_key = BN_bn2hex(client_privatKey);

    _clientPublicKey = [NSString stringWithCString:client_public_key encoding:NSUTF8StringEncoding];

    // server

    NSArray* lines = [self loadServerPublicKeyXY];

    NSString *public_str_x = [lines objectAtIndex:0];
    NSString *public_str_y = [lines objectAtIndex:1];

    BN_dec2bn(&server_publicK_x, [public_str_x UTF8String]);
    BN_dec2bn(&server_publicK_y, [public_str_y UTF8String]);

    if ((server_key_curve = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)) == NULL)
        @throw p;

    if ((server_key_group = (EC_GROUP *)EC_KEY_get0_group(server_key_curve)) == NULL)
        @throw p;

    if (EC_KEY_generate_key(server_key_curve) != 1)
        @throw p;

    if ((server_publicKey = EC_POINT_new(server_key_group)) == NULL)
        @throw p;

    if (EC_POINT_set_affine_coordinates_GFp(server_key_group, server_publicKey, server_publicK_x, server_publicK_y, bn_ctx) != 1)
        @throw p;

    if (EC_KEY_check_key(server_key_curve) != 1)
        @throw p;

    unsigned char *key_agreement = NULL;
    key_agreement = (unsigned char *)OPENSSL_malloc(SHA_DIGEST_LENGTH);
    if (ECDH_compute_key(key_agreement, SHA_DIGEST_LENGTH, server_publicKey, client_key_curve, KDF1_SHA1) == 0)
        @throw p;
    _symmetricKey = [NSData dataWithBytes:key_agreement length:16];
}

void *KDF1_SHA1(const void *input, size_t inlen, void *output, size_t *outlen)
{
    if (*outlen < SHA_DIGEST_LENGTH)
        return NULL;
    else
        *outlen = SHA_DIGEST_LENGTH;
    return SHA1(input, inlen, output);
}
public byte[] generateAESSymmetricKey(byte[] client_public_key_hex) throws InvalidRequest{
    try {
        // ECDH Private Key as well as other prime256v1 params was generated by Java "keytool" and stored in a JKS file
        KeyStore keyStore = ...;
        PrivateKey privateKey = (PrivateKey) keyStore.getKey("keyAlias", "keyStorePassword".toCharArray());
        ECPrivateKeyParameters ecdhPrivateKeyParameters = (ECPrivateKeyParameters) (PrivateKeyFactory.createKey(privateKey.getEncoded()));

        ECCurve ecCurve = ecdhPrivateKeyParameters.getParameters().getCurve();
        ECDomainParameters ecDomainParameters = ecdhPrivateKeyParameters.getParameters();
        ECPublicKeyParameters client_public_key = new ECPublicKeyParameters(ecCurve.decodePoint(client_public_key_hex), ecDomainParameters);

        BasicAgreement agree = new ECDHBasicAgreement();
        agree.init(ecdhPrivateKeyParameters);
        byte[] keyAgreement = agree.calculateAgreement(client_public_key).toByteArray();

        SHA1Digest sha1Digest = new SHA1Digest();
        sha1Digest.update(keyAgreement, 0, keyAgreement.length);
        byte hashKeyAgreement[] = new byte[sha1Digest.getDigestSize()];
        sha1Digest.doFinal(hashKeyAgreement, 0);

        byte[] server_calculatd_symmetric_key = new byte[16];
        System.arraycopy(hashKeyAgreement, 0, server_calculatd_symmetric_key, 0, server_calculatd_symmetric_key.length);
        return server_calculatd_symmetric_key;
    } catch (Throwable ignored) {
        return null;
    }
}

编辑2:作为对@PeterDettman回答的反馈,我做了一些修改来反映他的建议,尽管不平等率降低了,但双方生成的密钥协议(共享秘密)并不是在所有情况下都是平等的。

有可能用以下数据再现一个不等式情况

  • 公钥:02e05c058c3df6e8d63791660d9c5ea98b5a0822ab93339b0b8815322131119c4c
  • privat密钥:062e8ac930bd6009cf929e51b37432498075d21c335bd00086bf68ce09933aca
  • 由OpenSSL生成共享秘密:51d027264f8540e5d0fde7000000000000
  • 由BouncyCastle生成共享机密:51d027264f8540e5d0fde700e5db0fab

共有1个答案

公孙胡媚
2023-03-14

服务器代码中ECDH协议值转换为字节的方式存在问题:

byte[] keyAgreement = agree.calculateAgreement(client_public_key).toByteArray();

请尝试以下操作:

BigInteger agreementValue = agree.calculateAgreement(client_public_key);
byte[] keyAgreement = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), agreementValue);

这将确保一个固定大小的字节数组作为输出,这是将EC字段元素转换为八位元组字符串的必要条件(搜索“字段元素到八位元组字符串转换原语”以获得更多详细信息)。

 类似资料:
  • 需要在给定的64字节公钥(他们的)和32字节的私钥(我们的)之间共享秘密。 我有由外部提供商生产的密钥(P-256 ECC代码)。公钥为 64 字节,私钥为 32 字节。 我不知道如何让BouncyCastle导入这些密钥。它似乎期望DER编码的密钥,但这不是我必须使用的。 我的期望是加密/密钥代码可以处理32和64字节的密钥数组。在处理嵌入式设备和其他代码空间有限的领域时,无法处理包装/DER/

  • 在阅读equals()和hashcode()时,我了解到,如果两个对象相等,那么它们的hashcode必须相等,反之亦然。 但是下面的例子并没有反映这一点。 如果是,则返回true,即使它们的哈希代码不同,这从打印和中可以明显看出。 有人能给我解释一下吗?

  • 问题内容: 拥有Java代码 该字符串将分配在相同的内存位置(或多倍): 是否多次启动同一程序(并行)执行? 可能的答案: 我目前是C#开发人员(尽管在上个千年中使用Java编程)。 我之所以问这个问题,是因为我相信.NET CLR和Java(JVM)之间是相同的,我希望得到.NET应用程序的答案(但由于经常遇到的“应用程序”池术语而引起疑问)。 答案是 字符串 实习生池由同一JVM或.NET C

  • 我在Java中有两个几乎相同的方法。唯一的区别是它们有不同的参数类型。它们使用泛型并返回输入参数的类型T。我怎样才能摆脱重复的代码?下面是我的两个方法。最后,它们都使用不同的类型调用Spring。否则,方法是相同的。

  • 问题内容: 我有我的数组数据,如下所示: 如何基于相同的“银行名称”求和“金额”。 我的数据应显示如下: 问题答案:

  • 本文向大家介绍在C ++中生成相同总和的对的最大数量,包括了在C ++中生成相同总和的对的最大数量的使用技巧和注意事项,需要的朋友参考一下 给我们一个整数数组。目的是在数组中找到最大对数,相加后将产生相同的总和。我们必须找到此类对的最大数量。 输入值 输出结果 说明-数字对的总和- 输入值 输出结果 说明-数字对的总和- 以下程序中使用的方法如下 整数数组Arr []用于存储整数。 整数“大小”存

  • 我使用OpenSSL(在C中)对文本进行签名,但是我的Java程序并不总是验证签名的消息(只有大约1/5得到验证)。有趣的是,https://kjur.github.io/jsrsasign/sample/sample-ecdsa.html没有证实其中任何一个: 曲线名称:secp256k1签名算法:SHA256with ECDSA 私钥 密钥 失败: 成功: 爪哇 C.