Post

椭圆曲线加密漫谈--如何使用openssl进行ECC加密

最近做一个分析,碰到了采用椭圆曲线的情况,顺路学习一下这种加密方式的用法和基本原理。这里作为记录学习的过程和想法。RSA见得多了,比较熟。ECC见得少,还没有符号,看起来是真的痛苦。

相对于传统的对称加密(DES/AES),非对称加密拥有一个更优的品质,那就是不用传输密钥本身来实现两端的加解密,同时还可以胜任身份认证的过程。本期漫谈,我们会简单的涉及到椭圆曲线的意义和如何使用openssl来实现一次加密(C语言实现)。

对称与非对称

​ 我们常说对称与非对称,它们的意思究竟是什么?本质来看,就是加解密是否应用了同一个密钥k[^对称加密]。对称加密的数学表达很简单,形式如下: \(E = f(M,k) \\ M = f^{-1}(E,k)\) ​ 假如我们用一段简单的代码来表示,可以写成:

1
2
3
4
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两者的数学本质可以分别表示为: \(n = p*q \\ b = a^i(mod\quad p) (0\leq i\leq p)\) ​ 两者的可靠性分别建立在大数分解问题和离散对数问题[^离散对数]之上(计算的复杂度不可达)。相对于对称加密,二者采用了另一种思路,把加密的可靠性通过数学来保证,而非密钥隐藏。

​ 通过上述的描述,我们知道了两者的区别,但是我们必须承认,对称加密的速度是很快的,这一点非对称还存在差距。

非对称加密-椭圆曲线

​ 相对于上一个公式,下面这一个公式可能更容易理解,其分别的代表意义如下: \(Q = d*G \\ E = \{rG, M+rQ\}\)

  1. 私钥d
  2. 基点G
  3. 公钥Q
  4. 密文E,原文M
  5. 随机数r

​ 在椭圆曲线中,我们可以简单的理解成,已知d、G很容易计算Q。但是已知Q、G很难计算出d。这就是我们使用非对称加密的优势所在,同时也可以被用作身份认证。(联想到什么了吗?证书)

​ 发行者持有d、G而公开Q、G。采用Q加密,只有d才能解密。而身份验证相反,d加密,采用Q进行验证。下面给出一个简单的采用椭圆曲线加解密流程。 ​ ​ 注意上方的E是采用了一个点对的形式表达,这意味我们同时给出了rQ和M+rG,这里的+代表一种运算方式,可以简单的理解成上述对称加密中的f。在接收到E之后,下列公式将会恢复M,这就是解密的基础数学原理。 \(M+rQ-d(rG) = M+rQ-r(dG) = M\)

ECC示例代码

代码将会极度简化,不包含任何错误处理并且不包含任何可用公私钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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进行一次加解密。同时,注意到由于随机数的存在,每一次的加密密文都是不同的,这一点相较于对称加密,也是一个很大的区别。

啥也不是,散会!:laughing:

This post is licensed under CC BY 4.0 by the author.