生成EC Diffie-Hellman公钥和私钥对

5

我需要生成一个EC Diffie Hellman密钥对。我使用的是命名曲线secp256r1和OpenSSL。以下是目前我拥有的:

unsigned char *ecdh(size_t *secret_len)
{
    EVP_PKEY_CTX *pctx, *kctx;
    EVP_PKEY_CTX *ctx;
    unsigned char *secret;
    EVP_PKEY *pkey = NULL, *peerkey, *params = NULL;
    /* NB: assumes pkey, peerkey have been already set up */

    /* Create the context for parameter generation */
    if(NULL == (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL))) 
        printf("Error in EC key generation\n");

    /* Initialise the parameter generation */
    if(1 != EVP_PKEY_paramgen_init(pctx)) 
        printf("Error in EC key generation\n");

    /* We're going to use the ANSI X9.62 Prime 256v1 curve */
    if(1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1)) 
        printf("Error in EC key generation\n");

    /* Create the parameter object params */
    if (!EVP_PKEY_paramgen(pctx, &params)) 
        printf("Error in EC key generation\n");

    /* Create the context for the key generation */
    if(NULL == (kctx = EVP_PKEY_CTX_new(params, NULL))) 
        printf("Error in EC key generation\n");

    /* Generate the key */
    if(1 != EVP_PKEY_keygen_init(kctx)) 
        printf("Error in EC key generation\n");

    if (1 != EVP_PKEY_keygen(kctx, &pkey)) 
        printf("Error in EC key generation\n");

    /* Get the peer's public key, and provide the peer with our public key -
     * how this is done will be specific to your circumstances */
    peerkey = get_peerkey(pkey);

    /* Create the context for the shared secret derivation */
    if(NULL == (ctx = EVP_PKEY_CTX_new(pkey, NULL))) 
        printf("Error in EC key generation\n");

    /* Initialise */
    if(1 != EVP_PKEY_derive_init(ctx)) 
        printf("Error in EC key generation\n");

    /* Provide the peer public key */
    if(1 != EVP_PKEY_derive_set_peer(ctx, peerkey)) 
        printf("Error in EC key generation\n");

    /* Determine buffer length for shared secret */
    if(1 != EVP_PKEY_derive(ctx, NULL, secret_len)) 
        printf("Error in EC key generation\n");

    /* Create the buffer */
    if(NULL == (secret = OPENSSL_malloc(*secret_len))) 
        printf("Error in EC key generation\n");

    /* Derive the shared secret */
    if(1 != (EVP_PKEY_derive(ctx, secret, secret_len))) 
        printf("Error in EC key generation\n");

    EVP_PKEY_CTX_free(ctx);
    EVP_PKEY_free(peerkey);
    EVP_PKEY_free(pkey);
    EVP_PKEY_CTX_free(kctx);
    EVP_PKEY_free(params);
    EVP_PKEY_CTX_free(pctx);

    /* Never use a derived secret directly. Typically it is passed
     * through some hash function to produce a key */
    return secret;
} 

我发现为了使此函数起作用,我需要一个带有第二方公钥的EVP_KEY对象。我在一个字节数组中拥有这个公钥,以及它的长度。那么我该如何将其转换为所需的类型?另外我在OpenSSL中找不到secp256r1曲线,但是经过一些研究后,我使用了代码中的曲线。这样做正确吗?
谢谢!

我不是 OpenSSL 的专家,但我要警告你的一件事是实现自己的加密可能是危险的。例如,你不应该只是接收到任何密钥并相信它属于你打算通信的那个人:这会使你容易受到中间人攻击。因此,需要某种类型的身份验证来证明密钥属于声称拥有它的人。 - TheGreatContini
是的,实际上我现在发现密钥带有签名(我所说的是TLS中的服务器密钥交换记录),所以安全问题应该是好的 :) 不过我无法从签名中提取公钥。 - previouslyactualname
你的公钥长什么样?这个答案适用吗?https://dev59.com/AE7Sa4cB1Zd3GeqP0Q4q - Chiara Hsieh
2个回答

1
我知道这是一个大约8年前的问题,但是随着去年秋季OpenSSL 3的发布,我的答案可能对刚开始使用OpenSSL 3进行椭圆曲线Diffie-Hellman密钥交换的人有用。

原标题和正文问题不同。 基本上有3个问题:

  • 如何生成ECDH密钥?
  • 如何序列化/反序列化对等方公钥?
  • 使用什么EC命名曲线?

ECDH密钥生成(OpenSSL 3,纯C)

const char curveName[] = "...";

OSSL_PARAM_BLD* paramBuild = OSSL_PARAM_BLD_new();
if (paramBuild == NULL) {
  // Failed.
  return;
}

