1.背景
關於數字證書、數字信封、OID等基本知識,此文不做贅述。
在傳統的數字信封體系中,我們的流程大概這樣的。
這裡有個值得注意的點是:
節點1用於加密對稱金鑰的公鑰和節點3用於簽名的私鑰,它們是否可以是同一對。
基於這個思想,我們有了單證書體系和雙證書體系。
其原因之一就是,從功能角度隔離開我們的加密秘鑰對和簽名金鑰對。
根據“中國金融認證中心標準-SM2 雙證書申請及下載規範”,我們可以看到一個雙證書的基本流程。
-
產生簽名金鑰對和互動金鑰對。
-
生成 Base64 編碼的 SM2 雙證書請求。
-
向伺服器端提交 SM2 雙證書請求。
-
解析伺服器端返回的報文資料,並解密加密證書私鑰。
-
匯入簽名公鑰證書、加密公鑰證書、加密證書私鑰。
2.雙證書
以國密SM2為例
上面我們知道,所謂的雙證書,即。
- 專門用於簽名驗籤
- 專門用於加密解密
注意,在中國金融認證中心標準-SM2 雙證書申請及下載規範中,此處的加密秘鑰不是由我們的本地生成的,而是由CA生成。
整個邏輯大致互動邏輯如下:
- 本地生成簽名公私鑰對
- 本地生成臨時公私鑰對(注意,非加密秘鑰對,僅僅在生成請求證書階段使用)
- 結合簽名公鑰、臨時公鑰,生成雙證書請求檔案。
- CA簽發簽名證書
- CA生成加密公私鑰對(並使用剛才我們的臨時公鑰加密加密秘鑰對中的私鑰)
- 下載CA簽發的簽名證書、加密證書、加密私鑰
- 使用臨時私鑰解密加密過的私鑰,得到最終的加密私鑰。
3.雙證請求檔案
請求檔案、CSR、P10,不嚴格的場景下,你可以當做是同一個東西。
3.1 格式描述
3.1.1 整體格式
SM2 雙證書請求的 ASN.1 資料格式。
CertificationRequest ::= SEQUENCE {
certificationRequestInfo CertificationRequestInfo,
signatureAlgorithm AlgorithmIdentifier{{ SignatureAlgorithms }},
signature BIT STRING
}
- ccertificationRequestInfo: SM2 雙證書請求資訊。
- signatureAlgorithm:簽名演算法 ID,文件中OID取值為:1.2.156.10197.1.501。
- signature:使用簽名私鑰,對 certificationRequestInfo 節點的簽名結果。
這裡提到了oid,可以在這裡查詢。
國家OID註冊中心
GmSSL
然後,這裡的{{ SignatureAlgorithms }}
是什麼意思呢?
-
AlgorithmIdentifier:這是一個ASN.1的標準型別,用於標識演算法。它通常包含兩個欄位:演算法OID(物件識別符號)和可選的引數。
-
{{ SignatureAlgorithms }}:表示
AlgorithmIdentifier
的值必須來自SignatureAlgorithms
的集合。
嗯,就是下面這個。
3.1.2 CertificationRequestInfo
CertificationRequestInfo ::= SEQUENCE {
version INTEGER,
subject Name,
subjectPKInfo SubjectPublicKeyInfo,
attributes [0] Attributes
}
-
version:版本號,本文件中取值為 0x00。
-
subject:公鑰證書 DN。詳細介紹,請參考 PKCS#10。
-
subjectPKInfo:簽名公鑰資訊。
-
attributes:屬性資訊。
3.1.3 SubjectPublicKeyInfo
SubjectPublicKeyInfo ::= SEQUENCE {
algorithm AlgorithmIdentifer,
subjectPublicKey BIT STRING
}
AlgorithmIdentifer::= SEQUENCE {
algorithm OBJECT IDENTIFIER,
parameters ANY DEFINED BY algorithm OPTIONAL
}
-
algorithm:ECC 公鑰演算法 OID,在本文件中,取值為:1.2.840.10045.2.1。
-
parameters:SM2 公鑰演算法 OID,在本文件中,取值為:1.2.156.10197.1.301。
-
subjectPublicKey:SM2 公鑰資料,結構如下。
0x04||簽名公鑰 X 分量||簽名公鑰 Y 分量。
3.1.4 attributes
Attributes ::= Context[0] {
chanllegPassword ChanllegPassword,
tempPublicKeyInfo TempPulicKeyInfo
}
ChanllegPassword ::= SEQUENCE {
chanllegPasswordOID OBJECTIDENTIFIER,
password PrintableString
}
TempPulicKeyInfo ::= SEQUENCE {
tempPublicKeyOID OBJECTIDENTIFIER,
tempPublicKey OCTECT STRING
}
- password:預設取值:111111。
- tempPublicKeyOID:互動公鑰標識 OID,本文件中取值為:1.2.840.113549.1.9.63。
- tempPublicKey:互動公鑰 TempPulicKey 的 OCTECT STRING 編碼。
3.1.5 互動公鑰
TempPulicKey ::= SEQUENCE {
version INTEGER,
tempPublicKeyData OCTET STRING
}
-
version:版本號,本文件中取值為:0x01。
-
tempPublicKeyData:互動公鑰資料,結構如下。
0x00 0xB4 0x00 0x00||0x00 0x01 0x00 0x00 ||互動公鑰 X 分量||32 位元組 0x00 擴充套件空間||互動公鑰 Y 分 量||32 位元組 0x00 擴充套件空間
3.2 示例檔案
首先,給出文件中的demo雙證請求檔案。
MIIB0TCCAXUCAQAwWzENMAsGA1UEBh4EAEMATjEhMB8GA1UECh4YAEMARgBDAEEAIABUAEUAUwBU
ACAAQwBBMScwJQYDVQQDHh4AYwBlAHIAdABSAGUAcQB1AGkAcwBpAHQAaQBvAG4wWTATBgcqhkjO
PQIBBggqgRzPVQGCLQNCAAQv93JF1oROzBImU6Plgleu+HI659cECfKn+gajy7JWGAEoSyw+9rsB
WoRA+kqA7FmgO8NcNcm3fRBWS+yLBMLUoIG3MBMGCSqGSIb3DQEJBxMGMTExMTExMIGfBgkqhkiG
9w0BCT8EgZEwgY4CAQEEgYgAtAAAAAEAAGmQSyS20/zQ4tHJQKA5EYPgdLuPE568SYcKlqmwWGjW
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACokwM02BfEmqVM+qPPlx2I4v38pc1N4WgC
xVb2QmgSygAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAwGCCqBHM9VAYN1BQADSAAw
RQIgfm4txwd5pHMPPtsHEfN+4Y8iMKmKCxy1T3eIMwkYS0kCIQCu6nbbBxVF99qaX1h1/qksk9u9
fs6qkzlkrFbkPkvMjw==
我們使用ASN.1線上解析工具,可以看到大體結構,上文中對細節處已經做了框選,此處不贅述。
看起來複雜,實際也很複雜,哈哈。
但是別慌,跟咱們前面的格式描述那裡對應上就好了。
就像咱們Java的實體類一樣,沒啥特別的,只不過走了ASN1編碼而已。
3.3 程式碼
本文基於Java構建
4.結果檔案解析
4.1 分析
根據規範,將會收到3個結果檔案。
- SignCert.cer:簽名對應的公鑰證書
- EncCert.cer:加密對應的公鑰證書
- PrivateKey.key:加密過的公私鑰檔案(使用我們的臨時公鑰加密)
由規範指引,我們可以得到解密的具體邏輯。
- 根據
,
分隔,提取密文資料。 - 解析密文資料,得到實際密文。
- 使用臨時公鑰解密密文,得到秘鑰值。
注意,直接移除,
後得到的資料並不是直接的SM2解密源文,它是具有如下結構的(回顧3.1.5節)。
TempPulicKey ::= SEQUENCE {
version INTEGER,
tempPublicKeyData OCTET STRING
}
4.2 程式碼
注意點如下:
- 返回的資料裡面會解析出version和encryptedKeyData兩個部分的資料,encryptedKeyData才是我們實際解密的源資料。
- 使用BC庫解密的時候,私鑰前加00/密文前加04/公鑰前加04。
- 經過驗證,模式使用C1C3C2。
關於為啥咱們BC庫裡密文前要加04,你可以參考這個issue:hutool-SM2私鑰解密檔案報錯Invalid point encoding 0x30
當然,咱們此處沒有用hutool,不過原因你可以研究下。
具體程式碼參考:easy-cryptography