1.ECC数学知识
1.1 ECC 实数域椭圆曲线
1.1.1 ECC实数域定义
更多曲线图形可可视化ECC曲线
定义加法运算
定义倍数运算
1.1.2 加法运算:
计算斜率:
计算:
计算
1.2 ECC有限域离散椭圆曲线
1.2.1 ECC 有限域定义
(where p is prime and p > 3) or 𝔽2m (where the fields size p = 2^{m}).
1.2.2 如何判断点在曲线上
代入(x,y)到上式,若成立则代表点在曲线上;(9^3 + 7 - 15^2) % 17 != 0
计算斜率:
计算:
计算
注意:计算k kk时的除法,实际上是乘分母的逆元;其他的运算包括模加/减、模乘;即点加运算可以分解为模加/减、模乘和模逆运算。若P = Q,
更多详细细节请参考 ECC椭圆曲线加解密原理详解(配图)https://blog.csdn.net/sitebus/article/details/82835492/
1.2.3 有限域点乘定义
将椭圆曲线(EC)上的两个点(EC点)相加,结果会得到另一个点。这个操作称为EC点加法。如果我们将一个点G与自身相加,结果就是G + G = 2 * G。如果再将加上G结果,就可以得到3 * G,以此类推。这就是EC点乘法的定义。
在有限域上的椭圆曲线上,一个点G(EC点)可以乘以一个整数k,结果是相同曲线上的另一个EC点P,而且这个操作很快速: P = k * G;
计算2G,则根据G点(x,y),可以计算出k,X,Y,(X,Y) 即为2G点,以此类推;
For example let's take the EC point G = {15, 13} on the elliptic curve over finite field y2 ≡ x3 + 7 (mod 17) and multiply it by k = 6. We shall obtain an EC point P = {5, 8}:
-
P = k * G = 6 * {15, 13} = {5, 8}
给定k和G,根据加法法则,计算K很容易;但给定K和G,求k就相对困难了。这就是椭圆曲线加密算法采用的难题。点G称为基点(base point),k(k<n,n为基点G的阶)称为私有密钥(privte key),K称为公开密钥(public key)。
1.2.4 有限点与点乘
由上图可知G-27G必然出现在有限数域内,G点,KG点的(X,Y)不具备线性关系;选择不同的G点,可以计算出不同的KG,直到O点(无限远点);
from tinyec.ec import SubGroup, Curve field = SubGroup(p=17, g=(15, 13), n=18, h=1) curve = Curve(a=0, b=7, field=field, name='p1707') print('curve:', curve) for k in range(0, 25): p = k * curve.g print(f"{k} * G = ({p.x}, {p.y})")
curve: "p1707" => y^2 = x^3 + 0x + 7 (mod 17) 0 * G = (None, None) 1 * G = (15, 13) 2 * G = (2, 10) 3 * G = (8, 3) 4 * G = (12, 1) 5 * G = (6, 6) 6 * G = (5, 8) 7 * G = (10, 15) 8 * G = (1, 12) 9 * G = (3, 0) 10 * G = (1, 5) 11 * G = (10, 2) 12 * G = (5, 9) 13 * G = (6, 11) 14 * G = (12, 16) 15 * G = (8, 14) 16 * G = (2, 7) 17 * G = (15, 4) 18 * G = (None, None) 19 * G = (15, 13) 20 * G = (2, 10) 21 * G = (8, 3) 22 * G = (12, 1) 23 * G = (6, 6) 24 * G = (5, 8)
可以看出,0 * G = 无穷远点,椭圆曲线群是循环的,并且椭圆曲线群的阶数为n = 18,因为从k = 18开始,下一个点会重复第一个点: 18 * G = 0 * G = 无穷远点 19 * G = 1 * G = {15, 13} 20 * G = 2 * G = {2, 10} 21 * G = 3 * G = {8, 3} 等等。
在上面的例子中(有限域y^2 ≡ x^3 + 7 mod 17上的椭圆曲线),如果我们将点G = {15, 13}作为生成元,通过将G乘以范围在[1...18]内的整数,可以得到曲线上的任何其他点。因此,该椭圆曲线的阶数为n = 18,余因子为h = 1。
需要注意的是,该曲线有17个普通的椭圆曲线点(如上图所示)和一个特殊的"无穷远点",它们都属于同一子群,并且该曲线的阶数为18(而不是17)。
from tinyec.ec import SubGroup, Curve field = SubGroup(p=17, g=(5, 9), n=18, h=1) curve = Curve(a=0, b=7, field=field, name='p1707') print('curve:', curve) for k in range(0, 25): p = k * curve.g print(f"{k} * G' = ({p.x}, {p.y})")
curve: "p1707" => y^2 = x^3 + 0x + 7 (mod 17) 0 * G' = (None, None) 1 * G' = (5, 9) 2 * G' = (5, 8) 3 * G' = (None, None) 4 * G' = (5, 9) 5 * G' = (5, 8) 6 * G' = (None, None) ...
如果我们将点{5, 9}作为生成元,它只能生成3个椭圆曲线点:{5, 8},{5, 9}和无穷远点。由于曲线的阶数不是质数,不同的生成元可能生成不同阶数的子群。这是一个很好的例子,说明为什么我们不应该为密码目的自己"发明"椭圆曲线,而应该使用经过验证的曲线。
ECC加密算法可以采用不同的椭圆曲线. 不同的椭圆曲线具备不同的安全等级、不同的性能、不同的key长度,以及可能涉及到的不同底层算法.
1.2.5 ECC 椭圆曲线私钥公钥密钥对
ECC 中涉及的椭圆曲线中,非常的出名的有,sepc256k1、Curve25519等
from tinyec import registry
curve = registry.get_curve('secp192r1')
print('curve:', curve)
for k in range(0, 10):
p = k * curve.g
print(f"{k} * G = ({p.x}, {p.y})")
print("Cofactor =", curve.field.h)
print('Cyclic group order =', curve.field.n)
nG = curve.field.n * curve.g
print(f"n * G = ({nG.x}, {nG.y})")
curve: "secp192r1" => y^2 = x^3 + 6277101735386680763835789423207666416083908700390324961276x + 2455155546008943817740293915197451784769108058161191238065 (mod 6277101735386680763835789423207666416083908700390324961279)
0 * G = (None, None)
1 * G = (602046282375688656758213480587526111916698976636884684818, 174050332293622031404857552280219410364023488927386650641)
2 * G = (5369744403678710563432458361254544170966096384586764429448, 5429234379789071039750654906915254128254326554272718558123)
3 * G = (2915109630280678890720206779706963455590627465886103135194, 2946626711558792003980654088990112021985937607003425539581)
4 * G = (1305994880430903997305943738697779408316929565234787837114, 3981863977451150342116987835776121688410789618551673306674)
5 * G = (410283251116784874018993562136566870110676706936762660240, 1206654674899825246688205669651974202006189255452737318561)
6 * G = (4008504146453526025173196900303594155799995627910231899946, 3263759301305176906990806636587838100022690095020155627760)
7 * G = (3473339081378406123852871299395262476289672479707038350589, 2152713176906603604200842901176476029776544337891569565621)
8 * G = (1167950611014894512313033362696697441497340081390841490910, 4002177906111215127148483369584652296488769677804145538752)
9 * G = (3176317450453705650283775811228493626776489433309636475023, 44601893774669384766793803854980115179612118075017062201)
Cofactor = 1
Cyclic group order = 6277101735386680763835789423176059013767194773182842284081
n * G = (None, None)
from tinyec import registry
import secrets
curve = registry.get_curve('secp192r1')
privKey = secrets.randbelow(curve.field.n)
pubKey = privKey * curve.g
print("private key:", privKey)
print("public key:", pubKey)
private key: 4225655318977962031264230130242180748818603147467615868902
public key: (5396030834456770190396776530938374882273836179487834152291, 3422160588166914010077732710830109086004758012634997793937) on "secp192r1" => y^2 = x^3 + 6277101735386680763835789423207666416083908700390324961276x + 2455155546008943817740293915197451784769108058161191238065 (mod 6277101735386680763835789423207666416083908700390324961279)
一个椭圆曲线密钥对由公钥P和私钥d组成。生成密钥对的过程如下。
输入:具有加密强度的椭圆曲线域参数(p,a,b,G,n,h)
输出:密钥对(d,P)
操作:执行以下操作:
- d = RNG(f1; 2; : : : ; n - 1g)
- P = [d]G (If P is generated for ECGDSA or ECKCDSA, set P = [d-1 mod n]G instead).
- Output (d; P)
以后我们将使用这样的椭圆曲线加密(ECC)密钥对{私钥,公钥}来加密数据、签署消息和验证签名。
需要注意的是,在实际项目中,192位的曲线被认为是弱的,因此建议使用256位(或更多位)的曲线,其中密钥也是256位(或相应地更多位);
2 ECC 椭圆曲线
ECC(椭圆曲线密码学)的椭圆曲线由一组椭圆曲线域参数描述,包括曲线方程参数、有限域参数和生成点坐标等。这些参数在密码学标准中进行了规定,例如:
- SEC 2:推荐的椭圆曲线域参数
- NIST FIPS PUB 186-4 数字签名标准(DSS)
- Brainpool ECC 标准(RFC-5639)
- NSA Suite B (2005).
- ANSSI FRP256V1 (2011).
- ANSI X9.63 (2001).
这些标准定义了一组命名曲线的参数,例如secp256k1、P-521和brainpoolP512t1。这些密码标准中描述的有限域上的椭圆曲线经过了密码学家的深入研究和分析,并被认为具有一定的安全强度,这些安全强度也在这些标准中进行了描述。
椭圆曲线的详细介绍可参考《SEC 1:Elliptic Curve
Cryptography,Version 2.0》 [1], 常用的椭圆曲线可参考《SEC
2:Recommended Elliptic Curve Domain Parameteres,Version 2.0》 [2]。
一些密码学家(如Daniel Bernstein)认为,官方密码标准中描述的大多数曲线是"不安全的",他们定义了自己的密码标准,更广泛地考虑椭圆曲线的安全性。
Bernstein的SafeCurves标准列出了符合一组ECC安全要求的曲线。该标准可以在https://safecurves.cr.yp.to 上获取。
from tinyec.ec import SubGroup, Curve
# Domain parameters for the `secp256k1` curve
# (as defined in http://www.secg.org/sec2-v2.pdf)
name = 'secp256k1'
p = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f
n = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141
a = 0x0000000000000000000000000000000000000000000000000000000000000000
b = 0x0000000000000000000000000000000000000000000000000000000000000007
g = (0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,
0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8)
h = 1
curve = Curve(a, b, SubGroup(p, g, n, h), name)
print('curve:', curve)
privKey = int('0x51897b64e85c3f714bba707e867914295a1377a7463a9dae8ea6a8b914246319', 16)
print('privKey:', hex(privKey)[2:])
pubKey = curve.g * privKey
pubKeyCompressed = '0' + str(2 + pubKey.y % 2) + str(hex(pubKey.x)[2:])
print('pubKey:', pubKeyCompressed)
curve: "secp256k1" => y^2 = x^3 + 0x + 7 (mod 115792089237316195423570985008687907853269984665640564039457584007908834671663)
privKey: 51897b64e85c3f714bba707e867914295a1377a7463a9dae8ea6a8b914246319
pubKey: 02f54ba86dc1ccb5bed0224d23f01ed87e4a443c47fc690d7797a13d41d2340e1a
3 ECDH 密钥交换
3.1 原理
ECDH(椭圆曲线迪菲-赫尔曼密钥交换)是一种匿名密钥协商方案,允许两个各自拥有椭圆曲线公私钥对的参与方在不安全的信道上建立一个共享秘密。ECDH与经典的DHKE(迪菲-赫尔曼密钥交换)算法非常相似,但它使用椭圆曲线的点乘法而不是模指数运算。ECDH基于以下椭圆曲线点的性质:
(a * G) * b = (b * G) * a
如果我们有两个密钥a和b(分别属于Alice和Bob的私钥),以及一个ECC椭圆曲线和生成点G,我们可以通过不安全的信道交换值(a * G)和(b * G)(即Alice和Bob的公钥),然后我们可以派生出一个共享秘密:secret = (a * G) * b = (b * G) * a。非常简单。
上述方程可以表达为:
alicePubKey * bobPrivKey = bobPubKey * alicePrivKey = secret
ECDH算法(椭圆曲线迪菲-赫尔曼密钥交换)非常简单:
- Alice生成一个随机的ECC密钥对:{alicePrivKey, alicePubKey = alicePrivKey * G}
- Bob生成一个随机的ECC密钥对:{bobPrivKey, bobPubKey = bobPrivKey * G}
- Alice和Bob通过不安全的信道交换他们的公钥(例如通过互联网)
- Alice计算sharedKey = bobPubKey * alicePrivKey
- Bob计算sharedKey = alicePubKey * bobPrivKey
现在,Alice和Bob都拥有相同的sharedKey == bobPubKey * alicePrivKey == alicePubKey * bobPrivKey
3.2 算法协议
3.2.1 ECDH mbedtls API使用样例
详细细节请参考《密码技术与物联网安全:mbedtls开发实战》第十章ECDH密钥协商;
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#if defined(MBEDTLS_ECDH_C)
#include <stdio.h>
#include "string.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/ecdh.h"
#define GENERATOR "2"
#define T_P "FFFFFFFFFFFFFFFFADF85458A2BB4A9AAFDC5620273D3CF1D8B9C583CE2D3695" \
"A9E13641146433FBCC939DCE249B3EF97D2FE363630C75D8F681B202AEC4617A"\
"D3DF1ED5D5FD65612433F51F5F066ED0856365553DED1AF3B557135E7F57C935"\
"984F0C70E0E68B77E2A689DAF3EFE8721DF158A136ADE73530ACCA4F483A797A"\
"BC0AB182B324FB61D108A94BB2C8E3FBB96ADAB760D7F4681D4F42A3DE394DF4"\
"AE56EDE76372BB190B07A7C8EE0A6D709E02FCE1CDF7E2ECC03404CD28342F61"\
"9172FE9CE98583FF8E4F1232EEF28183C3FE3B1B4C6FAD733BB5FCBC2EC22005"\
"C58EF1837D1683B2C6F34A26C1B2EFFA886B423861285C97FFFFFFFFFFFFFFFF"
static uint8_t buf[65];
static void dump_buf(uint8_t *buf, uint32_t len)
{
int i;
for (i = 0; i < len; i++) {
printf("%s%02X%s", i % 16 == 0 ? "\r\n\t" : " ",
buf[i],
i == len - 1 ? "\r\n" : "");
}
}
int mbedtls_ecdh_test(void)
{
int ret;
size_t olen;
const char *pers = "ecdh_test";
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_ecp_point client_pub, server_pub;
mbedtls_ecp_group grp;
mbedtls_mpi client_secret, server_secret;
mbedtls_mpi client_pri, server_pri;
/* 1. init structure */
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_mpi_init(&client_secret);
mbedtls_mpi_init(&server_secret);
mbedtls_mpi_init(&client_pri);
mbedtls_mpi_init(&server_pri);
mbedtls_ecp_group_init(&grp);
mbedtls_ecp_point_init(&client_pub);
mbedtls_ecp_point_init(&server_pub);
/* 2. update seed with we own interface ported */
printf( "\n . Seeding the random number generator..." );
ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char *) pers,
strlen(pers));
if(ret != 0) {
printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* 3. select ecp group SECP256R1 */
printf("\n . Select ecp group SECP256R1...");
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecp_group_load returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf("ok\r\n");
/* 4. Client generate public parameter */
printf("\n . Client Generate public parameter...");
ret = mbedtls_ecdh_gen_public(&grp, &client_pri, &client_pub, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdh_gen_public returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show public parameter */
mbedtls_ecp_point_write_binary(&grp, &client_pub, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, buf, sizeof(buf));
dump_buf(buf, olen);
/* 5. Client generate public parameter */
printf("\n . Server Generate public parameter...");
ret = mbedtls_ecdh_gen_public(&grp, &server_pri, &server_pub, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdh_gen_public returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show public parameter */
mbedtls_ecp_point_write_binary(&grp, &server_pub, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, buf, sizeof(buf));
dump_buf(buf, olen);
/* 6. Calc shared secret */
printf("\n . Client Calc shared secret...");
ret = mbedtls_ecdh_compute_shared(&grp, &client_secret, &server_pub, &client_pri, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdh_compute_shared returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show public parameter */
mbedtls_mpi_write_binary(&client_secret, buf, sizeof(buf));
dump_buf(buf, olen);
/* 7. Server Calc shared secret */
printf("\n . Server Calc shared secret...");
ret = mbedtls_ecdh_compute_shared(&grp, &server_secret, &client_pub, &server_pri, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdh_compute_shared returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show public parameter */
mbedtls_mpi_write_binary(&server_secret, buf, sizeof(buf));
dump_buf(buf, olen);
/* 8. mpi compare */
ret = mbedtls_mpi_cmp_mpi(&server_secret, &client_secret);
printf("compare result: %d\r\n", ret);
exit:
/* 10. release structure */
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_mpi_free(&client_secret);
mbedtls_mpi_free(&server_secret);
mbedtls_mpi_free(&client_pri);
mbedtls_mpi_free(&server_pri);
mbedtls_ecp_group_free(&grp);
mbedtls_ecp_point_free(&client_pub);
mbedtls_ecp_point_free(&server_pub);
return ret;
}
#endif /* MBEDTLS_DHM_C */
4 ECDSA 加签验签
4.1简介
ECDSA(Elliptic Curve Digital Signature Algorithm)是一种基于椭圆曲线密码学(ECC)的加密安全数字签名方案。ECDSA依赖于有限域上椭圆曲线的循环群数学以及ECDLP问题(椭圆曲线离散对数问题)的复杂性。ECDSA的签名/验证算法基于椭圆曲线点乘法,并按以下方式工作。相比RSA,ECDSA的密钥和签名长度更短,但提供相同的安全级别。256位的ECDSA签名具有与3072位RSA签名相同的安全强度。
ECDSA使用经典的魏尔斯特拉斯形式下的有限域上的椭圆曲线(EC)进行加密。这些曲线通过它们的EC域参数来描述,由各种密码学标准(如SECG: SEC 2和Brainpool - RFC 5639)指定。在密码学中使用的椭圆曲线定义了以下内容:
- 生成点G,用于对曲线进行标量乘法(将整数与椭圆曲线点相乘)
- 由G生成的EC点子群的阶数n,该阶数定义了私钥的长度(例如256位)
DSA和ECDSA的一个特点是,它们需要为每个签名生成产生一个新的随机值(下文称为k)。为了有效的安全性,必须使用加密安全过程从模整数集中随机和统一地选择k。即使在该过程中存在轻微偏差,也可能转化为对签名方案的攻击。
需要密码学安全的随机源证明是某些架构中部署DSA和ECDSA签名方案的障碍,特别是嵌入式系统,如智能卡。在这些系统中,RSA签名算法通常更受欢迎,即按照公钥密码学标准(PKCS) #1 [RFC3447](采用"类型1"填充而不是概率签名方案(PSS))和ISO 9796-2 [ISO-9796-2]规定使用,即使它计算上更昂贵,因为RSA(通过这种填充方案)是确定性的,因此不需要随机源。
DSA和ECDSA的随机性也使得实现更难进行测试。自动化测试无法可靠地检测实现是否使用了足够高质量的随机源。这使得实现过程更容易受到灾难性故障的影响,通常是在系统部署并成功受到攻击后才被发现。
确保随机源的质量对于DSA和ECDSA的安全性至关重要。为了减轻这一难点,有一些方法可以在实践中提高实现的可测试性和安全性,例如使用确定性ECDSA算法或采取其他随机数生成方案。
此外,对于任何密码学算法的实现,包括DSA和ECDSA,都需要进行严格的安全审计和测试,以确保其符合安全标准,并抵御已知的攻击。这样可以最大程度地降低实现过程中可能出现的漏洞和失败的风险。
4.2 ECDSA算法
4.2.1 ECDSA签名算法
ECDSA签名算法(RFC 6979)以消息msg和私钥privKey作为输入,并生成一个由一对整数{r, s}组成的签名。ECDSA签名算法基于ElGamal签名方案,其工作原理如下(经过简化):
- 使用密码散列函数(如SHA-256)计算消息的散列值:h = hash(msg)mod n。
- 在范围[1..n-1]内安全地生成一个随机数k。在确定性ECDSA的情况下,k的值是从h and privKey进行HMAC派生的(参见RFC 6979)。
- 计算随机点R = k * G,并取其x坐标:r = R.x。
- 计算签名证明:
其中,
是一个整数,满足
。
- 返回签名{r, s}。
计算得到的签名{r, s}是一对整数,每个整数在范围[1...n-1]内。它编码了随机点R = k * G以及一个证明s,确认签名者知道消息h和私钥privKey。该证明s可以使用相应的公钥pubKey进行验证。
需要确保签名生成过程中使用的随机数k是安全生成的,否则攻击者可能能够猜测私钥privKey或伪造签名。确定性ECDSA方案(RFC 6979)通过使用基于消息散列值h和私钥privKey的确定性算法来生成k,解决了这个问题。
4.2.2 ECDSA验签算法
验证ECDSA签名的算法以签名消息msg、由签名算法生成的签名{r, s}以及与签名者私钥对应的公钥pubKey作为输入,并输出一个布尔值:有效或无效的签名。ECDSA签名验证算法的工作原理如下(经过简化):
- 使用与签名过程中相同的密码散列函数计算消息的散列值:h = hash(msg)。
- 计算签名证明的模反元素:s1 =
。
- 恢复用于签名的随机点:R' = (h * s1) * G + (r * s1) * pubKey。
- 从R'中获取其x坐标:r' = R'.x。
- 通过比较r'是否等于r来计算签名验证结果。 签名验证的一般思想是使用公钥恢复点R',并检查它是否与在签名过程中随机生成的点R相同。
如果签名验证通过,即r'与r相等,则可以确认签名是有效的,并且发起者确实拥有私钥。否则,如果r'与r不相等,则可以确认签名无效,要么消息被篡改,要么使用了错误的公钥进行验证。
请注意,确保公钥和签名完整且正确地传输以及保持私密性对于正确的签名验证至关重要
ECDSA签名{r, s}的简单解释如下: 签名过程将随机点R(仅由其x坐标表示)通过使用私钥privKey和消息散列h进行椭圆曲线变换,编码为一个数字s,这个数字s是证明消息签名者知道私钥privKey的凭证。由于椭圆曲线离散对数问题(ECDLP)的困难性,签名{r, s}无法揭示私钥。
签名验证过程通过使用公钥pubKey和消息散列h对签名中的证明数字s进行解码,将其还原为原始点R,并将其x坐标与签名中的r值进行比较。
4.2.3 ECDSA 数学证明
在签名验证过程中计算的点R'恢复方程可以进行如下转换,将pubKey替换为privKey * G:
R' = (h * s1) * G + (r * s1) * pubKey = (h * s1) * G + (r * s1) * privKey * G = (h + r * privKey) * s1 * G
如果我们采用在签名过程中计算得到的数字
我们可以通过以下方式计算
=
=
现在,将s1替换到点R'中。
R' = (h + r * privKey) * s1 * G
=
=
最后一步是将由pubKey解码的点R'与由privKey编码的点R进行比较。实际上,该算法仅比较R'和R的x坐标:整数r'和r。 如果签名有效,则预期r' == r;如果签名或消息或公钥不正确,则预期r' ≠ r。
计算样例
Alice通过ECDSA密钥对生成方法计算出公钥和私钥, 并将公钥发送给Bob, 私钥由Alice自己保管。 ECDSA生成密钥对的具体过程如下:
- 选择椭圆曲线E: y2≡x3+2x+2 mod 17, 生成元为G=(5,1), 循环群的阶n=19。
- 选择一个随机数d=7;
- 计算Q=d·G=7·(5,1)=(0,6);
- 得到私钥(7), 公钥(17, 2, 2, (5, 1), 19, (0, 6))。
Alice使用私钥对消息M进行ECDSA签名计算, 得到签名结果,并将签名结果和消息一起发送给Bob。
- 选择一个随机数k=10;
- 计算R=k·G=10·(5,1)=(7,11);
- 计算z=HASH(M)=26;
- 设置r=xR mod n→r=7;
- 计算s≡(z+dr)k-1mod n→s≡(26+7·7)10-1mod 19→s=17;
- 得到签名(7,17)。
Bob收到签名结果和消息后, 使用得到的公钥对消息签名进行验
证。 ECDSA验证签名的具体过程如下:
- 计算w≡s-1mod n→w≡17-1mod 19→w=9;
- 计算z=HASH(M)=26;
- 计算u1≡(wz)mod n→u1≡(9·26)mod 19→u1=6;
- 计算u2≡(wr)mod n→u2≡(9·7)mod 19→u2=6;
- 计算P=u1·g+u2·Q=6·(5,1)+6·(0,6)=(7,11);
- xP≡r mod n→7≡7 mod 19, 签名有效。
4.2.4 ECDSA: Sign / Verify - Examples
from pycoin.ecdsa import generator_secp256k1, sign, verify
import hashlib, secrets
def sha3_256Hash(msg):
hashBytes = hashlib.sha3_256(msg.encode("utf8")).digest()
return int.from_bytes(hashBytes, byteorder="big")
def signECDSAsecp256k1(msg, privKey):
msgHash = sha3_256Hash(msg)
signature = sign(generator_secp256k1, privKey, msgHash)
return signature
def verifyECDSAsecp256k1(msg, signature, pubKey):
msgHash = sha3_256Hash(msg)
valid = verify(generator_secp256k1, pubKey, msgHash, signature)
return valid
mbedtls ECDSA example 详细细节请参考《密码技术与物联网安全:mbedtls开发实战》 11.5 ECDSA数字签名
#if !defined(MBEDTLS_CONFIG_FILE)
#include "mbedtls/config.h"
#else
#include MBEDTLS_CONFIG_FILE
#endif
#if defined(MBEDTLS_ECDSA_C)
#include <stdio.h>
#include "string.h"
#include "mbedtls/entropy.h"
#include "mbedtls/ctr_drbg.h"
#include "mbedtls/ecdsa.h"
static uint8_t buf[97];
static void dump_buf(uint8_t *buf, uint32_t len)
{
int i;
for (i = 0; i < len; i++) {
printf("%s%02X%s", i % 16 == 0 ? "\r\n\t" : " ",
buf[i],
i == len - 1 ? "\r\n" : "");
}
}
int mbedtls_ecdsa_test(void)
{
int ret;
size_t qlen, dlen;
size_t rlen, slen;
uint8_t hash[32];
const char *msg = "HelloWorld";
const char *pers = "ecdsa_test";
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_mpi r, s;
mbedtls_ecdsa_context ctx;
mbedtls_md_context_t md_ctx;
/* 1. init structure */
mbedtls_md_init(&md_ctx);
mbedtls_entropy_init(&entropy);
mbedtls_ctr_drbg_init(&ctr_drbg);
mbedtls_mpi_init(&r);
mbedtls_mpi_init(&s);
/* 2. update seed with we own interface ported */
printf( "\n . Seeding the random number generator..." );
ret = mbedtls_ctr_drbg_seed( &ctr_drbg, mbedtls_entropy_func, &entropy,
(const unsigned char *) pers,
strlen(pers));
if(ret != 0) {
printf( " failed\n ! mbedtls_ctr_drbg_seed returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* 3. hash message */
printf( "\n . Hash message..." );
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), (uint8_t *)msg, strlen(msg), hash);
if(ret != 0) {
printf( " failed\n ! mbedtls_md returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show hash */
dump_buf(hash, sizeof(hash));
/* 4. generate keypair */
printf( "\n . Generate ecdsa keypair..." );
ret = mbedtls_ecdsa_genkey(&ctx, MBEDTLS_ECP_DP_SECP256R1, mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_genkey returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show keypair */
mbedtls_ecp_point_write_binary(&ctx.grp, &ctx.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &qlen, buf, sizeof(buf));
dlen = mbedtls_mpi_size(&ctx.d);
mbedtls_mpi_write_binary(&ctx.d, buf + qlen, dlen);
dump_buf(buf, qlen + dlen);
/* 5. ecdsa sign */
printf( "\n . ECDSA sign..." );
ret = mbedtls_ecdsa_sign(&ctx.grp, &r, &s, &ctx.d, hash, sizeof(hash), mbedtls_ctr_drbg_random, &ctr_drbg);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_sign returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
/* show sign */
rlen = mbedtls_mpi_size(&r);
slen = mbedtls_mpi_size(&s);
mbedtls_mpi_write_binary(&r, buf, rlen);
mbedtls_mpi_write_binary(&s, buf + rlen, slen);
dump_buf(buf, rlen + slen);
/* 6. ecdsa verify */
printf( "\n . ECDSA verify..." );
ret = mbedtls_ecdsa_verify(&ctx.grp, hash, sizeof(hash), &ctx.Q, &r, &s);
if(ret != 0) {
printf( " failed\n ! mbedtls_ecdsa_verify returned %d(-0x%04x)\n", ret, -ret);
goto exit;
}
printf( " ok\n" );
exit:
/* 7. release structure */
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_entropy_free(&entropy);
mbedtls_mpi_free(&r);
mbedtls_mpi_free(&s);
mbedtls_md_free(&md_ctx);
mbedtls_ecdsa_free(&ctx);
return ret;
}
#endif /* MBEDTLS_DHM_C */
4.2.5 EdDSA
EdDSA(Edwards曲线数字签名算法)是一种基于性能优化的椭圆曲线,例如255位曲线Curve25519和448位曲线Curve448-Goldilocks的现代安全数字签名算法。 EdDSA签名分别使用椭圆曲线的Edwards形式(出于性能原因),即edwards25519和edwards448。 EdDSA算法基于Schnorr签名算法,并依赖于ECDLP问题的难度。 EdDSA签名算法及其变体Ed25519和Ed448在RFC 8032中进行了技术描述。
5. ECC加密解密
5.1 ECDH+AES
在本节中,我们将解释如何实现基于椭圆曲线的公钥加密/解密(基于ECC的非对称加密方案)。这是一个复杂的过程,通常涉及到混合加密方案的设计,其中包括ECC密码学、ECDH密钥交换和对称加密算法。
假设我们有一对ECC私钥和公钥。我们希望使用这些密钥来进行数据的加密和解密。根据定义,非对称加密的工作方式如下:如果我们使用私钥加密数据,我们将能够通过相应的公钥来解密密文:
上述过程可以直接应用于RSA密码系统,但不适用于ECC。椭圆曲线密码学(ECC)并不直接提供加密方法。相反,我们可以通过使用ECDH(椭圆曲线迪菲-赫尔曼)密钥交换方案来派生一个共享的密钥,用于对称数据加密和解密。
大多数混合加密方案的工作方式(加密过程):
大多数混合加密方案的工作方式(解密过程):
from tinyec import registry
from Crypto.Cipher import AES
import hashlib, secrets, binascii
def encrypt_AES_GCM(msg, secretKey):
aesCipher = AES.new(secretKey, AES.MODE_GCM)
ciphertext, authTag = aesCipher.encrypt_and_digest(msg)
return (ciphertext, aesCipher.nonce, authTag)
def decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey):
aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce)
plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag)
return plaintext
def ecc_point_to_256_bit_key(point):
sha = hashlib.sha256(int.to_bytes(point.x, 32, 'big'))
sha.update(int.to_bytes(point.y, 32, 'big'))
return sha.digest()
curve = registry.get_curve('brainpoolP256r1')
def encrypt_ECC(msg, pubKey):
ciphertextPrivKey = secrets.randbelow(curve.field.n)
sharedECCKey = ciphertextPrivKey * pubKey
secretKey = ecc_point_to_256_bit_key(sharedECCKey)
ciphertext, nonce, authTag = encrypt_AES_GCM(msg, secretKey)
ciphertextPubKey = ciphertextPrivKey * curve.g
return (ciphertext, nonce, authTag, ciphertextPubKey)
def decrypt_ECC(encryptedMsg, privKey):
(ciphertext, nonce, authTag, ciphertextPubKey) = encryptedMsg
sharedECCKey = privKey * ciphertextPubKey
secretKey = ecc_point_to_256_bit_key(sharedECCKey)
plaintext = decrypt_AES_GCM(ciphertext, nonce, authTag, secretKey)
return plaintext
msg = b'Text to be encrypted by ECC public key and ' \
b'decrypted by its corresponding ECC private key'
print("original msg:", msg)
privKey = secrets.randbelow(curve.field.n)
pubKey = privKey * curve.g
encryptedMsg = encrypt_ECC(msg, pubKey)
encryptedMsgObj = {
'ciphertext': binascii.hexlify(encryptedMsg[0]),
'nonce': binascii.hexlify(encryptedMsg[1]),
'authTag': binascii.hexlify(encryptedMsg[2]),
'ciphertextPubKey': hex(encryptedMsg[3].x) + hex(encryptedMsg[3].y % 2)[2:]
}
print("encrypted msg:", encryptedMsgObj)
decryptedMsg = decrypt_ECC(encryptedMsg, privKey)
print("decrypted msg:", decryptedMsg)
5.2 ECIES Hybrid Encryption Scheme
一个类似于之前展示的代码的混合加密方案被标准化,并在许多加密标准(如SECG SEC-1、ISO/IEC 18033-2、IEEE 1363a和ANSI X9.63)中命名为Elliptic Curve Integrated Encryption Scheme(ECIES)。ECIES是一种公钥认证加密方案,其工作原理类似于上述代码示例,但使用密钥派生函数(KDF)从ECDH共享密钥中派生出独立的MAC密钥和对称加密密钥 。
①选择一个在 1 和 n - 1 之间的临时随机整数k,计算 R = kG = ( Gx,Gy) 。
②将点 R 转换成八进制字符串 R’。
③由临时密钥 k 和公钥 Q 得到共享秘密域元素 z,( 例如 P = ( Gx,Gy) = kQ,令 z = xp) 。
④将 z 转换成八进制字符串 Z。
⑤根据配置文件用 KDF 函数从 Z 和[共享消息#1] 产生密钥数据 K。
⑥按照配置文件从 K 中提取加密密钥 EK 和MAC 密钥 MK。
⑦根据配置文件中的对称加密函数和 EK,加密消息 M 得到密文 EM。
⑧根据配置文件中的 MAC 函数和 MK,由 EM‖[共享消息#2] 得到 D。
⑨输出 C = R’‖EM‖D。
根据密文 C = R’‖EM‖D, 以及私钥 d, 接受者解密 C 恢复明文 M 的流程如下:
①将八进制字符串 R’转换成椭圆曲线点 R =( xR,yR) 。
②得到共享秘密域元素 z,( 例如 dR = dkG =kQ = P = P = ( Gx,Gy) ,令 z = xp) 。
③将 z 转换成八进制字符串 Z。
④用 KDF 函数从 Z 和[共享消息#1] 产生密钥数据 K。
⑤从 K 中提取加密密钥 EK 和 MAC 密钥 MK。
⑥用 MK 计算 MAC,通过与 D 进行比较来确定发送者的身份。
⑦用 EK 解密 EM 得到明文 M。
- ECIES加密的输入由接收方的公钥和明文消息组成。输出包括发送方的临时公钥(密文公钥)、加密的消息(密文+对称算法参数)和认证标签(MAC代码):
ECIES-encrypt(接收方公钥, 明文消息) ➔ { 密文公钥, 加密的消息, 认证标签 }
- ECIES解密需要使用加密的输出、接收方的私钥,并产生原始的明文消息或检测出问题(例如完整性/身份验证错误):
ECIES-decrypt(密文公钥, 加密的消息, 认证标签, 接收方私钥) ➔ 明文消息
ECIES加密方案是一个框架,而不是一个具体的算法。它可以通过插入不同的算法来实现,例如使用secp256k1或P-521椭圆曲线进行公钥计算,使用PBKDF2或Scrypt进行密钥派生函数,使用AES-CTR或AES-GCM或ChaCha20-Poly1305进行对称加密和认证标签,使用HMAC-SHA512进行MAC算法(在未经身份验证的加密情况下)。