SM2 encryption and decryption algorithm (based on GMSSL C code implementation) [non original, record it]

1, Elliptic curve cryptography
Elliptic curve: it is a kind of bivariate polynomial equation whose solution forms an elliptic curve.

Elliptic curve parameter: defines a unique elliptic curve. Two parameters g (base point) and n (order) are introduced. G point (xG, yG) is the base point on the elliptic curve. All other points on the elliptic curve in the finite field can be calculated by multiplying G point, that is, P=[d]G, d belongs to the finite field, and the maximum value of d is the prime number n.

Elliptic curve on finite field: the solution on elliptic curve is not continuous, but discrete, and the value of solution meets the limit of finite field. There are two kinds of finite fields, Fp and F2m.

E(Fq): a set of all rational points (including infinity O) of elliptic curve E on Fq.

Fp: a set of prime integers. The maximum value is P-1. All the values in the set are prime numbers. The elements in the set satisfy the following modular operations: a+b=(a+b) mod p and ab=(ab) mod p.

SM2: an elliptic curve on the finite field Fp, whose parameters are fixed.

Public private key: P=[d]G, G is known, the large number d is the private key, and point P(XP, YP) is the public key.

SM2 recommends using 256 bit elliptic curve in prime field:
–>EC_GROUP_new_by_curve_name(NID_sm2p256v1)

//Fixed parameters can be obtained
//The parameters specified in Sm2 determine the curve y2 = x3 + ax + b
#define _P "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF"
#define _a "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC"
#define _b "28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93"
#define _n "FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123"
#define _Gx "32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7"
#define _Gy "BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0"
Code involved in OpenSSL
//Initialize an empty algorithm group
EC_GROUP *group = EC_GROUP_new(EC_GFp_mont_method());
//Initializing an algorithm group of recommended elliptic curves
EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
//Context
BN_CTX *ctx = BN_CTX_new();
//Create EC_KEY, use recommended elliptic curve
EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1)
//Generate public key and private key
EC_KEY_generate_key(ec_key);
//Set private key
EC_KEY_set_private_key(ec_key, d);
//Set public key
EC_KEY_set_public_key(ec_key, P);
//Through ec_key get algorithm group
EC_GROUP *ec_group = EC_KEY_get0_group(ec_key);
//Get base point G
EC_POINT * G = EC_GROUP_get0_generator(ec_group);
//Large number initialization
BIGNUM *rand = BN_new();
//EC_POINT initialization
EC_POINT *P = EC_POINT_new(ec_group);
//Get the x, y value of coordinate point p
EC_POINT_get_affine_coordinates_GFp(ec_group,p,x,y,ctx);
//Gets the order of a EC_GROUP - order n corresponds to the ﹐ n of the above fixed parameter
EC_GROUP_get_order(ec_group, order, ctx);
//Random number generation
do {
BN_rand_range(rand,order);
} while (BN_is_zero(rand));
//Big data to binary
int len = BN_bn2bin(bn, outChar);
//Obtain the coordinate point p to the largest number bn
EC_POINT_point2bn(ec_group, p, POINT_CONVERSION_COMPRESSED, bn, ctx);
//Product of point lP = P * rand
EC_POINT_mul(ec_group, lP, NULL, P, rand, ctx);
//Verify that point C1 is on the elliptic curve
EC_POINT_is_on_curve(ec_group, c1, ctx);
Start now
2, SM2 encryption algorithm (manual implementation and GMSSL library implementation)
PS: in encryption and decryption, the conversion mode of elliptic curve point C1 must be the same as that of elliptic curve point C1 in decryption, otherwise C1 cannot be solved.
1. Manual implementation
technological process

image.png
Algorithm:

1. The random number k is generated, and the value of K is from 1 to 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. Calculate the elliptic curve point C1=[k]G=(x1,y1), and use EC for C1_ POINT_ Point 2oct is converted into bit string;

//Get base point 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. Verify the public key PB, and calculate S=[h] PB. If S is an infinite point, exit in error;

EC_POINT_is_on_curve(ec_group, PB, ctx);
EC_POINT_is_at_infinity(ec_group, s);

4. Calculation (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. Calculate t = KDF (x2|| Y2, klen). KDF is the key derivation function, klen is the plaintext length.

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);

