椭圆曲线加密漫谈
最近做一个分析,碰到了采用椭圆曲线的情况,顺路学习一下这种加密方式的用法和基本原理。这里作为记录学习的过程和想法。RSA见得多了,比较熟。ECC见得少,还没有符号,看起来是真的痛苦。
相对于传统的对称加密(DES/AES),非对称加密拥有一个更优的品质,那就是不用传输密钥本身来实现两端的加解密,同时还可以胜任身份认证的过程。本期漫谈,我们会简单的涉及到椭圆曲线的意义和如何使用openssl来实现一次加密(C语言实现)。
对称与非对称
我们常说对称与非对称,它们的意思究竟是什么?本质来看,就是加解密是否应用了同一个密钥k[1]。对称加密的数学表达很简单,形式如下:
假如我们用一段简单的代码来表示,可以写成:
for(int i=0;i<src_len;i++)
src[i] ^= key[i];
for(int i=0;i<enc_len;i++)
enc[i] ^= key[i];
上述就是一个最简单的加密,各字段意义如下:
1. 加密函数`f`和它的逆`f^-1`
2. 原文`M`与密文`E`
3. 密钥`k`
现实中常见的对称加密一般是AES,这一部分很多代码片段,这里不再赘述。考察上式可以注意到,k在这里面起到了关键作用,它依赖于双方的约定,要么是提前商量好,要么是加密时传输。这就会导致密钥泄露的风险。
那么非对称加密又是什么呢?非对称加密的可靠性通常依赖一个数学问题,比如RSA算法和bitcoin所用的secp256k1两者的数学本质可以分别表示为:
两者的可靠性分别建立在大数分解问题和离散对数问题[2]之上(计算的复杂度不可达)。相对于对称加密,二者采用了另一种思路,把加密的可靠性通过数学来保证,而非密钥隐藏。
通过上述的描述,我们知道了两者的区别,但是我们必须承认,对称加密的速度是很快的,这一点非对称还存在差距。
非对称加密-椭圆曲线
相对于上一个公式,下面这一个公式可能更容易理解,其分别的代表意义如下:
- 私钥d
- 基点G
- 公钥Q
- 密文E,原文M
- 随机数r
在椭圆曲线中,我们可以简单的理解成,已知d、G很容易计算Q。但是已知Q、G很难计算出d。这就是我们使用非对称加密的优势所在,同时也可以被用作身份认证。(联想到什么了吗?证书)
发行者持有d、G而公开Q、G。采用Q加密,只有d才能解密。而身份验证相反,d加密,采用Q进行验证。下面给出一个简单的采用椭圆曲线加解密流程。
注意上方的E是采用了一个点对的形式表达,这意味我们同时给出了rQ和M+rG,这里的+
代表一种运算方式,可以简单的理解成上述对称加密中的f
。在接收到E之后,下列公式将会恢复M,这就是解密的基础数学原理。
ECC示例代码
代码将会极度简化,不包含任何错误处理并且不包含任何可用公私钥。
int do_enc(char *src, size_t src_len, char *enc, size_t *enc_len, const EC_KEY *pub_ec_key)
{
const EC_GROUP *group = EC_KEY_get0_group(pub_ec_key);
BN_CTX *ctx = BN_CTX_new();
BIGNUM *kx = BN_new();
BIGNUM *ky = BN_new();
EC_POINT *calc_point = EC_POINT_new(group);
EC_POINT *rG = EC_POINT_new(group);
EC_POINT *rQ = EC_POINT_new(group);
const EC_POINT *pubk = EC_KEY_get0_public_key(pub_ec_key);
/*选取一个随机数,并且计算rG,rQ,最终将rQ转化成坐标形式参与后续运算*/
BIGNUM *generate_rnd = BN_new();
BIGNUM *bn_range = BN_new();
ret = BN_rand_range(generate_rnd, bn_range);
EC_POINT_mul(group, rG, generate_rnd, 0, 0, ctx);
EC_POINT_get_affine_coordinates(group, rQ, kx, ky, ctx);
EC_POINT_mul(group, rQ, 0, pubk, generate_rnd, ctx);
/*操作kx ky进行加密即可*/
......
}
int do_dec(char *src, size_t src_len, char *dec, size_t *dec_len, const EC_KEY *pri_ec_key)
{
/*我们是获取的E = {rG,M+rQ}, 恢复rG,然后和私钥计算得到rQ*/
const BIGNUM *pri_key_bn = EC_KEY_get0_private_key(pri_ec_key);
const EC_GROUP *group = EC_KEY_get0_group(pri_ec_key);
EC_POINT *rG = EC_POINT_new(group);
ret = EC_POINT_set_affine_coordinates(group, rG, kx, ky, ctx);
EC_POINT *rQ = EC_POINT_new(group);
ret = EC_POINT_mul(group, rQ, 0, rG, pri_key_bn, ctx);
/*还记得上面的rQ吗,它就是参与加密的部分,下面代码操作kx ky进行解密即可*/
......
}
int main()
{
char *use_pubk = "THIS IS PUBLICK_KEY.GENERATED BY OPENSSL";
char *use_prik = "THIS IS PRIVATE_KEY.GENERATED BY OPENSSL";
EVP_PKEY *evp_key = EVP_PKEY_new();
BIO *bio = BIO_new_mem_buf(use_pubk, strlen(use_pubk));
BIO *prik_bio = BIO_new_mem_buf(www_prik, strlen(www_prik));
const EC_KEY *pri_ec_key = PEM_read_bio_ECPrivateKey(prik_bio, NULL, NULL, NULL);
PEM_ASN1_read_bio((d2i_of_void *)d2i_PUBKEY, "PUBLIC KEY", bio, (void **)&evp_key, NULL, NULL);
EC_KEY *pub_ec_key = EVP_PKEY_get1_EC_KEY(evp_key);
char *src = "hello, ecc!";
size_t src_len = strlen(src);
char enc[256] = {0,};
size_t enc_len = 255;
char dec[256] ={0,};
size_t dec_len = 255;
do_enc(src, src_len, enc, enc_len, pub_ec_key);
do_dec(enc,enc_len, dec, dec_len, pri_ec_key);
}
好,到此我们就简单了解了ecc的原理和如何使用openssl进行一次加解密。同时,注意到由于随机数的存在,每一次的加密密文都是不同的,这一点相较于对称加密,也是一个很大的区别。
啥也不是,散会!