Общий секрет ECDH, полученный из OpenSSL и BouncyCastle, не всегда одинаков, хотя константы и параметры домена одинаковы для обоих
я пытаюсь реализовать криптографию AES между приложением iOS и сервлетом java. Сервлет Java использует библиотеку BouncyCastle, а приложение iOS использует OpenSSL. Хотя я использовал ту же пару открытых/закрытых ключей и параметры домена для обеих сторон, общий секрет, генерируемый OpenSSL, иногда отличается от того, что генерирует BouncyCastle на стороне сервера.
процедура следующая;
- пара открытый/закрытый ключ, сгенерированная на сервере с указанным домен
параметры (скажем
server_public_key
,server_private_key
) -
server_public_key
встроен в приложение iOS в видеEC_POINT
X и Y - во время выполнения приложение iOS генерирует свою собственную пару открытых / закрытых ключей (скажем
client_key_curve
что этоEC_KEY
), и - после нагрузки
server_public_key
и вычисляет общий секрет (key_agreement
) на основе server_public_key и client_key_curve, и - затем
client_public_key
(взято изclient_key_curve
), а также зашифрованное сообщение симметрично используя производный общий секрет (key_agreement
) отправляются на сервер - на стороне сервера общий секрет снова вычисляется с помощью
client_public_key
и параметры сервера ECDH которые эти же как сторона клиента, и - затем зашифрованное сообщение расшифровывается с помощью вычисляемого
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) используется с обеих сторон, но результаты не всегда тот же.
правка 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
is client_public_key
который преобразуется в массив байтов. Ожидаемым результатом является то, что server_calculatd_symmetric_key
равна symmetricKey
за все время. Но они не всегда одинаковы.
правка 2: В качестве обратной связи с ответом @PeterDettman я внес некоторые изменения, чтобы отразить его предложение и хотя скорость неравенство уменьшается, генерируемые ключевые соглашения (общий секрет) с обеих сторон не во всех случаях равны.
можно воспроизвести один из случаев неравенства со следующими данными
- публичный ключ : 02E05C058C3DF6E8D63791660D9C5EA98B5A0822AB93339B0B8815322131119C4C
- приватный ключ : 062E8AC930BD6009CF929E51B37432498075D21C335BD00086BF68CE09933ACA
- сгенерированный общий секрет OpenSSL : 51d027264f8540e5d0fde70000000000
- сгенерированный общий секрет BouncyCastle : 51d027264f8540e5d0fde700e5db0fab
Итак, есть ли какая-либо ошибка в реализованном коде или процедуре?
спасибо
1 ответов
существует проблема в коде сервера, в том, как значение соглашения ECDH преобразуется в байты:
byte[] keyAgreement = agree.calculateAgreement(client_public_key).toByteArray();
попробуйте это вместо этого:
BigInteger agreementValue = agree.calculateAgreement(client_public_key);
byte[] keyAgreement = BigIntegers.asUnsignedByteArray(agree.getFieldSize(), agreementValue);
Это обеспечит массив байтов фиксированного размера в качестве вывода, что является требованием для преобразования элементов поля EC в октетные строки (поиск "элемент поля в примитив преобразования Октетных строк" для получения более подробной информации).
Я рекомендую вам игнорировать часть вывода ключа SHA1, пока вы не сможете получить этот Java keyAgreement массив байтов точно соответствует входу в вашу функцию KDF1_SHA1.