Общий секрет ECDH не совпадает между Crypto++ и Android

Итак, я пишу реализацию ECDH на Android, используя Java и библиотеку Crypto++ 5.6.3.

Я написал некоторый код C++ JNI для вызова функций Crypto++, у меня есть одна функция для создания пары открытого/закрытого ключа и другая функция для извлечения общего секрета. Однако существует проблема с несовпадением общих секретов.

Ситуация следующая. Алиса и Боб генерируют свои собственные пары открытых и закрытых ключей. Они успешно обмениваются открытыми ключами.

Чтобы получить общий секрет, Алиса делает следующее:

byte[] sharedSecret = getSharedSecret(bobPublicKey, alicePrivateKey);

Боб делает аналогичную операцию:

byte[] sharedSecret = getSharedSecret(alicePublicKey, bobPrivateKey);

Проблема, которую я вижу, заключается в том, что два общих секрета не соответствуют друг другу. Есть ли какое-то непонимание с моей стороны о том, как это должно работать?

Я предполагаю, что на моей стороне есть только конкретная проблема реализации, связанная с общим секретом, но я не уверен. Реализация C++ JNI приведена ниже. Функция retrieveSharedSecret всегда выводит «Это сработало». Любые идеи о том, что я делаю неправильно здесь?

JNIEXPORT jobject JNICALL Java_com_myproject_test_cryptopp_ECDHLibrary_generateKeyPair
        (JNIEnv *env, jclass)
{
    // Generate a public private key pair using ECDH (Elliptic Curve Diffie Hellman)
    OID CURVE = secp256r1(); // the key is 256 bits (32 bytes) long
    AutoSeededRandomPool rng;

    // Because we are using point compression
    // Private Key 32 bytes
    // Public Key 33 bytes
    // If compression was not used the public key would be 65 bytes long
    ECDH < ECP >::Domain dhA( CURVE );
    dhA.AccessGroupParameters().SetPointCompression(true);

    SecByteBlock privA(dhA.PrivateKeyLength()), pubA(dhA.PublicKeyLength());
    dhA.GenerateKeyPair(rng, privA, pubA);

    jobject publicKeyByteBuffer = (*env).NewDirectByteBuffer(pubA.BytePtr(), pubA.SizeInBytes());
    jobject privateKeyByteBuffer = (*env).NewDirectByteBuffer(privA.BytePtr(), privA.SizeInBytes());

    // Return the ECDH Key Pair back as our custom Java ECDHKeyPair class object
    jclass keyPairClass = (*env).FindClass("com/myproject/test/cryptopp/ECDHKeyPair");
    jmethodID midConstructor = (*env).GetMethodID(keyPairClass, "<init>", "(Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;)V");
    jobject keyPairObject = (*env).NewObject(keyPairClass, midConstructor, publicKeyByteBuffer, privateKeyByteBuffer);

    return keyPairObject;
}

JNIEXPORT jobject JNICALL Java_com_myproject_test_cryptopp_ECDHLibrary_retrieveSharedSecret
        (JNIEnv *env, jclass, jbyteArray publicKeyArray, jbyteArray privateKeyArray)
{
    // Use the same ECDH Setup that is specified in the generateKeyPair method above
    OID CURVE = secp256r1();
    DL_GroupParameters_EC<ECP> params(CURVE);
    ECDH<ECP>::Domain dhAgreement(params);
    dhAgreement.AccessGroupParameters().SetPointCompression(true);

    // Figure out how big the public and private keys are
    // Public Key: This belongs to the other user
    // Private Key: This is out personal private key
    int pubLen = (int)(*env).GetArrayLength(publicKeyArray);
    int privLen = (int)(*env).GetArrayLength(privateKeyArray);

    // Convert the keys from a jbyteArray to a SecByteBlock so that they can be passed
    // into the CryptoPP Library functions.
    unsigned char* pubData = new unsigned char[pubLen];
    (*env).GetByteArrayRegion(publicKeyArray, 0, pubLen, reinterpret_cast<jbyte*>(pubData));

    unsigned char* privData = new unsigned char[privLen];
    (*env).GetByteArrayRegion(privateKeyArray, 0, privLen, reinterpret_cast<jbyte*>(privData));

    SecByteBlock pubB(pubData, pubLen) , privA(privData, privLen);

    // Now extract shared secret between the two keys
    SecByteBlock sharedSecretByteBlock(dhAgreement.AgreedValueLength());
    ALOG("Shared Agreed Value Length: %d", dhAgreement.AgreedValueLength());

    bool didWork = dhAgreement.Agree(sharedSecretByteBlock, privA, pubB);

    ALOG("Key Agreement: %s", didWork ? "It Worked" : "It Failed");
    ALOG("Shared Secret Byte Size: %d", sharedSecretByteBlock.SizeInBytes());

    // Return the shared secret as a Java ByteBuffer
    jobject publicKeyByteBuffer = (*env).NewDirectByteBuffer(sharedSecretByteBlock.BytePtr(), sharedSecretByteBlock.SizeInBytes());

    return publicKeyByteBuffer;
}

