20 椭圆曲线¶
20.1 ECC介绍¶
椭圆曲线算法可以看作是定义在特殊集合下数的运算,满足一定的规则。
椭圆曲线在如 下两个域中定义:Fp 域和 F2m 域。
Fp 域,素数域,p 为素数;
F2m 域:特征为 2 的有限域,称之为二元域或者二进制扩展域。该域中,元素的个数为 2m 个。
椭圆曲线标准文档如下:
1) X9.62
Public Key Cryptography For The Financial Services Industry: The Elliptic Curve
Digital Signature Algorithm (ECDSA);
2) SEC1
SEC 1:Elliptic Curve Cryptography;
3) SEC2
SEC 2: Recommended Elliptic Curve Domain Parameters;
4) NIST
(U.S.) National Institute of Standards and Technology,美国国家标准。
这些标准一般都描述了 Fp 域和 F2m 域、椭圆曲线参数、数据转换、密钥生成以及
推荐了多种椭圆曲线。
一些术语说明:
1) 椭圆曲线的阶(order of a curve)
椭圆曲线所有点的个数,包括无穷远点;
2) 椭圆曲线上点的阶(order of a point)
P 为椭圆曲线上的点,nP=无穷远点,n 取最小整数,既是 P 的阶;
3) 基点(base point)
椭圆曲线参数之一,用 G 表示,是椭圆曲线上都一个点;
4) 余因子(cofactor)
椭圆曲线的余因子,用 h 表示,为椭圆曲线点的个数/基点的阶
5) 椭圆曲线参数:
素数域:
(p,a,b,G,n,h)
其中,p 为素数,确定 Fp,a 和 b 确定椭圆曲线方程,G 为基点,
n 为 G 的阶, h 为余因子。
二进制扩展域:
(m,f(x),a,b,G,n,h)
其中,m 确定 F2m,f(x)为不可约多项式,a 和 b 用于确定椭圆曲线方程,G 为基点,
n 为 G 的阶, h 为余因子。
6) 椭圆曲线公钥和私钥
椭圆曲线的私钥是一个随机整数,小于 n;
椭圆曲线的公钥是椭圆曲线上的一个点: Q = 私钥 * G。
20.2 openssl的ECC实现¶
Openssl实现了ECC算法。
ECC算法系列包括三部分:
1. ECC算法 (crypto/ec)、
2. 椭圆曲线数字签名算法 ECDSA (crypto/ecdsa)
3. 圆曲线密钥交换算法 ECDH (crypto/dh)
研究椭圆曲线需要注意的有:
1) 密钥数据结构
主要是 公钥 和 私钥 数据结构。
椭圆曲线密钥数据结构如下,定义在/crypto/ec/ec_lcl.h:272 中,对用户是透明的。
struct ec_key_st {
int version;
EC_GROUP *group;
EC_POINT *pub_key;
BIGNUM *priv_key;
/* 其他项 */
}
EC_POINT据结构如下,定义在/crypto/ec/ec_lcl.h: 287 中,
struct ec_point_st {
const EC_METHOD *meth;
/* NID for the curve if known */
int curve_name;
/*
* All members except 'meth' are handled by the method functions, even if
* they appear generic
*/
BIGNUM *X;
BIGNUM *Y;
BIGNUM *Z; /* Jacobian projective coordinates: * (X, Y,
* Z) represents (X/Z^2, Y/Z^3) if Z != 0 */
int Z_is_one; /* enable optimized point arithmetics for
* special case */
};
include/openssl/ec.h:46
typedef struct ec_point_st EC_POINT;
2) 密钥生成
对照公钥和私钥的表示方法,非对称算法不同有各自的密钥生成过程。
椭圆曲线的密钥生成实现在crytpo/ec/ec_key.c中。
Openssl中,椭圆曲线密钥生成时,首先用户需要选取一种椭圆曲线
(openssl的crypto/ec_curve.c中内置实现了67种,调用EC_get_builtin_curves获取该列表),
然后根据选择的椭圆曲线计算密钥生成参数group,最后根据密钥参数group来生公私钥。
3)签名值数据结构
非对称算法不同,签名的结果表示也不一样。
与DSA签名值一样,ECDSA的签名结果表示为两项。
ECDSA的签名结果数据结构定义在crypto/ecdsa/ecdsa.h中,如下:
typedef struct ECDSA_SIG_st {
BIGNUM *r;
BIGNUM *s;
} ECDSA_SIG;
4) 签名与验签
对照签名结果,研究其是如何生成的。
crypto/ecdsa/ ecs_sign.c实现了签名算法,crypto/ecdsa/ ecs_vrf.c实现了验签。
5)密钥交换
研究其密钥交换是如何进行的;crypto/ecdh/ech_ossl.c实现了密钥交换算法。
20.3 主要函数¶
20.3.1 参数设置¶
1) int EC_POINT_set_affine_coordinates_GF2m(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
BN_CTX *ctx )
说明: 设置二进制域椭圆曲线上点 point 的几何坐标;
2) int EC_POINT_set_affine_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
BN_CTX *ctx )
说明: 设置素数域椭圆曲线上点 point 的几何坐标;
) int EC_POINT_set_affine_coordinates(const EC_GROUP *group, EC_POINT *p,
const BIGNUM *x, const BIGNUM *y,
BN_CTX *ctx);
说明:设置素数域椭圆曲线上点point的几何坐标;
The functions EC_POINT_set_affine_coordinates_GFp() and EC_POINT_set_affine_coordinates_GF2m()
are synonyms for EC_POINT_set_affine_coordinates().
They are defined for backwards compatibility only and should not be used.
3) int EC_POINT_set_compressed_coordinates_GF2m(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
int y_bit,
BN_CTX *ctx )
说明: 二进制域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;
用于将 Octet-String转化为椭圆曲线上的点;
4) int EC_POINT_set_compressed_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
int y_bit,
BN_CTX *ctx)
说明: 素数域椭圆曲线,给定压缩坐标x和y_bit参数,设置point的几何坐标;
用于将 Octet-String转化为椭圆曲线上的点;
5) int EC_POINT_set_Jprojective_coordinates_GFp(
const EC_GROUP *group,
EC_POINT *point,
const BIGNUM *x,
const BIGNUM *y,
const BIGNUM *z,
BN_CTX *ctx )
说明: 素数域椭圆曲线group,设置点point的投影坐标系坐标x、y和z;
6) int EC_POINT_set_to_infinity(const EC_GROUP *group, EC_POINT *point)
说明:将点 point 设为无穷远点
7) int EC_GROUP_set_curve_GF2m(
EC_GROUP *group,
const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx )
说明: 设置二进制域椭圆曲线参数;
8) int EC_GROUP_set_curve_GFp( EC_GROUP *group,
const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx )
说明: 设置素数域椭圆曲线参数;
9) int EC_GROUP_set_generator( EC_GROUP *group,
const EC_POINT *generator,
const BIGNUM *order,
const BIGNUM *cofactor)
说明: 设置椭圆曲线的基G; generator、order和cofactor为输入参数;
10) size_t EC_GROUP_set_seed(EC_GROUP *group, const unsigned char *p, size_t len)
说明: 设置椭圆曲线随机数,用于生成a和b;
11) EC_GROUP *EC_GROUP_new_curve_GF2m( const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx)
说明: 生成二进制域上的椭圆曲线,输入参数为p,a和b;
12) EC_GROUP *EC_GROUP_new_curve_GFp( const BIGNUM *p,
const BIGNUM *a,
const BIGNUM *b,
BN_CTX *ctx)
说明: 生成素数域上的椭圆曲线。
13) int EC_POINT_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n,
const EC_POINT *q, const BIGNUM *m, BN_CTX *ctx);
点乘
EC_POINT_mul is a convenient interface to EC_POINTs_mul: it calculates the value generator * n + q * m and stores the result in r. The
value n may be NULL in which case the result is just q * m (variable point multiplication). Alternatively, both q and m may be NULL, and n
non-NULL, in which case the result is just generator * n (fixed point multiplication). When performing a single fixed or variable point
multiplication, the underlying implementation uses a constant time algorithm, when the input scalar (either n or m) is in the range [0,
ec_group_order).
14) int EC_POINTs_mul(const EC_GROUP *group, EC_POINT *r, const BIGNUM *n, size_t num,
const EC_POINT *p[], const BIGNUM *m[], BN_CTX *ctx);
EC_POINTs_mul calculates the value generator * n + q[0] * m[0] + ... + q[num-1] * m[num-1]. As for EC_POINT_mul the value n may be NULL or
num may be zero. When performing a fixed point multiplication (n is non-NULL and num is 0) or a variable point multiplication (n is NULL
and num is 1), the underlying implementation uses a constant time algorithm, when the input scalar (either n or m[0]) is in the range [0,
ec_group_order).
20.3.2 参数获取¶
1) const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *group)
说明: 获取椭圆曲线的基(G);
2) unsigned char *EC_GROUP_get0_seed(const EC_GROUP *group)
说明: 获取椭圆曲线参数的随机数,该随机数可选,用于生成椭圆曲线参数中的a和b;
3) int EC_GROUP_get_basis_type(const EC_GROUP *group)
说明: 获取二进制域多项式的类型;
4) int EC_GROUP_get_cofactor(const EC_GROUP *group,
BIGNUM *cofactor,
BN_CTX *ctx)
说明: 获取椭圆曲线的余因子。cofactor为X9.62中定义的h,
值为椭圆曲线点的个数/基 点的阶,即:cofactor = #E(Fq)/n。
5) int EC_GROUP_get_curve_GF2m( const EC_GROUP *group,
BIGNUM *p,
BIGNUM *a,
BIGNUM *b,
BN_CTX *ctx)
说明: 获取二元域椭圆曲线的三个参数,其中p可表示多项式;
6) int EC_GROUP_get_curve_GFp( const EC_GROUP *group,
BIGNUM *p,
BIGNUM *a,
BIGNUM *b,
BN_CTX *ctx)
说明: 获取素数域椭圆曲线的三个参数;
7) int EC_GROUP_get_curve_name(const EC_GROUP *group)
说明: 获取椭圆曲线名称,返回其NID;
8) int EC_GROUP_get_degree(const EC_GROUP *group)
说明: 获取椭圆曲线密钥长度。对于素数域Fp来说,是大数p的长度;
对二进制域F2m 来说,等于m;
9) int EC_GROUP_get_order( const EC_GROUP *group,
BIGNUM *order,
BN_CTX *ctx)
说明: 获取椭圆曲线的阶;
10) int EC_GROUP_get_pentanomial_basis( const EC_GROUP *group,
unsigned int *k1,
unsigned int *k2,
unsigned int *k3)
int EC_GROUP_get_trinomial_basis(const EC_GROUP *group, unsigned int *k)
说明: 获取多项式参数;
11) int EC_POINT_get_affine_coordinates_GF2m( const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BN_CTX *ctx)
说明: 获取二进制域椭圆曲线上某个点的x和y的几何坐标;
12) int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BN_CTX *ctx)
说明: 获取素数域上椭圆曲线上某个点的x和y的几何坐标;
13) int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *group,
const EC_POINT *point,
BIGNUM *x,
BIGNUM *y,
BIGNUM *z,
BN_CTX *ctx)
说明: 获取素数域椭圆曲线上某个点的x、y和z的投影坐标系坐标。
20.3.3 转化函数¶
1) EC_POINT *EC_POINT_bn2point( const EC_GROUP *group,
const BIGNUM *bn,
EC_POINT *point,
BN_CTX *ctx)
说明: 将大数转化为椭圆曲线上的点;
2) EC_POINT *EC_POINT_hex2point( const EC_GROUP *group,
const char *buf,
EC_POINT *point,
BN_CTX *ctx)
说明: 将buf中表示的十六进制数据转化为椭圆曲线上的点;
3) int BN_GF2m_poly2arr(const BIGNUM *a, unsigned int p[], int max)
说明: 将大数转化为多项式的各个项;
4) int BN_GF2m_arr2poly(const unsigned int p[], BIGNUM *a)
说明: 将多项式的各个项转化为大数;
5) int EC_POINT_make_affine( const EC_GROUP *group,
EC_POINT *point,
BN_CTX *ctx)
说明: 将椭圆曲线group上点的point转化为几何坐标系;
6) int EC_POINT_oct2point( const EC_GROUP *group,
EC_POINT *point,
const unsigned char *buf,
size_t len,
BN_CTX *ctx)
说明: 将buf中点数据转化为椭圆曲线上的点,len为数据长度;
7) BIGNUM *EC_POINT_point2bn( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
BIGNUM *ret,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为大数,其中from为压缩方式,可以是
POINT_CONVERSION_COMPRESSED、POINT_CONVERSION_UNCOMPRESSED或
POINT_CONVERSION_HYBRID,可参考x9.62;
8) char *EC_POINT_point2hex( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为十六进制,并返回该结果;
9) size_t EC_POINT_point2oct( const EC_GROUP *group,
const EC_POINT *point,
point_conversion_form_t form,
unsigned char *buf,
size_t len,
BN_CTX *ctx)
说明: 将椭圆曲线上的点转化为Octet-String,可分两次调用,
用法见EC_POINT_point2bn 的实现。
20.3.4 其他函数¶
1) size_t EC_get_builtin_curves( EC_builtin_curve *r,size_t nitems)
说明:获取内置的椭圆曲线。
当输入参数 r == NULL 或者 nitems == 0时,返回内置椭圆曲线的个数,
否则将各个椭圆曲线信息存放在 r 中
2) const EC_METHOD *EC_GF2m_simple_method(void)
说明: 返回二进制域上的方法集 EC_METHOD
3) const EC_METHOD *EC_GFp_mont_method(void)
const EC_METHOD *EC_GFp_nist_method(void)
const EC_METHOD *EC_GFp_simple_method(void)
返回素数域上的方法集 EC_METHOD
4) int EC_GROUP_check(const EC_GROUP *group, BN_CTX *ctx)
说明: 检查椭圆曲线,成功返回 1。
5) int EC_GROUP_check_discriminant(const EC_GROUP *group, BN_CTX *ctx)
说明: 检查椭圆曲线表达式。
对于素数域的椭圆曲线来说,该函数会调用
ec_GFp_simple_group_check_discriminant 函数,
主要检查 4*a^3 + 27*b^2 != 0 (mod p)。
而对于二进制域的椭圆曲线,会调用
ec_GF2m_simple_group_check_discriminant,
检查 y^2 + x*y = x^3 + a*x^2 + b 是否是一个椭圆曲线
并且 b !=0。
6) int EC_GROUP_cmp(const EC_GROUP *a, const EC_GROUP *b, BN_CTX *ctx)
说明: 通过比较各个参数来确定两个椭圆曲线是否相等;
7) int EC_GROUP_copy(EC_GROUP *dest, const EC_GROUP *src)
EC_GROUP *EC_GROUP_dup(const EC_GROUP *a)
说明: 椭圆曲线拷贝函数;
9) EC_GROUP *EC_GROUP_new_by_curve_name(int nid)
说明: 根据NID获取内置的椭圆曲线;
10) int EC_KEY_check_key(const EC_KEY *eckey)
说明: 检查椭圆曲线密钥;
11) int EC_KEY_generate_key(EC_KEY *eckey)
说明: 生成椭圆曲线公私钥;
12) int EC_KEY_print(BIO *bp, const EC_KEY *x, int off)
说明: 将椭圆曲线密钥信息输出到bio中,off为缩进量;
13) int EC_POINT_add( const EC_GROUP *group,
EC_POINT *r,
const EC_POINT *a,
const EC_POINT *b,
BN_CTX *ctx)
说明: 椭圆曲线上点的加法;
14) int EC_POINT_invert(const EC_GROUP *group,
EC_POINT *a,
BN_CTX *ctx)
说明: 求椭圆曲线上某点a的逆元,a既是输入参数,也是输出参数;
15) int EC_POINT_is_at_infinity(const EC_GROUP *group,
const EC_POINT *point)
说明: 判断椭圆曲线上的点point是否是无穷远点;
16) int EC_POINT_is_on_curve( const EC_GROUP *group,
const EC_POINT *point,
BN_CTX *ctx)
说明: 判断一个点point是否在椭圆曲线上;
17) int ECDSA_size
说明: 获取 ECC 密钥大小字节数。
18) ECDSA_sign
说明: 签名,返回 1 表示成功。
19) ECDSA_verify
说明: 验签,返回 1 表示合法。
20) EC_KEY_get0_public_key
说明: 获取公钥。
21) EC_KEY_get0_private_key
说明: 获取私钥。
22) ECDH_compute_key
说明: 生成共享密钥
23) EC_KEY *d2i_ECPrivateKey(EC_KEY **a, const unsigned char **in, long len)
说明: DER 解码将椭圆曲线密钥;
24) int i2d_ECPrivateKey(EC_KEY *a, unsigned char **out)
说明: 将椭圆曲线密钥 DER 编码;
更多底层函数的使用示例可参考 ec/ectest.c,特别是用户自行定义椭圆曲线参数。
#include <openssl/ec.h>
#include <openssl/evp.h> //include NID_sm2
/*
https://www.openssl.org/docs/man1.1.0/crypto/EC_GROUP_new.html
typedef struct {
int nid;
const char *comment;
} EC_builtin_curve
*/
int main()
{
EC_builtin_curve *curves = NULL;
size_t crv_len = 0, n = 0;
int nid,ret;
EC_GROUP *group = NULL;
/*
size_t EC_get_builtin_curves(EC_builtin_curve *r, size_t nitems)
说明: 获取内置的椭圆曲线。
当输入参数 r 为 NULL 或者 nitems 为 0 时,返回内置椭圆曲线的个数,
否则将各个椭圆曲线信息存放在 r 中。
*/
crv_len = EC_get_builtin_curves(NULL, 0);
curves = OPENSSL_malloc(sizeof(EC_builtin_curve) * crv_len);
/* 获取椭圆曲线列表 */
EC_get_builtin_curves(curves, crv_len);
for (n=0;n<crv_len;n++) {
nid = curves[n].nid;
group=NULL;
printf("[%zd] nid = %d comment=%s \n",
n , curves[n].nid,curves[n].comment);
group = EC_GROUP_new_by_curve_name(nid);
ret=EC_GROUP_check(group,NULL); /*检查椭圆曲线,成功返回 1*/
}
group = EC_GROUP_new_by_curve_name(NID_sm2);
ret=EC_GROUP_check(group,NULL); /*检查椭圆曲线,成功返回 1*/
if ( ret == 1){
printf("\nEC_GROUP_checks seccess : NID_sm2 \n\n");
}
OPENSSL_free(curves);
return 0;
}
#include <string.h>
#include <stdio.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/objects.h>
#include <openssl/err.h>
int main()
{
EC_KEY *key1,*key2;
EC_POINT *pubkey1,*pubkey2;
EC_GROUP *group1,*group2;
int ret,nid,size,i,sig_len;
unsigned char *signature,digest[20];
BIO *berr;
EC_builtin_curve *curves;
int crv_len;
//char shareKey1[128*2],shareKey2[128*2];
uint8_t shareKey1[256],shareKey2[256];
int len1,len2;
/* 构造 EC_KEY 数据结构 */
key1=EC_KEY_new();
if(key1==NULL) {
printf("EC_KEY_new err!\n");
return -1;
}
key2=EC_KEY_new();
if(key2==NULL) {
printf("EC_KEY_new err!\n");
return -1;
}
/* 获取实现的椭圆曲线个数 */
crv_len = EC_get_builtin_curves(NULL, 0);
curves = (EC_builtin_curve *)malloc(sizeof(EC_builtin_curve) * crv_len);
/* 获取椭圆曲线列表 */
EC_get_builtin_curves(curves, crv_len);
/*
nid=curves[0].nid;会有错误,原因是密钥太短
*/
/* 选取一种椭圆曲线 */
//nid=curves[25].nid;
nid=curves[81].nid; // SM2
/* 根据选择的椭圆曲线生成密钥参数 group */
group1=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
group2=EC_GROUP_new_by_curve_name(nid);
if(group1==NULL) {
printf("EC_GROUP_new_by_curve_name err!\n");
return -1;
}
/* 设置密钥参数 */
ret=EC_KEY_set_group(key1,group1);
if(ret!=1) {
printf("EC_KEY_set_group err.\n");
return -1;
}
ret=EC_KEY_set_group(key2,group2);
if(ret!=1) {
printf("EC_KEY_set_group err.\n");
return -1;
}
/* 生成密钥 */
ret=EC_KEY_generate_key(key1);
if(ret!=1) {
printf("EC_KEY_generate_key err.\n");
return -1;
}
ret=EC_KEY_generate_key(key2);
if(ret!=1) {
printf("EC_KEY_generate_key err.\n");
return -1;
}
/* 检查密钥 */
ret=EC_KEY_check_key(key1);
if(ret!=1) {
printf("check key err.\n");
return -1;
}
/* 获取密钥大小 */
size=ECDSA_size(key1);
printf("size %d \n",size);
for(i=0;i<20;i++){
memset(&digest[i],i+1,1);
}
signature=malloc(size);
/*加载各自错误信息*/
ERR_load_crypto_strings();
berr=BIO_new(BIO_s_file());
BIO_set_fp(berr,stdout,BIO_NOCLOSE);
/*签名 验签*/
/* 签名数据,本例未做摘要,可将 digest 中的数据看作是 sha1 摘要结果 */
ret=ECDSA_sign(0,digest,20,signature,&sig_len,key1);
if(ret!=1) {
ERR_print_errors(berr); printf("sign err!\n"); return -1;
}
/* 验证签名 */
ret=ECDSA_verify(0,digest,20,signature,sig_len,key1);
if(ret!=1) {
ERR_print_errors(berr); printf("ECDSA_verify err!\n"); return -1;
}
/** DH 会话密钥**/
/* 获取对方公钥,不能直接引用 */
pubkey2 = EC_KEY_get0_public_key(key2);
/* 生成一方的共享密钥 */
len1=ECDH_compute_key(shareKey1, 256, pubkey2, key1, NULL);
/* printf */
for ( i = 0; i<len1; i ++){
printf("%02x:",shareKey1[i]);
}
printf("\n");
pubkey1 = EC_KEY_get0_public_key(key1);
/* 生成另一方共享密钥 */
len2=ECDH_compute_key(shareKey2, 256, pubkey1, key2, NULL);
if(len1!=len2) {
printf("err\n");
}
else {
ret=memcmp(shareKey1,shareKey2,len1);
if(ret==0)
printf("生成共享密钥成功\n");
else
printf("生成共享密钥失败\n");
}
printf("test ok!\n");
BIO_free(berr);
EC_KEY_free(key1);
EC_KEY_free(key2);
free(signature);
free(curves);
return 0;
}