国密SM9算法C++实现之七:加密解密算法

SM9算法C++实现系列目录:

国密SM9算法C++实现之七:加密解密算法

加密算法流程

SM9标准文档中描述的加密算法流程如下所示:
这里写图片描述

其流程图为:
这里写图片描述

根据算法描述,定义接口函数:

	/**
	* 加密
	*
	* @param masterPublicKey 加密主公钥
	* @param id 用户ID
	* @param data 待加密数据
	* @param isBaseBlockCipher 指明加密明文的方法:true-分组密码;false-基于KDF的序列密码
	* @param macKeyLen MAC函数的密钥字节长度,应不少于MAC函数中杂凑函数的杂凑值长度。此处可设置32字节
	* @return 加密值
	* @throw std::exception SM9_ERROR_NOT_INIT | SM9_ERROR_CALC_RATE
	*/
	static Cipher encrypt(const string& masterPublicKey, const string& id, const string& data, bool isBaseBlockCipher, int macKeyLen);

对于加密和解密算法中的明文加密方法,这里定义isBaseBlockCipher 参数来指示;同时,加解密算法中有两个长度,一个是用分组密码加密时的K1_len,因为使用的是SM4,所以是固定的16字节;另一个是K2_len,这个是MAC函数的密钥长度。按照MAC函数定义来说,这个长度是可以变的,但最好不小于MAC函数中的哈希函数的哈希值长度,这里因为使用的是SM3,所以默认是32字节。此处提供一个macKeyLen参数表示此K2_len长度,可以根据需要去掉该参数。

加密结果值

SM9加密结果内部包括C1、C2和C3三个部分,对此,也简单将其封装为一个类。这个封装是可取的,可以方便解密时的数据提取。

Cipher.h

#ifndef YY_SM9_CIPHER_INCLUDE_H__
#define YY_SM9_CIPHER_INCLUDE_H__

#pragma once

#include <string>
using namespace std;

/**
* 加密值,封装了加密结果,包括C1、C2、C3.
* @author YaoYuan
*/
class Cipher {
public:
	Cipher() {}

	Cipher(const string& C1, const string& C2, const string& C3) {
		mC1 = C1;
		mC2 = C2;
		mC3 = C3;
	}

	~Cipher() {}

public:
	string getC1() const { return mC1; }
	string getC2() const { return mC2; }
	string getC3() const { return mC3; }

	/**
	* 加密数据结构构造函数.
	* @param cipherData 加密数据,可由{@link #toByteArray()}获得
	*/
	static Cipher fromByteArray(const string& cipherData) {
		string C1 = string(cipherData, 0, 64);
		string C3 = string(cipherData, 64, 32);
		string C2 = string(cipherData, 64 + 32, cipherData.length() - 96);
		return Cipher(C1, C2, C3);
	}

	string toByteArray() {
		return mC1 + mC3 + mC2;
	}

private:
	string mC1;
	string mC2;
	string mC3;

};


#endif

加密算法实现

按照加密流程,实现加密算法:

Cipher SM9::encrypt(const string& masterPublicKey, const string& id, const string& data, bool isBaseBlockCipher, int macKeyLen)
{
	Cipher cipher;
	bool hasException = true;
	big h1 = NULL;
	big r = NULL;
	epoint *Ppube = NULL;
	epoint *QB = NULL;
	epoint *C1 = NULL;
	ZZN12 g;
	ZZN12 w;
	string sC1, sC2, sC3, sW, bufferZ, sK, sK1, sK2, tmp, hashH1;
	int klen = 0;
	int k1len = 16; // key length for sm4
	int k2len = macKeyLen;

#ifdef SELF_CHECK
	string hashH1Hex, QBHex, rHex, C1Hex, C2Hex, C3Hex, gHex, wHex, K1Hex, K2Hex;
#endif

	if( !mIsInit ) {
		mErrorNum = SM9_ERROR_NOT_INIT;
		throw exception(getErrorMsg().c_str());
	}

	Parameters::init_big(h1);
	Parameters::init_epoint(Ppube);
	Parameters::init_epoint(QB);
	Parameters::init_epoint(C1);
	Parameters::init_big(r);

	hashH1 = KGC::H1(id, HID_ENCRYPT);
	Parameters::cin_big(h1, hashH1.c_str(), hashH1.length());
	Parameters::cin_epoint(Ppube, masterPublicKey.c_str());

#ifdef SELF_CHECK
	hashH1Hex = YY::YHex::bin2Hex(hashH1);
#endif

	// Step1 : QB=[H1(IDB||hid, N)]P1+Ppub-e
	ecurve_mult(h1, Parameters::param_P1, QB);
	ecurve_add(Ppube, QB);
	
#ifdef SELF_CHECK
	QBHex = YY::YHex::bin2hex(Parameters::cout_epoint(QB));
#endif

	while( true ) {
#ifdef SELF_CHECK
		rHex = YY::YHex::hex2bin("AAC0541779C8FC45E3E2CB25C12B5D2576B2129AE8BB5EE2CBE5EC9E785C");
		Parameters::cin_big(r, rHex.c_str(), rHex.length());
#else
		// Step2: generate r
		bigrand(Parameters::param_N, r);
#endif

		// Step3 : C1=[r]QB
		ecurve_mult(r, QB, C1);
		sC1 = Parameters::cout_epoint(C1);

#ifdef SELF_CHECK
		C1Hex = YY::YHex::bin2Hex(sC1);
#endif

		// Step4 : g=e(Ppub-e, P2)
		if( !ZZN12::calcRatePairing(g, Parameters::param_P2, Ppube, Parameters::param_t, Parameters::norm_X) ) {
			mErrorNum = SM9_ERROR_CALC_RATE;
			goto END;
		}

#ifdef SELF_CHECK
		gHex = YY::YHex::bin2Hex(g.toByteArray());
#endif

		// Step5 : calculate w=g^r
		w = g.pow(r);
		sW = w.toByteArray();

#ifdef SELF_CHECK
		wHex = YY::YHex::bin2Hex(sW);
#endif

		// Step6_1 : K = KDF(C1 || w || IDB, klen)
		if( isBaseBlockCipher ) {
			klen = k1len + k2len;
		} else {
			klen = data.length() + k2len;
		}

		bufferZ.append(sC1);
		bufferZ.append(sW);
		bufferZ.append(id);
		sK = KGC::KDF(bufferZ, klen);

		sK1 = string(sK, 0, sK.length()-k2len);
		sK2 = string(sK, sK1.length(), sK.length()-sK1.length());

#ifdef SELF_CHECK
		K1Hex = YY::YHex::bin2Hex(sK1);
		K2Hex = YY::YHex::bin2Hex(sK2);
#endif

		if( !isAllZero(sK1) )
			break;
	}
	
	// Step6_2
	if( isBaseBlockCipher ) {
		// C2=Enc(K1,M)
		YY::YCipher sm4Cipher = YY::YCipher(YY::YCipher::SM4, YY::YCipher::ECB, YY::YCipher::PKCS5Padding);
		sm4Cipher.init(YY::YCipher::ENCRYPT, sK1);
		sC2 = sm4Cipher.update(data);
		tmp = sm4Cipher.doFinal();
		sC2.append(tmp);
	} else {
		// C2=M^K1
		for( int i = 0; i < data.length(); i++ ) {
			sC2.append(1, data[i] ^ sK1[i]);
		}
	}
	
	// Step7 : C3=MAC(K2,C2)
	sC3 = MAC(sK2, sC2);

#ifdef SELF_CHECK
	C2Hex = YY::YHex::bin2Hex(sC2);
	C3Hex = YY::YHex::bin2Hex(sC3);
#endif

	// Step8 : C=C1|C3|C2
	cipher = Cipher(sC1, sC2, sC3);
	hasException = false;

END:
	Parameters::release_big(h1);
	Parameters::release_big(r);
	Parameters::release_epoint(Ppube);
	Parameters::release_epoint(QB);
	Parameters::release_epoint(C1);

	if( hasException ) {
		throw exception(getErrorMsg().c_str());
	}
	return cipher;
}

解密算法流程

SM9标准文档中描述的解密算法流程如下所示:
这里写图片描述

其流程为:
这里写图片描述

根据算法描述,定义接口函数:

	/**
	* 解密
	*
	* @param cipher 加密值
	* @param prikey 用户私钥
	* @param id 用户ID
	* @param isBaseBlockCipher 指明加密明文的方法:true-分组密码;false-基于KDF的序列密码
	* @param macKeyLen MAC函数的密钥字节长度,应不少于MAC函数中杂凑函数的杂凑值长度。此处可设置32字节
	* @return 原始数据
	* @throw std::exception SM9_ERROR_NOT_INIT | SM9_ERROR_CALC_RATE | 
	* SM9_ERROR_DECRYPT_C1_NOT_ON_G1 | SM9_ERROR_DECRYPT_K1_IS_ZERO | SM9_ERROR_DECRYPT_C3_VERIFY_FAILED
	*/
	static string decrypt(const Cipher& cipher, const string& prikey, const string& id, bool isBaseBlockCipher, int macKeyLen);