EDIT: я разместил свой тестовый проект на Github здесь, чтобы другие можно посмотреть и попытать счастья. Содержит некоторые инструкции в README о том, как его настроить и запустить.


person T. Colligan    schedule 23.12.2015    source источник
comment
Это как бы приближается к нам... Я обратился к Дэвиду Хуку из Bouncy Castle и надеюсь, что скоро у пользователей появятся хорошие примеры взаимодействия Java/Crypto++.   -  person jww    schedule 24.12.2015
comment
А пока... // TODO: Выясните, насколько велики открытый и закрытый ключи... — взгляните на Эфемерный ключ как координата (x,y). Открытый/закрытый ключ, которым обмениваются пользователи, и эфемерные ключи, используемые при обмене, похожи, но имеют разные форматы. Оба имеют кодировку ASN.1. Первые (статические ключи) являются субъектным {открытым|закрытым} ключом с OID. Последние (эфемерные ключи) — это просто внутренние {публичные|приватные}. Используйте дампер ASN.1, например dumpasn1 Гутмана, для их просмотра.   -  person jww    schedule 24.12.2015
comment
Извините, что мне потребовалось так много времени, чтобы опубликовать ответ. Честно говоря, я новичок во всем этом, и я понятия не имею, как использовать этот дампер ASN.1. Что я могу сказать, основываясь на своем собственном Java-коде прямо сейчас, так это то, что мои открытые ключи имеют размер 33 байта, а закрытые ключи — 32 байта. Вот пример в шестнадцатеричном формате: Открытый ключ пары ключей Алисы: 03E81B8252352EBE8EF941A8D01260066D2351D90D32C2005C4DF7F1DDF7EC8C74 Закрытый ключ: 560ECB9D777359C5FF6B8E5FBA21D57AA01C9DD308F6FD40   -  person T. Colligan    schedule 03.01.2016


Ответы (1)


Я смог понять это с помощью друга. Проблема заключалась в методе retrieveSharedSecret и в том факте, что он напрямую возвращал байтовый буфер, который указывал на адрес памяти, который был в области видимости во время вызова метода C++, но затем вышел из области видимости, как только он вернулся в код Java. . Таким образом, я, по сути, получал мусорную память в качестве своего общего секрета.

Я изменил код, чтобы метод возвращал пользовательский объект SharedSecret Java, как это делает метод keyGeneration. Это позволяет правильно копировать всю информацию, которая мне нужна, и мне не нужно беспокоиться об этой проблеме с областью действия.

Пересмотренный код метода приведен ниже. Я также обновлю проект Github, чтобы он мог существовать в качестве рабочего примера использования Android. Студия с НДК (неэкспериментальная) и КриптоПП.

// Use the same ECDH Setup that is specified in the generateKeyPair method above
OID CURVE = secp256r1();
DL_GroupParameters_EC<ECP> params(CURVE);
ECDH<ECP>::Domain dhAgreement(params);
dhAgreement.AccessGroupParameters().SetPointCompression(true);

// Figure out how big the public and private keys are
// Public Key: This belongs to the other user
// Private Key: This is out personal private key
int pubLen = (int)(*env).GetArrayLength(publicKeyArray);
int privLen = (int)(*env).GetArrayLength(privateKeyArray);

// Convert the keys from a jbyteArray to a SecByteBlock so that they can be passed
// into the CryptoPP Library functions.
unsigned char* pubData = new unsigned char[pubLen];
(*env).GetByteArrayRegion(publicKeyArray, 0, pubLen, reinterpret_cast<jbyte*>(pubData));

unsigned char* privData = new unsigned char[privLen];
(*env).GetByteArrayRegion(privateKeyArray, 0, privLen, reinterpret_cast<jbyte*>(privData));

SecByteBlock pubB(pubData, pubLen) , privA(privData, privLen);

// Now extract shared secret between the two keys
SecByteBlock sharedSecretByteBlock(dhAgreement.AgreedValueLength());
ALOG("Shared Agreed Value Length: %d", dhAgreement.AgreedValueLength());

bool didWork = dhAgreement.Agree(sharedSecretByteBlock, privA, pubB);

ALOG("Key Agreement: %s", didWork ? "It Worked" : "It Failed");
ALOG("Shared Secret Byte Size: %d", sharedSecretByteBlock.SizeInBytes());

// Return the shared secret as a Java ByteBuffer
jobject sharedSecretByteBuffer = (*env).NewDirectByteBuffer(sharedSecretByteBlock.BytePtr(), sharedSecretByteBlock.SizeInBytes());

// Return the ECDH Key Pair back as a Java ECDHKeyPair object
jclass keyPairClass = (*env).FindClass("com/tcolligan/ecdhtest/SharedSecret");
jmethodID midConstructor = (*env).GetMethodID(keyPairClass, "<init>", "(Ljava/nio/ByteBuffer;)V");
jobject sharedSecretObject = (*env).NewObject(keyPairClass, midConstructor, sharedSecretByteBuffer);

return sharedSecretObject;
person T. Colligan    schedule 04.01.2016
comment
Отличная работа. Я собираюсь начать цитировать это до тех пор, пока не получу достойный пример, размещенный в Интернете. - person jww; 18.01.2016