国密SM9算法C++实现之八:密钥交换算法

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

  • 基于JPBC的SM9算法的java实现与测试

  • 国密SM9算法C++实现之0:源码下载地址

  • 国密SM9算法C++实现之一:算法简介

  • 国密SM9算法C++实现之二:测试工具

  • 国密SM9算法C++实现之三:椭圆曲线接口、参数初始化

  • 国密SM9算法C++实现之四:基本功能函数与KGC接口的实现

  • 国密SM9算法C++实现之五:签名验签算法

  • 国密SM9算法C++实现之六:密钥封装解封算法

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

  • 国密SM9算法C++实现之八:密钥交换算法

  • 国密SM9算法C++实现之九:算法功能与测试例子

国密SM9算法C++实现之八:密钥交换算法

文章目录

  • 国密SM9算法C++实现之八:密钥交换算法
    • @[toc]
    • 密钥交换算法流程
      • 密钥交换结果值
      • 密钥协商初始化:keyExchange_init
      • 临时密钥对:TempKeyPair
      • KeyAgreement .h
      • 接口描述
      • 密钥交换算法实现

密钥交换算法流程

SM9标准文档中描述的密钥交换算法流程如下所示:
国密SM9算法C++实现之八:密钥交换算法_第1张图片

其流程图为:
国密SM9算法C++实现之八:密钥交换算法_第2张图片

密钥交换结果值

SM9密钥交换结果包括两个可选的哈希验证值和一个共享密钥值,,对此,也简单将其封装为一个类。

密钥协商初始化:keyExchange_init

根据算法描述和算法流程图,可以看到,在密钥交换的第1步~第3部中,需要产生一个[1,N-1]范围内的随机数,并计算G1群上的倍点,可以个过程理解为生成G1群上的一对公私钥,私钥就是随机数,公钥就是倍点,点是通过主公钥和G1的几点P1运算后得到的,倍数就是私钥。
同时,在A4和B7中,需要将公钥发送给对方。因此,此处将前3步提取出来,作为密钥交换的初始化部分,这个接口命名为keyExchange_init

临时密钥对:TempKeyPair

keyExchange_init密钥协商初始化的结果是产生了一对G1上的临时公私钥,将其封装为TempKeyPair,并放在密钥交换结果值之中。

KeyAgreement .h

#ifndef YY_SM9_KEYAGREEMENT_INCLUDE_H__
#define YY_SM9_KEYAGREEMENT_INCLUDE_H__

#pragma once

#include 
using namespace std;

/**
* 密钥交换结果值类,包括两个hash验证值和共享密钥.
* @author YaoYuan
*/
class KeyAgreement {
public:
	KeyAgreement() {}

	KeyAgreement(const string& shareKey, const string& hashA2, const string& hashB1) {
		mShareKey = shareKey;
		mHashA2 = hashA2;
		mHashB1 = hashB1;
	}

	~KeyAgreement() {}

public:
	string getShareKey() {
		return mShareKey;
	}

	string getHashA2() {
		return mHashA2;
	}

	string getHashB1() {
		return mHashB1;
	}

private:
	/** 共享密钥. */
	string mShareKey;
	/** 选项S_A或者S_2. */
	string mHashA2;
	/** 选项S_B或者S_1. */
	string mHashB1;
};



/**
* 密钥交换中的临时密钥对,是群G1上的一对公私钥.
* @author YaoYuan
*/
class TempKeyPair {
public:
	TempKeyPair() {};
	TempKeyPair(const string& prikey, const string& pubkey) : mPrikey(prikey), mPubkey(pubkey) { }

public:
	string getPrivate() const { return mPrikey; }
	string getPublic() const { return mPubkey; }

private:
	string mPrikey;
	string mPubkey;
};

#endif

接口描述

根据上面的描述,定义两个接口函数:

	/**
	* 密钥交换步骤1:初始化
	*
	* @param masterPublicKey 加密主公钥
	* @param othId 对方ID
	* @return 临时密钥对
	* @throw std::exception SM9_ERROR_NOT_INIT
	*/
	static TempKeyPair keyExchange_init(const string& masterPublicKey, const string& othId);


	/**
	* 密钥交换步骤2:计算共享密钥
	*
	* @param masterPublicKey 加密主公钥
	* @param isSponsor true-发起方;false-响应方
	* @param myId 己方ID
	* @param othId 对方ID
	* @param myTempKeyPair 己方临时密钥对
	* @param othTempPubkey 对方临时公钥
	* @param klen 要计算的共享密钥字节长度
	* @return 密钥交换值
	* @throw std::exception SM9_ERROR_NOT_INIT | SM9_ERROR_CALC_RATE | SM9_ERROR_KEYEXCHANGE_R_NOT_ON_G1
	*/
	static KeyAgreement keyExchange(const string& masterPublicKey, bool isSponsor, const string& myId, const string& othId, 
									const string& myPrikey, const TempKeyPair& myTempKeyPair, const string& othTempPubkey, int klen);