//kdf method
void *kdf(const EVP_MD *md, const void *in, size_t inlen,
void *out, size_t *outlen)
{
EVP_MD_CTX ctx;
uint32_t counter = 1;
uint32_t counter_be;
unsigned char dgst[EVP_MAX_MD_SIZE];
unsigned int dgstlen;
unsigned char *pout = out;
size_t rlen = *outlen;
size_t len;

EVP_MD_CTX_init(&ctx);

while (rlen > 0) {
    counter_be = cpu_to_be32(counter);
    counter++;
    
    EVP_DigestInit(&ctx, md);
    EVP_DigestUpdate(&ctx, in, inlen);
    EVP_DigestUpdate(&ctx, &counter_be, sizeof(counter_be));
    EVP_DigestFinal(&ctx, dgst, &dgstlen);
    
    len = dgstlen <= rlen ? dgstlen : rlen;
    memcpy(pout, dgst, len);
    rlen -= len;
    pout += len;
}

EVP_MD_CTX_cleanup(&ctx);
return out;

}
6. Calculate C2=M^t (here ^ is exclusive or)

unsigned char c2[tlen];
unsigned long c2len = 0;
for (int i = 0; i < tlen; i ++) {
    c2[i] = M[i] ^ t[i];
    c2len++;
}

7. Calculation 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. Output ciphertext 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);

Note: the ciphertext is divided into three parts: C1,C2,C3. The length of C1 is 65 bytes (according to the conversion method), C2 is the length of clear text, and C3 is 32 bytes (Hash uses sm3).
Note: C1 | C2 | C3 means to put together, not to do anything or operation

According to the SM2 elliptic curve public key cryptosystem recommended by guomi, the first step is to generate a random number to calculate the curve point C1, two 32 byte BIGNUM large numbers, which is the first part (C1) of SM2 encryption results. The second part is the real ciphertext, which is the result of encrypting plaintext. The length is the same as plaintext (C2). The third part is the hash value, which is used to verify the data (C3). According to the 256 bit elliptic curve recommended by the National Security Council, the plaintext encryption result will be 97 bytes larger than the original length (C1 uses EC_POINT_point2oct conversion).
Note: the bitwise exclusive or calculation in step 6 can only be performed through key derivation function calculation.
2. Implementation with GMSSL Library
Based on GmSSL 1.2.2 (OpenSSL 1.0.2d)
/**
Repair BN_ When the result of bn2bin function is not 32 (high complement 0)
@param sourceBn source BIGNUM
@param out output
@Return return length
*/
int fixBn2Bin(BIGNUM sourceBn,unsigned char out){
unsigned char tempBin[32] = {0};
int tempLen = BN_bn2bin(sourceBn, tempBin);
if (tempLen != 32) {
memset(out, 0, 32 - tempLen);
}
memcpy(&out[32-tempLen], tempBin, tempLen);
return 32;
}
#define RV_OK 0x00000000 //success
#define RV_EncErr 0x01000010 //encrypt error
/
SM2 encryption uses GMSSL Library of national security

@param P public key
@param encryptData data to be encrypted
@param outData output encrypted data
@param outDataLen output encrypted data length
@return 0 success / other failure
*/
int sm2EncryptByGMSSL(EC_POINT *P,unsigned char *encryptData,unsigned long encryptDataLen,unsigned char *outData,unsigned long *outDataLen){
int resultCode = RV_OK;
//Variables
EC_KEY *ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1);
EC_KEY_set_public_key(ec_key, P);
const EC_GROUP *ec_group = EC_KEY_get0_group(ec_key);

BN_CTX *ctx = BN_CTX_new();
SM2_CIPHERTEXT_VALUE *cv = NULL;
SM2_ENC_PARAMS params;

//c1
BIGNUM *x = NULL;
BIGNUM *y = NULL;
unsigned char *c1Buf = NULL;
unsigned long c1Len = 0;

//Start encryption (using GMSSL method)
SM2_ENC_PARAMS_init_with_recommended(&params);
if (!(cv = SM2_do_encrypt(&params, encryptData, encryptDataLen, ec_key))) {
    resultCode = RV_EncErr;
    goto end;
}
OPENSSL_assert(cv);
//C1
//Determine the space needed for c1
if (!(c1Len = EC_POINT_point2oct(ec_group, cv->ephem_point,
                                 params.point_form, NULL, 0, ctx))) {
    resultCode = RV_EncErr;
    goto end;
}
//Space required to create c1
c1Buf = malloc(c1Len);
if (!(c1Len = EC_POINT_point2oct(ec_group, cv->ephem_point,
                                 params.point_form, c1Buf, c1Len, ctx))) {
    resultCode = RV_EncErr;
    goto end;
}
memcpy(outData, c1Buf, c1Len);
//Get C1 method 2

