引言
最近基于openssl学习非对称加密,内部复杂的算法对本人来说也相当于盲盒,但是只要弄清楚它们的性质,就可以使用它们了。本文主要记录基于openssl如何使用secp256k1算法。
secp256k1概述
secp256k1就是方程:
y
2
y^{2}
y2=
x
3
x^3
x3 + 7 的一条曲线
有一个基点G = 04 79BE667E F9DCBBAC 55A06295 CE870B07 029BFCDB 2DCE28D9 59F2815B 16F81798 483ADA77 26A3C465 5DA4FBFC 0E1108A8 FD17B448 A6855419 9C47D08F FB10D4B8
04代表全坐标,后面32字节是X坐标,再32字节是Y坐标。
secp256k1算法自定义了一套加法和乘法, G点作为输入参数之一参与预算。具体怎么计算的,可以看一下这篇回答知乎–谁能最简单的详解椭圆曲线算法,secp256k1 是如何生成公钥和私钥的?
这个加法和乘法运算具备两个性质
- 不可逆(无减法和除法)
- 具备普通的交换律和结合律
使用时,需要先生成一个256bits的随机数作为私钥PrivKey。
那么就可以计算出公钥 PubKey = G * PrivKey
因为该乘法不可逆,得知PubKey无法推出私钥,只能暴力破解。
私钥是32字节随机数
公钥完整形态也是 04 x坐标(32Bytes) y坐标(32Bytes)
公钥压缩形态是 02 或03 x坐标(32Bytes)
签名
- 用私钥对摘要加密生成签名
签名是两个256bits大数的组合,一个r 一个s - 用公钥和摘要明文验证签名
验签是不可能解密出摘要的,而是公钥和摘要再加上签名里的s一起运算,最后得出的结果和r一致,就验签成功了。
秘钥交换
一般椭圆曲线都具备ECDH的功能,一种快速秘钥协商功能。
ECDH原理如下:
PubKey1 = G * PrivKey1
PubKey2 = G * PrivKey2
SK1 = PubKey2 * PrivKey1 = G * PrivKey2 * PrivKey1
SK2 = PubKey1 * PrivKey2 = G * PrivKey1 * PrivKey2
所以 SK1 = SK2
也就是说本地私钥和对方公钥计算出来的值和本地公钥和对方私钥计算出来的值一样,那么这个值就可以作为共享秘钥,可以用于后续的信息的对称加密。
代码示例
基于开源openssl 1.1.1g代码库
代码片段包括
- 私钥和公钥生成
- 秘钥的二进制输出和输入
- 签名和验证签名
- ECDH秘钥交换验证
EC_KEY *key;
EC_GROUP *group;
/* 构造EC_KEY数据结构 */
key=EC_KEY_new();
EC_KEY_set_conv_form(key, POINT_CONVERSION_COMPRESSED);
if(key==NULL)
{
printf("EC_KEY_new err!\n");
return -1;
}
/* 设置spec256k1的曲线group */
group = EC_GROUP_new_by_curve_name(NID_secp256k1);
if(group==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
/* 设置key的group属性 */
ret=EC_KEY_set_group(key, group);
if(ret!=1)
{
printf("EC_KEY_set_group err.\n");
return -1;
}
/* 生成密钥对,内部先生成随机数(NIST SP 800-90A DRBG)
然后再计算出公钥 */
ret=EC_KEY_generate_key(key);
if(ret!=1)
{
printf("EC_KEY_generate_key err.\n");
return -1;
}
/* 秘钥输出 */
unsigned char* privbuf1;
unsigned char *pubbuf1;
len = EC_KEY_priv2buf(key, &privbuf1);
printf("key priv len = %lu\n", len);
len = i2o_ECPublicKey(key, &pubbuf1);
printf("key pub len = %lu\n", len);
/* 私钥输入 */
BIGNUM* priv = BN_new();
/* 二进制转换成大数, 再输入给key3*/
BN_bin2bn(privbuf1, 32, priv);
EC_KEY_set_private_key(key3, priv);
/* 根据已有私钥计算年出公钥 */
BN_CTX * ctx = BN_CTX_new();
BN_CTX_start(ctx);
EC_POINT * pub3 = EC_POINT_new(group3);
EC_POINT_mul(group3, pub3, priv, NULL, NULL, ctx);
EC_KEY_set_public_key(key3, pub3);
/* 公钥输入 */
o2i_ECPublicKey(&key4, &pub_bytes_copy, len);
/* 生成签名 */
ECDSA_SIG *signature;
int rnum_len = 16;
signature = ECDSA_do_sign(rnum, rnum_len, key3);
/* 签名打印*/
const ECDSA_SIG * aa = signature;
int sig_len = ECDSA_size(key3);
const BIGNUM* r = ECDSA_SIG_get0_r(aa);
const BIGNUM* s = ECDSA_SIG_get0_s(aa);
unsigned char rbuf[128] = {0};
unsigned char sbuf[128] = {0};
int rlen = BN_bn2bin(r, rbuf);
int slen = BN_bn2bin(s, sbuf);
printf("rlen = %d r:", rlen);
print_keystr(pstr, rbuf, rlen);
printf("slen = %d s:", slen);
print_keystr(pstr, sbuf, slen);
printf("signature max len = %d\n", sig_len);
i2d_ECDSA_SIG(signature, &sigbuf);
printf("key3 sig: ");
print_keystr(pstr, sigbuffer, sig_len);
/* 验证签名 */
BIGNUM* newr = NULL;
BIGNUM* news = NULL;
newr = BN_bin2bn(rbuf, rlen, NULL);
news = BN_bin2bn(sbuf, slen, NULL);
ECDSA_SIG *new_signature = ECDSA_SIG_new();
ECDSA_SIG_set0(new_signature, newr, news);
int verified = ECDSA_do_verify(rnum, rnum_len,
new_signature,
key4);
if (verified == 1)
printf("Verified\n");
/*ECDH 秘钥交换*/
// pubkey2 , key2公钥从buff到 pubkey2
len = 33;
EC_KEY *pubkey2;
pubkey2=EC_KEY_new();
EC_KEY_set_group(pubkey2, group2);
pub_bytes_copy = pubbuf2;
o2i_ECPublicKey(&pubkey2, &pub_bytes_copy, 33);
unsigned char sharekey_1[32] = {0};
unsigned char sharekey_2[32] = {0};
len =ECDH_compute_key(sharekey_1, 32, EC_KEY_get0_public_key(pubkey2), key, NULL);
printf("Share key len %lu, ", len);
print_keystr(pstr, sharekey_1, len);
EC_KEY *pubkey1 = EC_KEY_new();
EC_KEY_set_group(pubkey1, group);
pub_bytes_copy = pubbuf1;
o2i_ECPublicKey(&pubkey1, &pub_bytes_copy, 33);
len =ECDH_compute_key(sharekey_2, 32, EC_KEY_get0_public_key(pubkey1), key2, NULL); //key4 has pubkey of key
printf("Share key2 len %lu, ", len);
print_keystr(pstr, sharekey_2, len);