需要注意的是,密钥交换初始化时使用的对方的ID。
因为密钥交换的发起方和响应方的算法流程是一样的,因此将其封装为一个函数,只是在传入参数是需要区别一下己方和对方。

密钥交换算法实现

按照密钥交换流程,实现密钥交换算法:


TempKeyPair SM9::keyExchange_init(const string& masterPublicKey, const string& othId)
{
	string sR, sr, hashH1, sg, sg3, sg0, bufferZ;
	big h1 = NULL;
	big r = NULL;
	epoint *Ppube = NULL;
	epoint *Qoth = NULL;
	epoint *R = NULL;

#ifdef SELF_CHECK
	string H1Hex, QothHex, rHex, RHex, gHex, g3Hex, g0Hex;
#endif

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

	Parameters::init_big(h1);
	Parameters::init_big(r);
	Parameters::init_epoint(Qoth);
	Parameters::init_epoint(Ppube);
	Parameters::init_epoint(R);

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

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

	// Step1 : QB =[H1(IDB||hid, N)]P1 +Ppub-e or QA = [H1(IDA || hid, N)]P1 + Ppub-e
	ecurve_mult(h1, Parameters::param_P1, Qoth);
	ecurve_add(Ppube, Qoth);

#ifdef SELF_CHECK
	QothHex = YY::YHex::bin2hex(Parameters::cout_epoint(Qoth));
#endif

#ifdef SELF_CHECK
	if( othId=="Bob" )
		rHex = YY::YHex::hex2bin("5879DD1D51E175946F23B1B41E93BA31C584AE59A426EC1046A4D03B06C8");
	else
		rHex = YY::YHex::hex2bin("018B98C44BEF9F8537FB7D071B2C928B3BC65BD3D69E1EEE213564905634FE");
	Parameters::cin_big(r, rHex.c_str(), rHex.length());
#else
	// Step2: generate r
	bigrand(Parameters::param_N, r);
#endif

	// Step3 : RA = [rA]QB or RB= [rB]QA
	ecurve_mult(r, Qoth, R);
	sr = Parameters::cout_big(r);
	sR = Parameters::cout_epoint(R);

	Parameters::release_big(h1);
	Parameters::release_big(r);
	Parameters::release_epoint(Qoth);
	Parameters::release_epoint(Ppube);
	Parameters::release_epoint(R);

	return TempKeyPair(sr, sR);
}

