ios - 源自 OpenSSL 和 BouncyCaSTLe 的 ECDH 共享 secret 并不总是相同,尽管两者的常量和域参数相同

标签 ios openssl bouncycastle elliptic-curve

我正在尝试在 iOS 应用程序和 java servlet 之间实现 AES 加密。 Java servlet 使用 BouncyCaSTLe 库,而 iOS 应用程序使用 OpenSSL。尽管我在双方都使用了相同的公钥/私钥对和域参数,但 OpenSSL 生成的共享 key 有时与 BouncyCaSTLe 在服务器端生成的共享 key 不同。

程序如下;

  1. 在具有指定域的服务器中生成的公钥/私钥对 参数(例如 server_public_keyserver_private_key)
  2. server_public_keyEC_POINT X 的形式嵌入到 iOS 应用中 和 Y
  3. 在运行时 iOS 应用生成自己的公钥/私钥对(比如 client_key_curve 是一个 EC_KEY),以及
  4. 然后加载server_public_key 并计算共享 key (key_agreement) 基于 server_public_key 和 client_key_curve,以及
  5. 然后 client_public_key(从 client_key_curve 中提取)以及 使用对称加密的加密消息 派生的共享 secret (key_agreement)被发送到服务器
  6. 在服务器端,共享 key 再次使用 client_public_key 和服务端ECDH参数相同 客户端,和
  7. 然后使用计算的key_agreement解密加密消息

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

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

- (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);
}

_clientPublicKey_symmetricKey 在类级别声明

两侧使用相同的曲线(命名为 prime256v1 或 secp256r1),但结果并不总是相同。

编辑 1: 作为对@PeterDettman 的回应,我发布了服务器端代码以获得更多说明

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;
    }
}

其中client_public_key_hex是转换为字节数组的client_public_key。预期结果是 server_calculatd_symmetric_key 始终等于 symmetricKey。但它们并不总是相同的。

编辑 2: 作为对@PeterDettman 回答的反馈,我做了一些更改以反射(reflect)他的建议,尽管不平等率有所降低,但双方生成的 key 协议(protocol)(共享 secret )并非在所有情况下都相同。

可以用以下数据重现不等式之一

  • 公钥: 02E05C058C3DF6E8D63791660D9C5EA98B5A0822AB93339B0B8815322131119C4C
  • 私钥: 062E8AC930BD6009CF929E51B37432498075D21C335BD00086BF68CE09933ACA
  • OpenSSL 生成的共享 key :51d027264f8540e5d0fde70000000000
  • 由 BouncyCaSTLe 生成的共享 key : 51d027264f8540e5d0fde700e5db0fab

那么实现的代码或者流程有没有错误呢?

谢谢

最佳答案

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

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

试试这个:

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

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

我建议您忽略 SHA1 key 派生部分,直到您可以使 Java keyAgreement 字节数组与您的 KDF1_SHA1 函数的输入完全匹配。

关于ios - 源自 OpenSSL 和 BouncyCaSTLe 的 ECDH 共享 secret 并不总是相同,尽管两者的常量和域参数相同,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15430784/

相关文章:

ios - 读取 info.plist : inferred to have type AnyObject which may be unexpected

PHP 将 MCRYPT_ENCRYPT 转换为 OPENSSL_ENCRYPT(SOAP header )

encryption - Bouncy CaSTLe 的加密级别是多少

android - key 工具错误 : java. lang.ClassNotFoundException : org. bouncycaSTLe.jce.provider.BouncyCaSTLeProvider

ios - 当我触摸 objective-c 中的标签时,如何根据标签的大小增加/减少矩形大小

ios - 获取音频选项并在内部切换它们以调用应用程序

android - 包装的 android-app 在恢复 "no current context"时崩溃

c++ - 将 DER 编码的 X509 证书缓冲区转换为 Windows CERT_CONTEXT 结构

openssl - 如何在 64 位系统上以 32 位模式编译 openSSL?

java - 如何使用 ECDH 加密明文?