// unsigned char c1Bin[64] = {0};
// x = BN_new();
// y = BN_new();
// EC_POINT_get_affine_coordinates_GFp(ec_group,cv->ephem_point, x, y, ctx);
//
// fixBn2BinLen(x,c1Bin);
// fixBn2BinLen(y,&c1Bin[32]);
/// / splice C1
// memcpy(outData, c1Bin, 64);

//Subsequent splicing
//Splice C3
memcpy(outData+c1Len, cv -> mactag, cv->mactag_size);
//Splice C2
memcpy(outData+c1Len+cv->mactag_size, cv ->ciphertext, cv->ciphertext_size);
*outDataLen = c1Len + cv->mactag_size + cv->ciphertext_size;

end:
EC_KEY_free(ec_key);
BN_CTX_free(ctx);
if(cv != NULL) SM2_CIPHERTEXT_VALUE_free(cv);
if(x != NULL) BN_free(x);
if(y != NULL) BN_free(y);
if (c1Buf != NULL) {
free(c1Buf);
}
return resultCode;
}
Based on GmSSL 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
Using gmssl SM2 encryption

@param inData data to be encrypted
@param inDataLen length of data to be encrypted
@param pubKey public key (point2oct)
@param pubKeyLen public key length
@Data encrypted by param encryptData
@return 0: success / non-0: failure
*/
int sm2EncryptByGMSSL(
unsigned char *inData,
unsigned long inDataLen,
unsigned char *pubKey,
unsigned long pubKeyLen,
SM2CiphertextValue **encryptData)
{
int resultCode = RV_OK;
//Public key
EC_KEY *ec_key = NULL;
//Public key
EC_POINT *publicKey = NULL;
//ec_group
EC_GROUP *ec_group = NULL;
//ctx
BN_CTX *ctx = NULL;

//Judge whether the input parameter is empty
if (inData == NULL || inDataLen == 0 || pubKey == NULL || pubKeyLen == 0 || encryptData == NULL) {
    resultCode = RV_InputErr;
    goto err;
}

//Recover public key
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 = RV_EncErr;
    goto err;
}

//Initialization data
ec_key = EC_KEY_new_by_curve_name(NID_sm2p256v1);
EC_KEY_set_public_key(ec_key, publicKey);
ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
ctx = BN_CTX_new();

//Start encryption (using GMSSL method)
if (!(*encryptData = SM2_do_encrypt(EVP_sm3(), inData, inDataLen, ec_key))) {
    resultCode = RV_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;
}
3, SM2 decryption algorithm
1. Manual implementation
technological process

image.png
Algorithm:
1. Take C1 from the ciphertext bit string c = c1|c3|c2, and convert C1 to a point on the elliptic curve;

#define POINT_BIN_LENGTH 65

unsigned char c1Bin[POINT_BIN_LENGTH];
unsigned long c1Binlen = POINT_BIN_LENGTH;
memcpy(c1Bin, encrypt(ciphertext), POINT_BIN_LENGTH);
EC_POINT  *c1 = EC_POINT_new(ec_group);
EC_POINT_oct2point(ec_group, c1, c1Bin, c1Binlen, ctx);

2. Verify C1 and calculate S=[h] C1. If S is an infinite point, exit in error;

int resultCode = EC_POINT_is_on_curve(ec_group, c1, ctx);
if (resultCode) {
    printf("verification C1 success\n");
}else{
    printf("verification C1 fail\n");
}

