8

iOSアプリとJavaサーブレットの間にAES暗号化を実装しようとしています。JavaサーブレットはBouncyCastleライブラリを使用し、iOSアプリはOpenSSLを使用します。両方の側で同じ公開/秘密鍵ペアとドメインパラメータを使用しましたが、OpenSSLによって生成された共有シークレットは、サーバー側のBouncyCastleによって生成されたものと異なる場合があります。

手順は次のとおりです。

  1. 指定されたドメインパラメータ(たとえばserver_public_keyserver_private_key)を使用してサーバーで生成された公開鍵と秘密鍵のペア
  2. server_public_keyEC_POINTXとYの形でiOSアプリに埋め込まれています
  3. 実行時に、iOSアプリは独自の公開鍵と秘密鍵のペアを生成します( client_key_curveこれはEC_KEY)、および
  4. 次に、server_public_keyとclient_key_curveに基づいてserver_public_key共有秘密( )をロードして計算し、key_agreement
  5. 次に、client_public_key(から抽出されたclient_key_curve)と、派生した共有シークレット()を使用して対称的に暗号化された暗号化メッセージがkey_agreementサーバーに送信されます。
  6. サーバー側では、共有シークレットは client_public_key、クライアント側と同じサーバーECDHパラメーターを使用して計算されます。
  7. 次に、計算されたものを使用して復号化された暗号化されたメッセージkey_agreement

ただし、復号化されたメッセージは、クライアントが送信したメッセージと常に同じであるとは限りません。

同じ手順を使用するが暗号化にBouncyCastleを使用するAndroidアプリも開発したので、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);
}

_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_hexclient_public_key、それはバイトの配列に変換されます。期待される結果は、常にserver_calculatd_symmetric_key等しいということですsymmetricKey。しかし、それらは常に同じではありません。

編集2:@PeterDettmanの回答へのフィードバックとして、彼の提案を反映するためにいくつかの変更を加えました。不平等の割合は減少しますが、どちらの側でも生成された鍵共有(共有秘密)はすべての場合でまだ同じではありません。

以下のデータで不平等ケースの1つを再現することが可能です

  • 公開鍵: 02E05C058C3DF6E8D63791660D9C5EA98B5A0822AB93339B0B8815322131119C4C
  • プライベートキー: 062E8AC930BD6009CF929E51B37432498075D21C335BD00086BF68CE09933ACA
  • OpenSSLによって生成された共有シークレット:51d027264f8540e5d0fde70000000000
  • BouncyCastleによって生成された共有シークレット: 51d027264f8540e5d0fde700e5db0fab

では、実装されたコードや手順に間違いはありますか?

ありがとう

4

1 に答える 1

5

ECDH合意値がバイトに変換される方法で、サーバーコードに問題があります。

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

代わりにこれを試してください:

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

これにより、出力として固定サイズのバイト配列が保証されます。これは、ECフィールド要素をオクテット文字列に変換するための要件です(詳細については、「フィールド要素からオクテット文字列への変換プリミティブ」を検索してください)。

そのJavakeyAgreementバイト配列がKDF1_SHA1関数への入力と完全に一致するようになるまで、SHA1キー派生部分を無視することをお勧めします。

于 2013-04-09T08:43:49.490 に答える