2013-03-15 9 views
5

Я пытаюсь реализовать криптографию AES между приложением iOS и сервом Java. Java-сервлет использует библиотеку BouncyCastle, в то время как приложение iOS использует OpenSSL. Несмотря на то, что я использовал такие же парные и парные ключи и параметры домена для обеих сторон, общий секрет, генерируемый OpenSSL, иногда отличается от того, что генерируется BouncyCastle на стороне сервера.ECDH Общий секрет, полученный из OpenSSL и BouncyCastle, не всегда одинаковый, хотя константы и параметры домена одинаковы для обоих

Процедура заключается в следующем:

  1. публичный/приватный ключ пара генерируется в сервере с указанным доменным параметров (скажем server_public_key, server_private_key)
  2. server_public_key встроен в IOS приложение в виде EC_POINT X и Y
  3. на run- (например,), а
  4. затем загружает server_public_key и вычисляет общий секрет (key_agreement) на основе server_public_key и client_key_curve и
  5. затем client_public_key (извлекается из client_key_curve), а также зашифровано сообщение, которое шифруется симметрично с помощью , полученных совместно в секрете (key_agreement) посылаются на сервер
  6. в сервере - сторона, общий секрет снова вычисляются с использованием client_public_key и сервер ECDH параметров, которые являются такими же, как сторона клиента и
  7. затем зашифрованного сообщения расшифровано с помощью вычисляется key_agreement

НО дешифрованные сообщения не всегда совпадают с сообщениями, отправленными клиентом.

Поскольку я также разработал приложение для 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); 
} 

_clientPublicKey и _symmetricKey объявляются на уровне класса

той же кривой (названный prime256v1 или secp256r1) является используются с обеих сторон, но результаты не всегда одинаковы.

EDIT 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 за все время. НО они не всегда одинаковы.

EDIT 2: В обратной связи @PeterDettman ответа, я сделал некоторые изменения, чтобы отразить его предложение, и хотя скорость неравенства уменьшает, генерируемую ключевые соглашения (общий секрет) по обе стороне не все еще равны во всех случаях ,

можно воспроизвести одно из неравенств случае со следующими данными

  • Открытый ключ: 02E05C058C3DF6E8D63791660D9C5EA98B5A0822AB93339B0B8815322131119C4C
  • Приват ключ: 062E8AC930BD6009CF929E51B37432498075D21C335BD00086BF68CE09933ACA
  • Сформирован Shared Secret по OpenSSL: 51d027264f8540e5d0fde70000000000
  • Сформирован Shared Secret по BouncyCastle: 51d027264f8540e5d0fde700e5db0fab

Так есть ли ошибка в реализованном коде или процедурах?

Благодаря

+0

Разделяется ли общий секрет с обеих сторон одинаково? –

+0

@PeterDettman: Иногда рассчитывается Shared Secret с обеих сторон равны, а иногда нет (лучше сказать, большую часть времени они не равны). Как только я изменяю тип кривой EC от ** prime256v1 - secp256r1 ** до ** prime192v1 **, скорость неравенства вычисленного общего секрета уменьшается. Я имею в виду, что они с большей вероятностью будут равны на ** prime192v1 ** кривой, и я не знаю, почему это так. – anonim

+0

Возможно, вы можете показать код сервлета, который использует BouncyCastle, особенно часть, которая берет исходное значение соглашения и выводит из нее симметричный ключ. –

ответ

3

Там проблема в коде сервера, в том, что значение ECDH соглашение преобразуется в байтах:

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

Попробуйте вместо этого:

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

Это будет убедитесь, что массив байтов с фиксированным размером в качестве вывода, что является требованием для преобразования элементов поля EC в октетные строки (поиск «Элемент« Элемент поля »для примитива преобразования октетной строки для более d etails).

Я рекомендую вам игнорировать часть вывода ключей SHA1, пока вы не сможете получить этот массив байтов Java keyAgreement точно, чтобы в точности соответствовать входному сигналу функции KDF1_SHA1.

+0

Дорогой @PeterDettman, я последовал твоему предложению, и обратная связь была добавлена ​​к вопросу. Не могли бы вы проверить это? – anonim

+0

На самом деле я проигнорировал применение SHA на вычисленном соглашении с ключами на обеих сторонах в соответствии с вашим предложением, однако иногда они совершенно разные – anonim

+0

Пожалуйста, сообщите, что * input * отправляется в KDF1_SHA1 в приложении iOS и соответствующее значение keyAgreement на Java боковая сторона. –