// Push the curve name to the OSSL_PARAM_BLD.
if (OSSL_PARAM_BLD_push_utf8_string(paramBuild,
    OSSL_PKEY_PARAM_GROUP_NAME, curveName, 0) != 1) {
  // Failed.
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Convert OSSL_PARAM_BLD to OSSL_PARAM.
OSSL_PARAM* params = OSSL_PARAM_BLD_to_param(paramBuild);
if (params == NULL) {
  // Failed.
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Create the EC key generation context.
EVP_PKEY_CTX* ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL);
if (ctx == NULL) {
  // Failed.
  OSSL_PARAM_free(params);
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Initialize the key generation context.
if (EVP_PKEY_keygen_init(ctx) <= 0) {
  // Failed.
  EVP_PKEY_CTX_free(ctx);
  OSSL_PARAM_free(params);
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Set the parameters which include the curve name.
if (EVP_PKEY_CTX_set_params(ctx, params) != 1) {
  EVP_PKEY_CTX_free(ctx);
  OSSL_PARAM_free(params);
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Generate a key pair.
EVP_PKEY* keyPair = NULL;
if (EVP_PKEY_generate(ctx, &keyPair) <= 0) {
  EVP_PKEY_CTX_free(ctx);
  OSSL_PARAM_free(params);
  OSSL_PARAM_BLD_free(paramBuild);
  return;
}

// Free auxiliary things...
EVP_PKEY_CTX_free(ctx);
OSSL_PARAM_free(params);
OSSL_PARAM_BLD_free(paramBuild);

// Now keyPair has the generated private and public keys.
// Use EVP_PKEY_free(keyPair) when you finish using them.

查看完整的C ++ OpenSSL 3示例:ec-diffie-hellman-openssl.h, ec-diffie-hellman-openssl.cpp

公钥序列化/反序列化

现在,您需要序列化Alice和Bob的公钥,将它们传输到另一方并将它们用于共享密钥派生。请参阅我的答案:如何在OpenSSL的EVP_PKEY结构中访问原始ECDH公钥、私钥和参数?

ECDH命名曲线

我建议即使你没有被要求这么做,在不影响性能(或者可能会带来 重大 性能问题)的情况下,遵循 FIPS 140-3。例如,FIPS 140-3和加密模块验证计划实施指南 中提到了NIST SP 800-56A rev3

问题/难题 在ECDSA签名算法和基于ECC的密钥协商方案的已批准操作模式中是否允许使用椭圆曲线?如果可以,使用它们的要求是什么?

解决办法 在SP 800-186中规定了用于ECDSA的椭圆曲线,如FIPS 186-5所实现的那样。 在SP 800-56Arev3的附录D中规定了用于基于ECC的密钥协商方案的椭圆曲线。

请参见NIST Special Publication 800-56A Revision 3附录D:已批准的ECC曲线和FFC安全质数群以获取批准的ECC曲线。
使用EC_get_builtin_curves来枚举OpenSSL支持的曲线。在这里查看ECDiffieHellmanOpenSSL::GetSupportedCurves方法的支持情况here

1
同行的公钥是曲线上的一个点。来自 crypto\ec\ec_lcl.h:
struct ec_key_st {
    int version;

    EC_GROUP *group;

    EC_POINT *pub_key;
    BIGNUM   *priv_key;

    unsigned int enc_flag;
    point_conversion_form_t conv_form;

    int     references;
    int flags;

    EC_EXTRA_DATA *method_data;
} /* EC_KEY */;

我认为你需要调用EC_POINT_new (c_lcl.h是私有头文件,因此您无法访问该结构)。

幸运的是,有很多函数可以操作它们。从EC_POINT_new(3)文档中可以看到:

EC_POINTs可以转换为各种外部表示形式,并从中转换。支持的表示形式包括八位字符串、BIGNUM和十六进制。外部表示的格式由point_conversion_form描述。有关point_conversion_form的说明,请参见EC_GROUP_copy(3)。八位字符串存储在一个缓冲区中,并带有相关联的缓冲区长度。通过将点转换为八位字符串,然后将该八位字符串转换为BIGNUM整数来计算在BIGNUM中保存的点。十六进制格式的点存储在一个以NULL结尾的字符字符串中,其中每个字符都是可打印值0-9或A-F(或a-f)之一。

此外还请参阅EC_POINT_set_affine_coordinates_GFpEC_POINT_set_affine_coordinates_GF2mEC_KEY_set_public_key
$ grep -R EC_KEY_set_public_key *
crypto/ec/ec.h:int EC_KEY_set_public_key(EC_KEY *key, const EC_POINT *pub);
...

您可以在OpenSSL维基上查看如何设置点的示例,链接为椭圆曲线密码学

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接