KeyAgreement SM9::keyExchange(const string& masterPublicKey, bool isSponsor, const string& myId, const string& othId, 
							  const string& myPrikey, const TempKeyPair& myTempKeyPair, const string& othTempPubkey, int klen)
{
	KeyAgreement keyAgreement;
	bool hasException = true;
	string sKey, sg1, sg2, sg3, bufferZ, bufferTmp, srmy, sRmy, sRoth, SB1, SA2, tmp;
	epoint *Ppube = NULL;
	epoint *Qoth = NULL;
	epoint *Rmy = NULL;
	epoint *Roth = NULL;
	big rmy = NULL;
	ZZN12 g1;
	ZZN12 g2;
	ZZN12 g3;
	ZZN12 gtmp;
	ecn2 de;
	YY::YSM3 sm3Digest;

#ifdef SELF_CHECK
	string g1Hex, g2Hex, g3Hex;
#endif

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

	Parameters::init_epoint(Qoth);
	Parameters::init_epoint(Ppube);
	Parameters::init_epoint(Rmy);
	Parameters::init_epoint(Roth);
	Parameters::init_big(rmy);
	Parameters::init_ecn2(de);

	srmy = myTempKeyPair.getPrivate();
	sRmy = myTempKeyPair.getPublic();
	Parameters::cin_big(rmy, srmy.c_str(), srmy.length());
	Parameters::cin_epoint(Rmy, sRmy.c_str());
	sRoth = othTempPubkey;
	Parameters::cin_epoint(Roth, sRoth.c_str());

	// check R is on G1
	if( !Parameters::isPointOnG1(Roth) ) {
		mErrorNum = SM9_ERROR_KEYEXCHANGE_R_NOT_ON_G1;
		goto END;
	}

	// StepA5_B4: g=e(Ppub-e,P2)^r
	Parameters::cin_epoint(Ppube, masterPublicKey.c_str());
	if( !ZZN12::calcRatePairing(gtmp, Parameters::param_P2, Ppube, Parameters::param_t, Parameters::norm_X) ) {
		mErrorNum = SM9_ERROR_CALC_RATE;
		goto END;
	}
	if( isSponsor )
		g1 = gtmp.pow(rmy);
	else
		g2 = gtmp.pow(rmy);

	// g=e(Roth,de)
	Parameters::cin_ecn2_byte128(de, myPrikey.c_str());
	if( !ZZN12::calcRatePairing(gtmp, de, Roth, Parameters::param_t, Parameters::norm_X) ) {
		mErrorNum = SM9_ERROR_CALC_RATE;
		goto END;
	}
	if( isSponsor )
		g2 = gtmp;
	else
		g1 = gtmp;

	// g3=g^r
	if( isSponsor )
		g3 = g2.pow(rmy);
	else
		g3 = g1.pow(rmy);

	sg1 = g1.toByteArray();
	sg2 = g2.toByteArray();
	sg3 = g3.toByteArray();

#ifdef SELF_CHECK
	g1Hex = YY::YHex::bin2Hex(sg1);
	g2Hex = YY::YHex::bin2Hex(sg2);
	g3Hex = YY::YHex::bin2Hex(sg3);
#endif

	// Step6 : S1 or SB
	bufferTmp.resize(0);
	if( isSponsor ) {
		bufferTmp.append(myId);
		bufferTmp.append(othId);
		bufferTmp.append(sRmy);
		bufferTmp.append(sRoth);
	} else {
		bufferTmp.append(othId);
		bufferTmp.append(myId);
		bufferTmp.append(sRoth);
		bufferTmp.append(sRmy);
	}

	bufferZ.resize(0);
	bufferZ.append(sg2);
	bufferZ.append(sg3);

	bufferZ.append(bufferTmp);
	sm3Digest.update(bufferZ);
	sm3Digest.finish();
	tmp = sm3Digest.getData();

	bufferZ.resize(0);
	bufferZ.append(1, (char)0x82);
	bufferZ.append(sg1);
	bufferZ.append(tmp);
	sm3Digest.update(bufferZ);
	sm3Digest.finish();
	SB1 = sm3Digest.getData();

	// StepA8_B7 : SA or S2
	bufferZ[0] = (char)0x83;
	sm3Digest.update(bufferZ);
	sm3Digest.finish();
	SA2 = sm3Digest.getData();

	// StepA7_B5 : SKA or SKB
	bufferZ.resize(0);
	bufferZ.append(bufferTmp);
	bufferZ.append(sg1);
	bufferZ.append(sg2);
	bufferZ.append(sg3);
	sKey = KGC::KDF(bufferZ, klen);

	keyAgreement = KeyAgreement(sKey, SA2, SB1);
	hasException = false;

END:
	Parameters::release_epoint(Qoth);
	Parameters::release_epoint(Ppube);
	Parameters::release_epoint(Rmy);
	Parameters::release_epoint(Roth);
	Parameters::release_big(rmy);
	Parameters::release_ecn2(de);

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

总结算法实现,实际的密钥交换流程为:

  1. 首先发送方需要知道响应方的ID,然后发起方用响应方的ID调用keyExchange_init,生成一对临时密钥对,并将其中的公钥和自己的ID告知响应方。
  2. 响应方用发起方的ID调用keyExchange_init,生成一对临时密钥对。响应方用发起方的临时公钥、发起方的ID和自己的参数调用keyExchange_init,计算出哈希选项和共享密钥。然后响应方将自己的临时公钥和哈希选项告知发起方。
  3. 发起方用响应方的临时公钥和自己的参数调用keyExchange_init,计算出哈希选项和共享密钥。然后和响应方的哈希选项进行对比,以决定成败与否。当然,双方的哈希选项的验证可以按照SM9标准文档的描述进行。

你可能感兴趣的:(程序设计)