我正在使用OpenSSL椭圆曲线Diffie-Hellman解释的openssl EVP方法创建ECDH
一切工作都期望我从哪里获得另一个对等方的公钥并基于它生成EVP_PKEY*。
在给定的链接中,它忽略了解码其他对等方公钥的实现细节,并使用了get_peerkey伪函数:
/* 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);
在我的实现中,另一个对等方的公钥被接收并存储在public key2中,其大小存储在pub_len2
size_t pub_len2 = 0;
const unsigned char *publickey2 = get_public_key(&pub_len2);
然后我尝试使用以下代码创建EVP_PKEY*:
EVP_PKEY *pkey3=NULL;
pkey3 = d2i_PublicKey(EVP_PKEY_EC, &pkey3, (const unsigned char **)&publickey2, pub_len2);
if(pkey3 == NULL) {
ERR_print_errors_fp(stderr);
}
但是pkey3总是为空,并给我以下错误!
139898837907104:error:10098043:elliptic curve routines:o2i_ECPublicKey:passed a null parameter:ec_asn1.c:1389:
139898837907104:error:0D09B00D:asn1 encoding routines:d2i_PublicKey:ASN1 lib:d2i_pu.c:123:
传递空参数错误。
你们有什么想法吗?
编辑:
我已经通过使用pkey3=d2i_PUBKEY(NULL,(const无符号字符**)解决了这个问题
但是我遇到了另一个问题,主要问题是从这些公钥计算得出的秘密在两个对等点是不同的!
这是我的秘密推导过程,在两个对等点中是相同的,并且从提供的链接中完全复制和粘贴(经过一些修改),我对修改的原因做了一些评论:
unsigned char *derive_secret(EVP_PKEY *pkey,
const unsigned char *peer_key,
size_t peerkey_len, size_t *secret_len)
{
EVP_PKEY_CTX *ctx;
unsigned char *secret;
//MY modification to get peer key of peer_key buffer.
//peer_key is created by i2d_PUBKEY at peer side and
//received by network and is passed here
EVP_PKEY *peerkey = d2i_PUBKEY(NULL, &peer_key, peerkey_len);
//I also set the group of newly created EC
EC_KEY_set_group(EVP_PKEY_get1_EC_KEY(peer_key),
EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1));
//I also set the CONVERSION format to make sure!
EC_KEY_set_conv_form(EVP_PKEY_get1_EC_KEY(peer_key), POINT_CONVERSION_COMPRESSED);
/* Create the context for the shared secret derivation */
if(NULL == (ctx = EVP_PKEY_CTX_new(pkey, NULL))) handleErrors();
/* Initialise */
if(1 != EVP_PKEY_derive_init(ctx)) handleErrors();
/* Provide the peer public key */
if(1 != EVP_PKEY_derive_set_peer(ctx, peerkey)) handleErrors();
/* Determine buffer length for shared secret */
if(1 != EVP_PKEY_derive(ctx, NULL, secret_len)) handleErrors();
/* Create the buffer */
if(NULL == (secret = OPENSSL_malloc(*secret_len))) handleErrors();
/* Derive the shared secret */
if(1 != (EVP_PKEY_derive(ctx, secret, secret_len))) handleErrors();
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(peerkey);
EVP_PKEY_free(pkey);
/* Never use a derived secret directly. Typically it is passed
* through some hash function to produce a key */
return secret;
}
我还打印出了发送和接收的公钥,并确保公钥是在一块接收的,与他们创建和发送的完全相同!
还有一个小问题是,当我发现一个长字节的公钥对两个对等点是相同的!这对于ECDiffie-Hellman的公钥来说是正常的吗?
以下是HEX格式显示的公钥
Peer1:
3082010A3081E306072A8648CE3D02013081D7020101302C06072A8648CE3D0101022100FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF305B0420FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC04205AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B031500C49D360886E704936A6678E1139D26B7819F7E900421036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296022100FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551020101032200026B0E07FE6177D23B0E6B776CF4CB0569735159D3261767FA5FC0A4636EF310C4
Peer2:
3082010A3081E306072A8648CE3D02013081D7020101302C06072A8648CE3D0101022100FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF305B0420FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC04205AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B031500C49D360886E704936A6678E1139D26B7819F7E900421036B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296022100FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC6325510201010322000208AE9F32ECE16072428A5FC875A19B3913C4516419917E723AA4C2DC20105C0A
一些字节(前缀)相同:X.509格式的所有公钥(更确切地说,SubjectPublicKeyInfo)至少有一个“对象标识符”(OID),对于给定算法的所有密钥(如EC)都是相同的,EC密钥也有指定组/曲线的“参数”,这对于同一曲线上的所有密钥都是相同的——ECDH协议的密钥必须在同一曲线上。这种相同的数据,加上实际的pubkey具有不同的值但大小相同的事实,导致ASN.1DER编码以相同的字节开头。
您发布的pubkey编码使用了大部分过时的显式曲线规范,参见rfc3279 sec 2.3.5(相当于X9.62或SEC1),它比现在首选且通常需要的“命名”规范长得多。猜测一下,您使用低于1.1.0的OpenSSL库来生成这些密钥,并且在使用i2d(或PEM_write)序列化(又名编码)之前没有在EC_GROUP对象(或EC_KEY的组子对象)中设置asn1_flag。您引用的wiki页面在第3节“ECDH和命名曲线”中涵盖了这一点,尽管它只在这也适用于公钥和证书时提到私钥——但是公钥和(然后)证书是从私钥派生的,所以在私钥上设置asn1_flag就足够了。它并没有说“命名”现在是1.1.0中的默认值,不再需要显式设置。
您新发布的代码:point_format只有在序列化(i2d或PEM_write)时才有意义,因此将其设置在只会使用和释放(而不是保留)的反序列化键上是无用的。OTOH将EC组设置为其现有值(从反序列化中设置)是无用的,但将其设置为任何其他值都会导致混乱。EC公钥是特定曲线上的点,不同的曲线有完全不同的点——一条曲线上的点不是另一条曲线上的点。此外,使用get1函数而不释放结果会泄漏内存。
推导结果不同:这对(EC)DH来说是非常错误的,我无法重现它。下面是您的推导代码,其中包含上面提到的一些修复和一些与我的编码风格相匹配的微小更改,加上来自wiki的生成代码和一个驱动它们的微不足道的主代码,当我运行它时,我得到了具有公共前缀的pubkey(由于使用命名形式而更短),但正如预期的那样,推导结果相同:
$ cat SO48130343.c
/* SO48130343 */
#include <stdio.h>
#include <openssl/opensslv.h>
#include <openssl/evp.h>
#include <openssl/x509.h>
#include <openssl/ec.h>
#include <openssl/err.h>
void hex (unsigned char *p, size_t n){ while(n--) printf("%02x", *p++); }
void err (const char * msg){ fprintf(stderr, "%s:\n", msg); ERR_print_errors_fp(stderr); exit(1); }
EVP_PKEY * gen (void) {
EVP_PKEY_CTX *pctx, *kctx;
EVP_PKEY *params = NULL, *pkey = NULL;
if( NULL == (pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL)) ) err("CTX1_new");
if( 1 != EVP_PKEY_paramgen_init(pctx) ) err("pg_init");
if( 1 != EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pctx, NID_X9_62_prime256v1) ) err("pg_curve");
if( 1 != EVP_PKEY_paramgen(pctx, ¶ms) ) err("pg");
if( NULL == (kctx = EVP_PKEY_CTX_new(params, NULL)) ) err("CTX2_new");
if( 1 != EVP_PKEY_keygen_init(kctx) ) err("kg_init");
if( 1 != EVP_PKEY_keygen(kctx, &pkey) ) err("kg");
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
EC_KEY_set_asn1_flag (pkey->pkey.ec, OPENSSL_EC_NAMED_CURVE);
/* point format needed before 'sending' and this is convenient */
EC_KEY_set_conv_form (pkey->pkey.ec, POINT_CONVERSION_COMPRESSED);
#else
/* asn1_flag now default but point format still needed */
EC_KEY_set_conv_form (EVP_PKEY_get0_EC_KEY (pkey), POINT_CONVERSION_COMPRESSED);
#endif
EVP_PKEY_CTX_free(pctx);
EVP_PKEY_CTX_free(kctx);
EVP_PKEY_free(params);
return pkey;
}
unsigned char * derive (EVP_PKEY * self,
const unsigned char * peer_ptr, size_t peer_len, size_t *len_ptr){
EVP_PKEY * peer = d2i_PUBKEY (NULL, &peer_ptr, peer_len);
/* DON'T change EC_GROUP; point_format not needed on 'receive' */
EVP_PKEY_CTX *ctx; unsigned char * buf_ptr;
if( !(ctx = EVP_PKEY_CTX_new (self, NULL)) ) err("CTX_new");
if( 1 != EVP_PKEY_derive_init(ctx) ) err("derive_init");
if( 1 != EVP_PKEY_derive_set_peer(ctx, peer) ) err("derive_peer");
if( 1 != EVP_PKEY_derive (ctx, NULL, len_ptr) ) err("derive1");
if( !(buf_ptr = OPENSSL_malloc (*len_ptr)) ) err("malloc");
if( 1 != EVP_PKEY_derive (ctx, buf_ptr, len_ptr) ) err("derive2");
EVP_PKEY_CTX_free(ctx);
EVP_PKEY_free(peer);
return buf_ptr;
}
int main (void){
EVP_PKEY * pkey1 = gen(), * pkey2 = gen();
unsigned char pub1 [100], pub2 [100], *ptr1 = &pub1[0], *ptr2 = &pub2[0];
size_t publen1 = i2d_PUBKEY (pkey1, &ptr1), publen2 = i2d_PUBKEY (pkey2, &ptr2);
printf ("pub1="); hex(pub1, publen1); putchar('\n');
printf ("pub2="); hex(pub2, publen2); putchar('\n');
size_t len1, len2;
unsigned char * out1 = derive (pkey1, pub2, publen2, &len1);
unsigned char * out2 = derive (pkey2, pub1, publen1, &len2);
printf ("prv1/pub2="); hex(out1, len1); putchar('\n');
printf ("prv2/pub1="); hex(out2, len2); putchar('\n');
/* don't bother freeing for Q&D test code */
return 0;
}
$ gcc [details for my system redacted]
$ ./SO48130343.exe
pub1=3039301306072a8648ce3d020106082a8648ce3d03010703220003302c6f990445ddd27b2c0ecd3a0cd33109eec44dea0edd538c6bfc98796885e3
pub2=3039301306072a8648ce3d020106082a8648ce3d0301070322000311940ba32c0b4d71f8785a884f7ea74cebed17e841e93a0fb1ccbeac32b2eb3b
prv1/pub2=84b7a84249f1e88741a751a05d34a43e4cb131e012181967e4f465c1f4bf3b35
prv2/pub1=84b7a84249f1e88741a751a05d34a43e4cb131e012181967e4f465c1f4bf3b35