國密演算法SM2證書製作
前段時間將系統的RSA演算法全部升級為SM2國密演算法,密碼機和UKey硬體裝置大都同時支援RSA和SM2演算法,只是應用系統的加解密簽名驗證需要修改,這個更改底層呼叫的加密動態庫來,原來RSA用的對稱加密演算法DES(AES)和摘要MD5(SHA1)也相應改變,分別對應SM1、SM3演算法,SM1演算法基於硬體實現,SM2、SM3演算法已公開。
SM2簽名驗證演算法
SM2簽名同樣也是需要先摘要原文資料,即先使用SM3密碼雜湊演算法計算出32byte摘要。SM3需要摘要簽名方ID(預設1234567812345678)、曲線引數a,b,Gx,Gy、共鑰座標(x,y)計算出Z值,然後再雜湊原文得出摘要資料。這個地方要注意曲線引數和座標點都是32byte,在轉換為BigInteger大數計算轉成位元組流時要去掉空補位,否則可能會出現摘要計算不正確的問題。SM2簽名實現如下:
public static BigInteger[] Sm2Sign(byte[] md, AsymmetricCipherKeyPair keypair) { SM3Digest sm3 = new SM3Digest(); ECPublicKeyParameters ecpub = (ECPublicKeyParameters)keypair.Public; byte[] z = SM2CryptoServiceProvider.Sm2GetZ(Encoding.Default.GetBytes(SM2CryptoServiceProvider.userId), ecpub.Q); sm3.BlockUpdate(z, 0, z.Length); byte[] p = md; sm3.BlockUpdate(p, 0, p.Length); byte[] hashData = new byte[32]; sm3.DoFinal(hashData, 0); // e BigInteger e = new BigInteger(1, hashData); // k BigInteger k = null; ECPoint kp = null; BigInteger r = null; BigInteger s = null; BigInteger userD = null; do { do { ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters)keypair.Private; k = ecpriv.D; kp = ecpub.Q; userD = ecpriv.D; // r r = e.Add(kp.X.ToBigInteger()); r = r.Mod(ecc_n); } while (r.Equals(BigInteger.Zero) || r.Add(k).Equals(ecc_n)); // (1 + dA)~-1 BigInteger da_1 = userD.Add(BigInteger.One); da_1 = da_1.ModInverse(ecc_n); // s s = r.Multiply(userD); s = k.Subtract(s).Mod(ecc_n); s = da_1.Multiply(s).Mod(ecc_n); } while (s.Equals(BigInteger.Zero)); byte[] btRS = new byte[64]; byte[] btR = r.ToByteArray(); byte[] btS = s.ToByteArray(); Array.Copy(btR, btR.Length - 32, btRS, 0, 32); Array.Copy(btS, btS.Length - 32, btRS, 32, 32); return new BigInteger[] { r, s }; }
SM2演算法是基於ECC演算法的,簽名同樣返回2個大數,共64byte。由於原來RSA演算法已很普遍支援,要實現RSA的簽名驗籤都有標準庫的實現,而SM2是國密演算法在國際上還沒有標準通用,演算法Oid標識在X509標準中是沒定義的。在.Net或Java中可以基於使用BouncyCastle加密庫實現,開源的也比較好學習擴充套件。SM2演算法驗籤可以使用軟驗籤,即可以不需要使用硬體裝置,同樣使用原始資料、簽名、證書(公鑰)來實現對簽名方驗證,保證資料完整性未被篡改。驗證過程同樣需先摘要原文資料,公鑰在證書中是以一個66byte的BitString,去掉前面標記位即64byte為共鑰座標(x,y),中間分割擷取再以Hex方式轉成BigInteger大數計算,驗籤程式碼如下:
public static bool Verify(byte[] msg, byte[] signData, byte[] certData)
{
X509Certificate2 x5092 = new X509Certificate2(certData);
byte[] certPK = x5092.GetPublicKey();
certPK = SubByte(certPK, 1, 64);
byte[] certPKX = SubByte(certPK, certPK.Length - 32 - 32, 32);
byte[] certPKY = SubByte(certPK, certPK.Length - 32, 32);
System.String strcertPKX = ByteToHexStr(certPKX);
System.String strcertPKY = ByteToHexStr(certPKY);
BigInteger biX = new BigInteger(strcertPKX, 16);
BigInteger biY = new BigInteger(strcertPKY, 16);
ECFieldElement x = new FpFieldElement(ecc_p, biX);
ECFieldElement y = new FpFieldElement(ecc_p, biY);
ECPoint userKey = new FpPoint(ecc_curve, x, y);
SM3Digest sm3 = new SM3Digest();
byte[] z = Sm2GetZ(Encoding.Default.GetBytes(userId), userKey);
sm3.BlockUpdate(z, 0, z.Length);
byte[] p = msg;
sm3.BlockUpdate(p, 0, p.Length);
byte[] md = new byte[32];
sm3.DoFinal(md, 0);
byte[] btR = SubByte(signData, 0, 32);
byte[] btS = SubByte(signData, 32, 32);
System.String strR = ByteToHexStr(btR);
System.String strS = ByteToHexStr(btS);
BigInteger r = new BigInteger(strR, 16);
BigInteger s = new BigInteger(strS, 16);
// e_
BigInteger e = new BigInteger(1, md);
// t
BigInteger t = r.Add(s).Mod(ecc_n);
if (t.Equals(BigInteger.Zero))
return false;
// x1y1
ECPoint x1y1 = ecc_point_g.Multiply(s);
x1y1 = x1y1.Add(userKey.Multiply(t));
// R
BigInteger R = e.Add(x1y1.X.ToBigInteger()).Mod(ecc_n);
return r.Equals(R);
}
製作SM2證書
基於BouncyCastle開源庫,可以輕鬆製作X509證書、CRL、pkcs10、pkcs12,支援國際通用的RSA、ECC演算法。製作SM2證書可以通過擴充套件BouncyCastle庫來實現,需加入SM2簽名演算法DerObjectIdentifier標識1.2.156.10197.1.501(基於SM3的SM2演算法簽名),金鑰對的生成使用國密推薦曲線引數,然後如上所示自行實現SM2簽名驗證演算法。X509證書由證書主體、證書籤名演算法標識、簽名組成,和RSA證書主要不同的是SM2證書的簽名演算法標識和簽名,及證書公鑰使用ECKeyParameters。生成自簽名SM2證書程式碼如下:
SM2證書生成public static Org.BouncyCastle.X509.X509Certificate MakeRootCert(string filePath, IDictionary subjectNames)
{
AsymmetricCipherKeyPair keypair = SM2CryptoServiceProvider.SM2KeyPairGenerator.GenerateKeyPair();
ECPublicKeyParameters pubKey = (ECPublicKeyParameters)keypair.Public; //CA公鑰
ECPrivateKeyParameters priKey = (ECPrivateKeyParameters)keypair.Private; //CA私鑰
X509Name issuerDN = new X509Name(GetDictionaryKeys(subjectNames), subjectNames);
X509Name subjectDN = issuerDN; //自簽證書,兩者一樣
SM2X509V3CertificateGenerator sm2CertGen = new SM2X509V3CertificateGenerator();
//X509V3CertificateGenerator sm2CertGen = new X509V3CertificateGenerator();
sm2CertGen.SetSerialNumber(new BigInteger(128, new Random())); //128位
sm2CertGen.SetIssuerDN(issuerDN);
sm2CertGen.SetNotBefore(DateTime.UtcNow.AddDays(-1));
sm2CertGen.SetNotAfter(DateTime.UtcNow.AddDays(365 * 10));
sm2CertGen.SetSubjectDN(subjectDN);
sm2CertGen.SetPublicKey(pubKey); //公鑰
sm2CertGen.SetSignatureAlgorithm("SM3WITHSM2");
sm2CertGen.AddExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(true));
sm2CertGen.AddExtension(X509Extensions.SubjectKeyIdentifier, false, new SubjectKeyIdentifierStructure(pubKey));
sm2CertGen.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, new AuthorityKeyIdentifierStructure(pubKey));
sm2CertGen.AddExtension(X509Extensions.KeyUsage, true, new KeyUsage(6));
Org.BouncyCastle.X509.X509Certificate sm2Cert = sm2CertGen.Generate(keypair);
sm2Cert.CheckValidity();
sm2Cert.Verify(pubKey);
return sm2Cert;
}
X509證書使用ASN1語法進行編碼,是用型別標識、長度和值序列來描述資料結構的。SM2證書在製作設定公鑰時,預設會帶ECKeyParameters引數,並沒有SM2的公鑰引數1.2.156.10197.1.301,因此需要自己寫個SM2橢圓曲線密碼演算法標識物件,這樣在生成的證書中就可以看到公鑰引數欄位,如下所示:
SM2證書公鑰標識using System;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Asn1;
namespace Common.Security
{
public class SM2AlgorithmIdentifier
: AlgorithmIdentifier
{
private readonly bool parametersDefined;
public SM2AlgorithmIdentifier(
DerObjectIdentifier objectID):base(objectID)
{
}
public SM2AlgorithmIdentifier(
DerObjectIdentifier objectID,
Asn1Encodable parameters)
: base(objectID, parameters)
{
this.parametersDefined = true;
}
/**
* Produce an object suitable for an Asn1OutputStream.
* * AlgorithmIdentifier ::= Sequence {
* algorithm OBJECT IDENTIFIER,
* parameters ANY DEFINED BY algorithm OPTIONAL }
*
*/
public override Asn1Object ToAsn1Object()
{
DerObjectIdentifier sm2Identifier = new DerObjectIdentifier("1.2.156.10197.1.301");
Asn1EncodableVector v = new Asn1EncodableVector(base.ObjectID, sm2Identifier);
return new DerSequence(v);
}
}
}
SM2演算法是國密局公佈的公鑰密碼演算法,在相當強度下金鑰比RSA短,在使用智慧卡有限空間儲存時非常可貴。目前國內很多CA大都升級支援SM2演算法證書,相信以後會慢慢地推廣更多應用,也期望之後能與國際標準接軌。
附:
國密推薦256位曲線引數
- p=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFF
- a=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF 00000000 FFFFFFFF FFFFFFFC
- b=28E9FA9E 9D9F5E34 4D5A9E4B CF6509A7 F39789F5 15AB8F92 DDBCBD41 4D940E93
- n=FFFFFFFE FFFFFFFF FFFFFFFF FFFFFFFF 7203DF6B 21C6052B 53BBF409 39D54123
- Gx=32C4AE2C 1F198119 5F990446 6A39C994 8FE30BBF F2660BE1 715A4589 334C74C7
- Gy=BC3736A2 F4F6779C 59BDCEE3 6B692153 D0A9877C C62A4740 02DF32E5 2139F0A0