3. Calculation (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. Calculate t = KDF (x2| Y2, klen). KDF is the key derivation function. If t is all zero bit string, it exits in error.

unsigned char x2y2[64] = {0};
unsigned long x2y2len = 0;
//x2||y2
x2y2len += BN_bn2bin(x2, x2y2);
x2y2len += BN_bn2bin(y2, &x2y2[32]);
//Original length 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. Take C2 from C = c1|c3|c2 and calculate M '= C2+t.

unsigned char c2[tlen];
memcpy(c2, encrypt+c1Binlen+c3len, tlen);

//original text
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, Calculate u=Hash(x2||M '||y2), compare whether u is equal to C3, otherwise exit.
7. Output plaintext M '.

2. Implementation with GMSSL Library
Based on GmSSL 1.2.2 (OpenSSL 1.0.2d)

#define RV_DecErr 0x01000011 //decrypt error

/**
Decryption using GMSSL

@param d private key
@param encrypt encrypt data
@param encryptLen encrypted data length
@param decryptData decrypt data
@param decryptDataLen decrypt data length
@return 0 success / other failure
*/
int sm2DecryptByGMSSL(BIGNUM *d,unsigned char *encrypt,unsigned long encryptLen,unsigned char *decryptData,unsigned long *decryptDataLen)
{
int resultCode = 0;

BN_CTX *ctx = NULL;
SM2_CIPHERTEXT_VALUE *cv = NULL;
//C1+C3 size (excluding ciphertext_size, C2)
int cvLen = 0;
SM2_ENC_PARAMS params;

EC_GROUP *ec_group = NULL;
EC_KEY *ec_key = NULL;
//bn_prime
BIGNUM *prime = NULL;
//c1 length
int c1Len = 0;

//initialization
ctx = BN_CTX_new();
SM2_ENC_PARAMS_init_with_recommended(&params);
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 = BN_new();
BN_hex2bn(&prime,_n);

//Obtain C1 (C = c1||||||||||c2) -- C is the encrypted data
if (!(cvLen = SM2_CIPHERTEXT_VALUE_size(ec_group, &params, 0))) {
    resultCode = RV_DecErr;
    goto end;
}

if (!(cv = OPENSSL_malloc(sizeof(SM2_CIPHERTEXT_VALUE)))) {
    resultCode = RV_DecErr;
    goto end;
}

cv->ephem_point = EC_POINT_new(ec_group);
cv->ciphertext_size = encryptLen - cvLen;
cv->ciphertext = OPENSSL_malloc(cv->ciphertext_size);
if (!cv->ephem_point || !cv->ciphertext) {
    resultCode = RV_DecErr;
    goto end;
}
int macTagSize = params.mactag_size<0 ? EVP_MD_size(params.mac_md) : params.mactag_size;
c1Len = cvLen - macTagSize;

if (!EC_POINT_oct2point(ec_group, cv->ephem_point, encrypt, c1Len, ctx)) {
    resultCode = RV_DecErr;
    goto end;
}

cv->mactag_size = macTagSize;
if (cv->mactag_size > 0) {
    memcpy(cv->mactag, encrypt + c1Len, cv->mactag_size);
}

memcpy(cv->ciphertext, encrypt + c1Len + cv->mactag_size, cv->ciphertext_size);

if (!SM2_do_decrypt(&params, cv, decryptData, decryptDataLen, ec_key))
{
    resultCode = RV_DecErr;
    goto end;
}
*decryptDataLen = cv->ciphertext_size;

end:
if (ctx != NULL) {
BN_CTX_free(ctx);
}
if (cv != NULL) {
SM2_CIPHERTEXT_VALUE_free(cv);
}
if (ec_group != NULL) {
EC_GROUP_free(ec_group);
}
if (ec_key != NULL) {
EC_KEY_free(ec_key);
}
if (prime != NULL) {
BN_free(prime);
}
return resultCode;
}
Based on GmSSL 2.5.4 - OpenSSL 1.1.0d 3 Sep 2019
/**
Decrypt using GMSSL

@param cv encrypting data
@param d private key
@param decryptData decrypt data
@param decryptDataLen decrypt data length
@return 0 success / other failure
*/
int sm2DecryptByGMSSL(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;

//Judge whether the input parameter is empty
if (cv == NULL || d == NULL || decryptData == NULL) {
    resultCode = RV_InputErr;
    goto end;
}


//initialization
ctx = BN_CTX_new();
ec_group = EC_GROUP_new_by_curve_name(NID_sm2p256v1);
//Set private key
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 is encrypted data
if (!SM2_do_decrypt(EVP_sm3(), cv, decryptData, decryptDataLen, ec_key))
{
    resultCode = RV_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;
}
4, Conclusion
In order to successfully decrypt the original text, the public key PB and private key dB must match, that is to say, PB=[dB]G must be satisfied. After two exclusive or calculations with the same bit string, the original text will still be the original text.

By Devil_Chen
Link: https://www.jianshu.com/p/24dd4d79630d
Source: Jianshu

Tags: OpenSSL Big Data

Posted on Sat, 20 Jun 2020 04:22:35 -0400 by rane500