ECDH 共享密钥在 Crypto++ 和 Android 之间不匹配

ECDH Shared Secret does not match between Crypto++ and Android

所以我正在使用 Java 和 Crypto++ 5.6.3 库在 Android 上编写 ECDH 实现。

我写了一些 C++ JNI 代码来调用 Crypto++ 函数,我有一个函数可以生成 public/private 密钥对,另一个函数可以提取共享密钥。但是似乎存在共享机密不匹配的问题。

情况如下。 Alice 和 Bob 都生成自己的 Public 和私钥对。他们成功地交换了 public 个密钥。

要获取共享秘密,Alice 会执行以下操作:

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

Bob 做了类似的操作:

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

我看到的问题是,两个共享机密彼此不匹配。我对这应该如何工作有什么误解吗?

我假设我这边只有一个与共享机密相关的具体实施问题,但我不确定。 C++ JNI 实现如下。 retrieveSharedSecret 函数总是输出 "It Worked"。关于我在这里做错了什么有什么想法吗?

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

编辑: 我把我的测试项目放在Github here上,这样其他人可以看看并试试自己的运气。在 README 中包含一些关于如何启动它的说明和 运行.

我在朋友的帮助下弄明白了。问题是 retrieveSharedSecret 方法以及它直接返回一个字节缓冲区的事实,它指向一个内存地址,该地址在 C++ 方法调用期间在范围内,但一旦返回到 Java代码。所以我基本上是把垃圾内存作为我的共享秘密。

我调整了代码,使方法 returns 成为自定义 SharedSecret Java 对象,就像 keyGeneration 方法一样。这样做可以让我正确复制我需要的所有信息,而不必担心这个范围问题。

修改后的方法代码如下。我还将更新 Github 项目,以便它可以作为一个工作示例存在,说明如何将 Android Studio 与 NDK (non-experimental) 和 CryptoPP.

一起使用
// 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;