一、橢圓曲線密碼算法
橢圓曲線:是一類二元多項式方程,它的解構成一個橢圓曲線。
橢圓曲線參數:定義一條唯一的橢圓曲線。介紹其中兩個參數G(基點)和n(階)。G點(xG, yG)是橢圓曲線上的基點, 有限域橢圓曲線上所有其他的點都可以通過G點的倍乘運算計算得到,即P=[d]G, d也是屬于有限域,d的最大值為素數n。
有限域上的橢圓曲線:橢圓曲線上的解不是連續的,而是離散的,解的值滿足有限域的限制。有限域有兩種,Fp和F2m。
E(Fq):Fq上橢圓曲線E 的所有有理點(包括無窮遠點O)組成的集合。
Fp:一個素整數的集合,最大值為P-1,集合中的值都是素數,里面元素滿足以下模運算: a+b=(a+b) mod p 和 ab=(ab) mod p。
SM2:有限域Fp上的一條橢圓曲線,其橢圓曲線參數是固定值。
公私鑰:P=[d]G,G是已知的,大數d為私鑰,點P(XP, YP)為公鑰。
SM2推薦使用素數域256位橢圓曲線:
-->EC_GROUP_new_by_curve_name(NID_sm2p256v1)
//可以得出固定參數
//Sm2 中指定的參數 確定下y2 = x3 + ax + b 曲線
#define _P "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"
#define _a "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"
#define _b "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"
#define _n "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
#define _Gx "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"
#define _Gy "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
- OpenSSL部分涉及代碼
//初始化一個空算法組
EC_GROUP *group = EC_GROUP_new(EC_GFp_mont_method());
//初始化一個推薦橢圓曲線的算法組
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
//上下文
BN_CTX *ctx = BN_CTX_new();
//創建EC_KEY,使用推薦橢圓曲線
EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1)
//生成公鑰私鑰
EC_KEY_generate_key(ec_key);
//設置私鑰
EC_KEY_set_private_key(ec_key, d);
//設置公鑰
EC_KEY_set_public_key(ec_key, P);
//通過ec_key獲取算法組
EC_GROUP *ec_group = EC_KEY_get0_group(ec_key);
//獲取基點G
EC_POINT * G = EC_GROUP_get0_generator(ec_group);
//大數初始化
BIGNUM *rand = BN_new();
//EC_POINT初始化
EC_POINT *P = EC_POINT_new(ec_group);
//獲取坐標點p的x,y值
EC_POINT_get_affine_coordinates_GFp(ec_group,p,x,y,ctx);
//Gets the order of a EC_GROUP -- n階 對應上面固定參數的_n
EC_GROUP_get_order(ec_group, order, ctx);
//隨機數生成
do {
BN_rand_range(rand,order);
} while (BN_is_zero(rand));
//大數轉二進制
int len = BN_bn2bin(bn, outChar);
//獲取坐標點p轉大數bn
EC_POINT_point2bn(ec_group, p, POINT_CONVERSION_COMPRESSED, bn, ctx);
//點的乘積 lP = P * rand
EC_POINT_mul(ec_group, lP, NULL, P, rand, ctx);
//驗證點C1是否在橢圓曲線上
EC_POINT_is_on_curve(ec_group, c1, ctx);
馬上開始
二、SM2加密算法(手動實現和使用GMSSL庫實現)
PS:加解密中,加密時橢圓曲線點C1轉換方式必須和解密時橢圓曲線點C1轉換方式一致,否則無法解出C1。
1、手動實現
-
流程
image.png 算法:
1、產生隨機數k, k的值從1到n-1;
BIGNUM *n,*k;
n = BN_new();
k = BN_new();
EC_GROUP_get_order(ec_group, n, ctx);
do {
BN_rand_range(k,n);
} while (BN_is_zero(k));
2、計算橢圓曲線點C1=[k]G=(x1,y1), 將C1使用EC_POINT_point2oct轉換成比特串;
//獲取基點G
const EC_POINT *G = EC_GROUP_get0_generator(ec_group);
EC_POINT *c1 = NULL;
c1 = EC_POINT_new(ec_group);
unsigned char c1bin[65];
unsigned long c1binlen = 65;
EC_POINT_mul(ec_group, c1, NULL, G, k, ctx);
EC_POINT_point2oct(ec_group, c1, POINT_CONVERSION_UNCOMPRESSED, c1bin, c1binlen, ctx);
3、 驗證公鑰PB, 計算S=[h] PB,如果S是無窮遠點,出錯退出;
EC_POINT_is_on_curve(ec_group, PB, ctx);
EC_POINT_is_at_infinity(ec_group, s);
4、計算(x2,y2)=[k] PB
EC_POINT *tempPoint = EC_POINT_new(ec_group);
BIGNUM *x2 = BN_new();
BIGNUM *y2 = BN_new();
EC_POINT_mul(ec_group, tempPoint, NULL, pb, k, ctx);
EC_POINT_get_affine_coordinates_GFp(ec_group,
tempPoint, x2, y2, ctx);
5、計算t=KDF(x2||y2, klen), KDF是密鑰派生函數,klen是明文長度。
unsigned char x2y2[64] = {0};
unsigned long x2y2len = 0;
//x2||y2
x2y2len += BN_bn2bin(x2, x2y2);
x2y2len += BN_bn2bin(y2, &x2y2[32]);
unsigned char t[klen];
unsigned long tlen = klen;
kdf(EVP_sm3(), x2y2, sizeof(x2y2), t, &tlen);
6、計算C2=M^t (此處^為異或)
unsigned char c2[tlen];
unsigned long c2len = 0;
for (int i = 0; i < tlen; i ++) {
c2[i] = M[i] ^ t[i];
c2len++;
}
7、 計算C3=Hash(x2||M||y2)
unsigned char c3[32];
unsigned long c3len = 32;
unsigned char tempC3[x2y2len+klen];
BN_bn2bin(x2, tempC3);
BN_bn2bin(y2, &tempC3[32+klen]);
memcpy(&tempC3[32], M, klen);
sm3(tempC3, x2y2len+klen, c3);
8、 輸出密文C=C1||C3||C2。
unsigned char c[c1binlen + c2len + c3len];
unsigned long clen = c1binlen + c2len + c3len;
memcpy(c, c1bin, c1binlen);
memcpy(&c[c1binlen], c3, c3len);
memcpy(&c[c1binlen+c3len], c2, c2len);
注:密文分為C1,C2,C3,三部分,C1長度是65字節(具體根據轉換方式),C2是明文的長度,C3是32字節(Hash使用sm3)。
注:C1 || C2 || C3 的意思就是拼在一起,而不是做什么或運算
- 根據國密推薦的SM2橢圓曲線公鑰密碼算法,首先產生隨機數計算出曲線點C1,2個32byte的BIGNUM大數,即為SM2加密結果的第1部分(C1)。第2部分則是真正的密文,是對明文的加密結果,長度和明文一樣(C2)。第3部分是雜湊值,用來效驗數據(C3)。按國密推薦的256位橢圓曲線,明文加密結果比原長度會大97byte(C1使用EC_POINT_point2oct轉換)。
- 注:通過密鑰派生函數計算,才能進行第6步的按位異或計算。
2、使用GMSSL庫實現
- 基于GmSSL 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
使用gmssl SM2加密
@param inData 需要加密的數據
@param inDataLen 需要加密的數據長度
@param pubKey 公鑰(point2oct)
@param pubKeyLen 公鑰長度
@param encryptData 加密后的數據
@return 0:成功/非0:失敗
*/
int sm2EncryptWithGmssl(
unsigned char *inData,
unsigned long inDataLen,
unsigned char *pubKey,
unsigned long pubKeyLen,
SM2CiphertextValue **encryptData)
{
int resultCode = Result_OK;
//公鑰
EC_KEY *ec_key = NULL;
//公鑰
EC_POINT *publicKey = NULL;
//ec_group
EC_GROUP *ec_group = NULL;
//ctx
BN_CTX *ctx = NULL;
//判斷輸入參數是否為空
if (inData == NULL || inDataLen == 0 || pubKey == NULL || pubKeyLen == 0 || encryptData == NULL) {
resultCode = Result_InputErr;
goto err;
}
//獲取公鑰
ctx = BN_CTX_new();
ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
publicKey = EC_POINT_new(ec_group);
int mark = EC_POINT_oct2point(ec_group, publicKey, pubKey, pubKeyLen, ctx);
if (mark != 1) {
resultCode = Result_EncErr;
goto err;
}
//初始化數據
ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1);
EC_KEY_set_public_key(ec_key, publicKey);
//調用gmssl SM2加密
if (!(*encryptData = SM2_do_encrypt(EVP_sm3(), inData, inDataLen, ec_key))) {
resultCode = Result_EncErr;
goto err;
}
err:
if (ec_key != NULL) {
EC_KEY_free(ec_key);
}
if (ec_group != NULL) {
EC_GROUP_free(ec_group);
}
if (ctx != NULL) {
BN_CTX_free(ctx);
}
if (publicKey != NULL) {
EC_POINT_free(publicKey);
}
return resultCode;
}
三、SM2解密算法
1、手動實現
-
流程
image.png
- 算法:
1、從密文比特串C=C1||C3||C2中取出C1, 將C1轉換成橢圓曲線上的點;
#define POINT_BIN_LENGTH 65
unsigned char c1Bin[POINT_BIN_LENGTH];
unsigned long c1Binlen = POINT_BIN_LENGTH;
memcpy(c1Bin, encrypt(密文), POINT_BIN_LENGTH);
EC_POINT *c1 = EC_POINT_new(ec_group);
EC_POINT_oct2point(ec_group, c1, c1Bin, c1Binlen, ctx);
2、驗證C1, 計算S=[h] C1,如果S是無窮遠點,出錯退出;
int resultCode = EC_POINT_is_on_curve(ec_group, c1, ctx);
if (resultCode) {
printf("驗證C1成功\n");
}else{
printf("驗證C1失敗\n");
}
3、計算(x2,y2)=[dB] C1
EC_POINT *dC1 = EC_POINT_new(ec_group);
EC_POINT_mul(ec_group, dC1, NULL, c1, d, ctx);
BIGNUM *x2 = BN_new();
BIGNUM *y2 = BN_new();
EC_POINT_get_affine_coordinates_GFp(ec_group,
dC1, x2, y2, ctx);
4、計算t=KDF(x2||y2, klen), KDF是密鑰派生函數,如果t是全0比特串,出錯退出。
unsigned char x2y2[64] = {0};
unsigned long x2y2len = 0;
//x2||y2
x2y2len += BN_bn2bin(x2, x2y2);
x2y2len += BN_bn2bin(y2, &x2y2[32]);
//原文長度klen
unsigned long klen = encryptLen - (c1Binlen+c3len);
unsigned char t[klen];
unsigned long tlen = klen;
sm3_kdf1(EVP_sm3(), x2y2, sizeof(x2y2), t, &tlen);
5、從C=C1||C3||C2中取出C2,計算M’= C2+t。
unsigned char c2[tlen];
memcpy(c2, encrypt+c1Binlen+c3len, tlen);
//原文
unsigned char M[tlen+1];
unsigned long Mlen = 0;
for (int i = 0; i < tlen; i ++) {
M[i] = c2[i] ^ t[i];
Mlen++;
}
M[tlen] = '\0';
printf("M'-->%s\n",M);
6、計算u=Hash(x2||M’||y2),比較u是否與C3相等,不相等則退出。
7、輸出明文M’。
2、使用GMSSL庫實現
- 基于GmSSL 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
使用GMSSL解密
@param cv 加密數據
@param d 私鑰
@param decryptData 解密數據
@param decryptDataLen 解密數據長度
@return 0成功/其它失敗
*/
int sm2DecryptWithGmssl(SM2CiphertextValue *cv,BIGNUM *d,unsigned char *decryptData,unsigned long *decryptDataLen)
{
int resultCode = 0;
BN_CTX *ctx = NULL;
EC_GROUP *ec_group = NULL;
EC_KEY *ec_key = NULL;
//bn_prime
BIGNUM *prime = NULL;
//判斷輸入參數是否為空
if (cv == NULL || d == NULL || decryptData == NULL) {
resultCode = Result_InputErr;
goto end;
}
//初始化
ctx = BN_CTX_new();
ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
//設置私鑰
ec_key = EC_KEY_new();
EC_KEY_set_group(ec_key, ec_group);
EC_KEY_set_private_key(ec_key, d);
//prime
prime = BN_new();
BN_hex2bn(&prime,SM2_n);
//C = C1||C3||C2 -- C為加密數據encryptData
if (!SM2_do_decrypt(EVP_sm3(), cv, decryptData, decryptDataLen, ec_key))
{
resultCode = Result_DecErr;
goto end;
}
printf("\n Decrypt Data-->%s\n",decryptData);
end:
if (ctx != NULL) {
BN_CTX_free(ctx);
}
if (ec_group != NULL) {
EC_GROUP_free(ec_group);
}
if (ec_key != NULL) {
EC_KEY_free(ec_key);
}
if (prime != NULL) {
BN_free(prime);
}
if (d != NULL) {
BN_free(d);
}
return resultCode;
}
四、結論
- 想要成功解密出原文,必須是公鑰PB和私鑰dB是匹配的,即滿足PB=[dB]G,原文經過兩次與同一比特串的異或計算,結果還是原文。