【问题标题】:ECDH Shared Secret does not match between Crypto++ and AndroidECDH 共享密钥在 Crypto++ 和 Android 之间不匹配
【发布时间】:2015-12-23 16:30:37
【问题描述】:

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

我编写了一些 C++ JNI 代码来调用 Crypto++ 函数,我有一个函数来生成公钥/私钥对,以及另一个函数来提取共享密钥。然而,共享机密不匹配似乎存在问题。

情况如下。 Alice 和 Bob 都生成自己的公钥和私钥对。他们成功交换了公钥。

为了获得共享秘密,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 中包含一些关于如何启动和运行它的说明。

【问题讨论】:

  • 这对我们来说有点快...我联系了 Bouncy Castle 的 David Hook,希望尽快为用户提供一些好的 Java/Crypto++ 互操作示例。
  • 同时... "// TODO: 弄清楚公钥和私钥有多大..." - 看看Ephemeral Key as (x,y) Coordinate。用户交换的公钥/私钥和交换中使用的临时密钥相似,但格式不同。两者都是 ASN.1 编码的。前者(静态密钥)是具有 OID 的主题 {public|private} 密钥。后者(临时密钥)只是内部的 {public|private}。使用 ASN.1 转储程序(例如 Gutmann 的 dumpasn1)来查看它们。
  • 抱歉,我花了这么长时间才回复。老实说,我对这一切都很陌生,我不知道如何使用那个 ASN.1 转储程序。根据我自己的 Java 代码,我现在可以判断的是,我的公钥是 33 个字节,而私钥是 32 个字节。这是一个十六进制示例: Alice 密钥对 公钥:03E81B8252352EBE8EF941A8D01260066D2351D90D32C2005C4DF7F1DDF7EC8C74 私钥:560ECB9D777359C5FF6B8E5FBA21D57AA01C9DD302FC898BFC8800000000000

标签: java android c++ java-native-interface crypto++


【解决方案1】:

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

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

修改后的方法代码如下。我还将更新 Github 项目,以便它可以作为如何将 Android Studio 与 NDK(非实验性)和 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;

【讨论】:

  • 干得好。我将开始引用这个,直到我能在网上找到一个像样的例子。
猜你喜欢
  • 1970-01-01
  • 2021-08-09
  • 2017-10-28
  • 2011-06-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-03
  • 2017-01-05
相关资源
最近更新 更多