这里的后两个参数在使用时应该和加密时保持一致。

解密算法实现


std::string SM9::decrypt(const Cipher& cipher, const string& prikey, const string& id, bool isBaseBlockCipher, int macKeyLen)
{
	bool hasException = true;
	string result, sC1, sC2, sC3, sK1, sK2, sK, sW, bufferZ, tmp, u, M;
	epoint* C1 = NULL;
	ecn2 de;
	ZZN12 w;
	YY::YSM3 sm3Digest;
	int klen = 0;
	int k1len = 16; // key length for sm4
	int k2len = macKeyLen;

#ifdef SELF_CHECK
	string wHex, K1Hex, K2Hex, uHex, MHex;
#endif

	if( !mIsInit ) {
		mErrorNum = SM9_ERROR_NOT_INIT;
		throw exception(getErrorMsg().c_str());
	}

	sC1 = cipher.getC1();
	sC2 = cipher.getC2();
	sC3 = cipher.getC3();
	Parameters::init_epoint(C1);
	Parameters::init_ecn2(de);
	
	// Step1 : check if C1 is on G1
	Parameters::cin_epoint(C1, sC1.c_str());
	if( !Parameters::isPointOnG1(C1) ) {
		mErrorNum = SM9_ERROR_DECRYPT_C1_NOT_ON_G1;
		goto END;
	}

	// Step2 : w=e(c,de)
	Parameters::cin_ecn2_byte128(de, prikey.c_str());
	if( !ZZN12::calcRatePairing(w, de, C1, Parameters::param_t, Parameters::norm_X) ) {
		mErrorNum = SM9_ERROR_CALC_RATE;
		goto END;
	}
	sW = w.toByteArray();

#ifdef SELF_CHECK
	wHex = YY::YHex::bin2Hex(sW);
#endif

	// Step3_1 : K = KDF(C1 || w || IDB, klen)
	if( isBaseBlockCipher ) {
		klen = k1len + k2len;
	} else {
		klen = sC2.length() + k2len;
	}

	bufferZ.append(sC1);
	bufferZ.append(sW);
	bufferZ.append(id);
	sK = KGC::KDF(bufferZ, klen);

	sK1 = string(sK, 0, sK.length() - k2len);
	sK2 = string(sK, sK1.length(), sK.length() - sK1.length());

#ifdef SELF_CHECK
	K1Hex = YY::YHex::bin2Hex(sK1);
	K2Hex = YY::YHex::bin2Hex(sK2);
#endif

	if( isAllZero(sK1) ) {
		mErrorNum = SM9_ERROR_DECRYPT_K1_IS_ZERO;
		goto END;
	}

	// Step3_2
	if( isBaseBlockCipher ) {
		// M=Dec(K1,C2)
		YY::YCipher sm4Cipher = YY::YCipher(YY::YCipher::SM4, YY::YCipher::ECB, YY::YCipher::PKCS5Padding);
		sm4Cipher.init(YY::YCipher::DECRYPT, sK1);
		M = sm4Cipher.update(sC2);
		tmp = sm4Cipher.doFinal();
		M.append(tmp);
	} else {
		// M=C2^K1
		for( int i = 0; i < sC2.length(); i++ ) {
			M.append(1, sC2[i] ^ sK1[i]);
		}
	}

	// Step4 : u=MAC(K2,C2)
	u = MAC(sK2, sC2);
	if( u.compare(sC3) != 0 ) {
		mErrorNum = SM9_ERROR_DECRYPT_C3_VERIFY_FAILED;
		goto END;
	}

#ifdef SELF_CHECK
	MHex = YY::YHex::bin2Hex(M);
	uHex = YY::YHex::bin2Hex(u);
#endif

	// Step5
	result = M;
	hasException = false;

END:
	Parameters::release_epoint(C1);
	Parameters::release_ecn2(de);

	if( hasException ) {
		throw exception(getErrorMsg().c_str());
	}
	return result;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值