C# 的RSA加密解密簽名,就為了支援 PEM PKCS#8 格式金鑰對的匯入匯出

xiangyuecn發表於2018-11-07

差點造了一整個輪子

.Net Framework 4.5 裡面的RSA功能,並未提供簡單對PEM金鑰格式的支援(.Net Core有咩?),差點(還遠著)造了一整個輪子,就為了支援PEM PKCS#8、PKCS#1格式金鑰對的匯入匯出。

Github: github.com/xiangyuecn/…

本文內容來自README,主要介紹了PEM PKCS#8、PKCS#1公鑰和私鑰格式,並以此為基礎寫的C#函式方法。

前言、自述、還有啥

在寫一個小轉換工具時加入了RSA加密解密支援(見圖RSA工具),祕鑰輸入框支援填寫XML和PEM格式,操作型別裡面支援XML->PEM、PEM->XML的轉換。

實現相應功能發現原有RSA操作類不能良好工作,PEM->XML沒問題,只要能通過PEM建立RSA,就能用RSACryptoServiceProvider自帶方法匯出XML。但XML->PEM沒有找到相應的簡單實現方法,大部分部落格寫的用BouncyCastle庫來操作,程式碼是少,但BouncyCastle就有好幾兆大小,我的小工具啊才100K;所以自己實現了一個支援匯出PKCS#1PKCS#8格式PEM金鑰的方法RSA_PEM.ToPEM

操作過程中發現原有RSA操作類不支援用PKCS#8格式PEM金鑰來建立RSA物件(用的RSACryptoServiceProviderExtension的擴充套件方法來支援PEM金鑰),僅支援PKCS#1,所以又自己實現了一個從PEM金鑰來建立RSACryptoServiceProvider的方法RSA_PEM.FromPEM

在實現匯入匯出PEM金鑰過程中,對PKCS#1PKCS#8格式的PEM金鑰有了一定的瞭解,主要參考了:

RSA公鑰檔案(PEM)解析》:公鑰位元組碼分解。

RSA私鑰檔案(PEM)解析》:私鑰位元組碼分解。

iOS安全相關 - RSA中公鑰的DER格式組成》:1位元組和2位元組長度表述方法,和為什麼有些欄位前面要加0x00。

跑起來

clone下來用vs應該能夠直接開啟,經目測看起來沒什麼卵用的檔案都svn:ignore掉了(svn滑稽。

主要支援

  • 通過XML格式金鑰對建立RSA
  • 通過PEM格式金鑰對建立RSA
  • RSA加密、解密
  • RSA簽名、驗證
  • 匯出XML格式公鑰、私鑰
  • 匯出PEM格式公鑰、私鑰
  • PEM格式祕鑰對和XML格式祕鑰對互轉

PEM金鑰編碼格式

長度表述方法

PEM格式中,每段資料基本上都是flag+長度資料佔用位數+長度數值+資料這種格式。

長度資料佔用位數有0x81和0x82兩個值,分別代表長度數值佔用了1位元組和2位元組。

但長度資料佔用位數不一定存在,如果長度數值<0x80時(理由應該和下面這個加0x00一致),長度數值直接在flag後面用1位來表述,變成了flag+長度數值(<0x80)+資料

什麼情況下內容前面要加0x00

如果內容的bit流的前4 bit十六進位制值>=8就要在內容前面加0x00,其他不用加。

一個大整數,最高位為符號位,其為1時,就是負數,所以要在最高位填充0x00以保證不為負。

PEM公鑰編碼格式

PKCS#1PKCS#8公鑰編碼都是統一的格式。

/*****1024位元組公鑰*****/
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYw9+M3+REzDtYqwBrs/as/Oy8
GRE5OmnqOV0EfkEiCIjiczbVEFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG
2KdpI8KMUqKAszNydsHSeh9MSKcd5zgS4NuE0u+eR7CB8kOWipiLDQmY38kpM36p
RWdNQlpIaTDo5IhJJwIDAQAB
-----END PUBLIC KEY-----

/*****二進位制表述*****/
30819F300D06092A864886F70D010101050003818D003081890281810098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E48849270203010001


/*****二進位制分解*****/

/*
後續(到結尾)內容長度為0x9F位元組,相當於整個檔案長度-當前這4位元組。
格式:tag[+長度資料佔用位數(可選)]+後續長度數值,
	此處tag=0x30,
	長度資料佔用1位(參考前面長度表述方法),
	後續長度數值=0x9F個位元組
*/
30 81 9F

/*
固定內容 encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"(其實不懂是啥玩意)
*/
30 0D 06092A864886F70D0101010500

/*後續內容長度,後面內容長度為0x8D位元組,和開頭格式一致*/
03 81 8D

/*固定內容*/
00

/*後續內容長度,後面內容長度為0x89位元組,和開頭格式一致*/
30 81 89

/*
RSA Modulus內容
格式:tag[+長度資料佔用位數(可選)]+內容長度數值+內容,
	此處tag=0x02,所有RSA相關引數都是用02,
	長度資料佔用位數為1位元組,
	內容長度數值=0x81個位元組,
	內容為0x00-0x27這一段(參考前面什麼情況下要加0x00)。
*/
02 81 81
0098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E4884927

/*RSA Exponent內容,和Modulus一樣,但此處長度資料佔用位數不存在*/
02 03 010001
複製程式碼

PEM PKCS#1私鑰編碼格式

/*****1024位元組PKCS#1私鑰*****/
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCYw9+M3+REzDtYqwBrs/as/Oy8GRE5OmnqOV0EfkEiCIjiczbV
EFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG2KdpI8KMUqKAszNydsHSeh9M
SKcd5zgS4NuE0u+eR7CB8kOWipiLDQmY38kpM36pRWdNQlpIaTDo5IhJJwIDAQAB
AoGAcGNSWRrynia+1onf4lzg8v2U0QGEKV0vRNF0/HRCSN6MjkUDJxdDc0UYHZsk
uSXklTMQi/w70msacQNRqOsNk32O6vVPxr4NfTVaIV59Jv9Z5SXGiRmRZXeRw0ks
KYdOwaDJJu9zETNHZoMFJm8sq/tGJPQCPNesoZRZssL7mjkCQQDOI6jKt60bvu6V
XvtQoyUUbyMj9eCOBatS49jRvv326TMc951e9TcbnD0cxJrV1N6yIi+++ejwfagb
eYf++N61AkEAvbc8KTlBbI9TMwnVkQpst+ckgm3gpRDhAfQ/Lt7r8g2KAHsJv+wb
AJCgu8PgqM9mQjVxZ+78+aLEQ+h5rvMV6wJAY1c9/ct8ihV+Zs+qL1cgBHP2rFrO
x8KlqMGS+KmhPD9v2XLfDScBUrX9oYKB17DJTXE6Lz/CaTs1K2BrEI4gzQJBAJIQ
s9chaAfHSc1v8uha2F23Ltrk8iLknfi9LrBNneedGPVJxbXoeNm0gKxQIXaXSCoN
r6TP0iH5eZa3NIjIS8UCQAbw+d2WJIon+vuUsKk2dtZTqZx8e53NreZUFMaIkoS5
JPJqI6/6hq8/2ARFO3P9/qkxDMkJv8mSjV91cZixB10=
-----END RSA PRIVATE KEY-----

/*****二進位制表述*****/
3082025C0201000281810098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E48849270203010001028180706352591AF29E26BED689DFE25CE0F2FD94D10184295D2F44D174FC744248DE8C8E45032717437345181D9B24B925E49533108BFC3BD26B1A710351A8EB0D937D8EEAF54FC6BE0D7D355A215E7D26FF59E525C6891991657791C3492C29874EC1A0C926EF73113347668305266F2CABFB4624F4023CD7ACA19459B2C2FB9A39024100CE23A8CAB7AD1BBEEE955EFB50A325146F2323F5E08E05AB52E3D8D1BEFDF6E9331CF79D5EF5371B9C3D1CC49AD5D4DEB2222FBEF9E8F07DA81B7987FEF8DEB5024100BDB73C2939416C8F533309D5910A6CB7E724826DE0A510E101F43F2EDEEBF20D8A007B09BFEC1B0090A0BBC3E0A8CF6642357167EEFCF9A2C443E879AEF315EB024063573DFDCB7C8A157E66CFAA2F57200473F6AC5ACEC7C2A5A8C192F8A9A13C3F6FD972DF0D270152B5FDA18281D7B0C94D713A2F3FC2693B352B606B108E20CD0241009210B3D7216807C749CD6FF2E85AD85DB72EDAE4F222E49DF8BD2EB04D9DE79D18F549C5B5E878D9B480AC50217697482A0DAFA4CFD221F97996B73488C84BC5024006F0F9DD96248A27FAFB94B0A93676D653A99C7C7B9DCDADE65414C6889284B924F26A23AFFA86AF3FD804453B73FDFEA9310CC909BFC9928D5F757198B1075D

/*****二進位制分解(大部分和公鑰格式相同)*****/

/*後續內容長度,後面內容長度為0x025C個位元組,和公鑰開頭格式一致,參考公鑰部分*/
30 82 025C

/*固定版本號*/
02 01 00

/*#####從這裡開始後面就是內容了 注:KCS#8僅僅是在此處插入部分內容#####*/

/*RSA Modulus內容,和公鑰開頭格式一致,參考公鑰部分*/
02 81 81 
0098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E4884927

/*RSA Exponent*/
02 03 010001

/*RSA D*/
02 81 80
706352591AF29E26BED689DFE25CE0F2FD94D10184295D2F44D174FC744248DE8C8E45032717437345181D9B24B925E49533108BFC3BD26B1A710351A8EB0D937D8EEAF54FC6BE0D7D355A215E7D26FF59E525C6891991657791C3492C29874EC1A0C926EF73113347668305266F2CABFB4624F4023CD7ACA19459B2C2FB9A39

/*RSA P*/
02 41
00CE23A8CAB7AD1BBEEE955EFB50A325146F2323F5E08E05AB52E3D8D1BEFDF6E9331CF79D5EF5371B9C3D1CC49AD5D4DEB2222FBEF9E8F07DA81B7987FEF8DEB5

/*RSA Q*/
02 41
00BDB73C2939416C8F533309D5910A6CB7E724826DE0A510E101F43F2EDEEBF20D8A007B09BFEC1B0090A0BBC3E0A8CF6642357167EEFCF9A2C443E879AEF315EB

/*RSA DP*/
02 40 63573DFDCB7C8A157E66CFAA2F57200473F6AC5ACEC7C2A5A8C192F8A9A13C3F6FD972DF0D270152B5FDA18281D7B0C94D713A2F3FC2693B352B606B108E20CD

/*RSA DQ*/
02 41
009210B3D7216807C749CD6FF2E85AD85DB72EDAE4F222E49DF8BD2EB04D9DE79D18F549C5B5E878D9B480AC50217697482A0DAFA4CFD221F97996B73488C84BC5

/*RSA InverseQ*/
02 40
06F0F9DD96248A27FAFB94B0A93676D653A99C7C7B9DCDADE65414C6889284B924F26A23AFFA86AF3FD804453B73FDFEA9310CC909BFC9928D5F757198B1075D
複製程式碼

PEM PKCS#8私鑰編碼格式

/*****1024位元組PKCS#8私鑰*****/
-----BEGIN PRIVATE KEY-----
MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJjD34zf5ETMO1ir
AGuz9qz87LwZETk6aeo5XQR+QSIIiOJzNtUQWdnepGMtsMBN+YHE1DpzoWBrydfb
yCUQf9ov9EbYp2kjwoxSooCzM3J2wdJ6H0xIpx3nOBLg24TS755HsIHyQ5aKmIsN
CZjfySkzfqlFZ01CWkhpMOjkiEknAgMBAAECgYBwY1JZGvKeJr7Wid/iXODy/ZTR
AYQpXS9E0XT8dEJI3oyORQMnF0NzRRgdmyS5JeSVMxCL/DvSaxpxA1Go6w2TfY7q
9U/Gvg19NVohXn0m/1nlJcaJGZFld5HDSSwph07BoMkm73MRM0dmgwUmbyyr+0Yk
9AI816yhlFmywvuaOQJBAM4jqMq3rRu+7pVe+1CjJRRvIyP14I4Fq1Lj2NG+/fbp
Mxz3nV71NxucPRzEmtXU3rIiL7756PB9qBt5h/743rUCQQC9tzwpOUFsj1MzCdWR
Cmy35ySCbeClEOEB9D8u3uvyDYoAewm/7BsAkKC7w+Coz2ZCNXFn7vz5osRD6Hmu
8xXrAkBjVz39y3yKFX5mz6ovVyAEc/asWs7HwqWowZL4qaE8P2/Zct8NJwFStf2h
goHXsMlNcTovP8JpOzUrYGsQjiDNAkEAkhCz1yFoB8dJzW/y6FrYXbcu2uTyIuSd
+L0usE2d550Y9UnFteh42bSArFAhdpdIKg2vpM/SIfl5lrc0iMhLxQJABvD53ZYk
iif6+5SwqTZ21lOpnHx7nc2t5lQUxoiShLkk8mojr/qGrz/YBEU7c/3+qTEMyQm/
yZKNX3VxmLEHXQ==
-----END PRIVATE KEY-----

/*****二進位制表述*****/
30820276020100300D06092A864886F70D0101010500048202603082025C0201000281810098C3DF8CDFE444CC3B58AB006BB3F6ACFCECBC1911393A69EA395D047E41220888E27336D51059D9DEA4632DB0C04DF981C4D43A73A1606BC9D7DBC825107FDA2FF446D8A76923C28C52A280B3337276C1D27A1F4C48A71DE73812E0DB84D2EF9E47B081F243968A988B0D0998DFC929337EA945674D425A486930E8E48849270203010001028180706352591AF29E26BED689DFE25CE0F2FD94D10184295D2F44D174FC744248DE8C8E45032717437345181D9B24B925E49533108BFC3BD26B1A710351A8EB0D937D8EEAF54FC6BE0D7D355A215E7D26FF59E525C6891991657791C3492C29874EC1A0C926EF73113347668305266F2CABFB4624F4023CD7ACA19459B2C2FB9A39024100CE23A8CAB7AD1BBEEE955EFB50A325146F2323F5E08E05AB52E3D8D1BEFDF6E9331CF79D5EF5371B9C3D1CC49AD5D4DEB2222FBEF9E8F07DA81B7987FEF8DEB5024100BDB73C2939416C8F533309D5910A6CB7E724826DE0A510E101F43F2EDEEBF20D8A007B09BFEC1B0090A0BBC3E0A8CF6642357167EEFCF9A2C443E879AEF315EB024063573DFDCB7C8A157E66CFAA2F57200473F6AC5ACEC7C2A5A8C192F8A9A13C3F6FD972DF0D270152B5FDA18281D7B0C94D713A2F3FC2693B352B606B108E20CD0241009210B3D7216807C749CD6FF2E85AD85DB72EDAE4F222E49DF8BD2EB04D9DE79D18F549C5B5E878D9B480AC50217697482A0DAFA4CFD221F97996B73488C84BC5024006F0F9DD96248A27FAFB94B0A93676D653A99C7C7B9DCDADE65414C6889284B924F26A23AFFA86AF3FD804453B73FDFEA9310CC909BFC9928D5F757198B1075D


/*****二進位制分解(和PKCS#1只是多了一段資料,詳細結構參考PKCS#1的)*****/

/*後續內容長度*/
30 82 0276

/*固定版本號*/
02 01 00

/*#####相對於KCS#1僅僅是在此處開始插入部分資料 Begin#####*/

/*
固定內容 encoded OID sequence for PKCS #1 rsaEncryption szOID_RSA_RSA = "1.2.840.113549.1.1.1"(其實不懂是啥玩意)
*/
30 0D 06092A864886F70D0101010500

/*後續內容長度,後面內容長度為0x0260位元組,和開頭格式一致*/
04 82 0260

/*後續內容長度,後面內容長度為0x025C位元組,和開頭格式一致*/
30 82 025C

/*固定版本號*/
02 01 00

/*#####相對於KCS#1僅僅是在此處結束插入部分資料 End#####*/

/*RSA Modulus內容*/
02 81 81 
0098C3DF8CDFE444CC3B58A...

...後續內容省略...
複製程式碼

C# RSA操作類

主要檔案

RSA.cs

此檔案依賴RSA_PEM.cs,用於進行加密、解密、簽名、驗證、祕鑰匯入匯出操作。

[建構函式] new RSA(1024)

通過指定金鑰長度來建立RSA,會生成新金鑰。

[建構函式] new RSA("<xml>")

通過XML格式金鑰對建立RSA,xml可以是公鑰或私鑰,XML格式如:

<RSAKeyValue><Modulus>mMPfjN/kRMw7WKsAa7P2rPzsvBkROTpp6jldBH5BIgiI4nM21RBZ2d6kYy2wwE35gcTUOnOhYGvJ19vIJRB/2i/0RtinaSPCjFKigLMzcnbB0nofTEinHec4EuDbhNLvnkewgfJDloqYiw0JmN/JKTN+qUVnTUJaSGkw6OSISSc=</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>
複製程式碼

[建構函式] new RSA("PEM", any)

通過PEM格式金鑰對建立RSA,PEM可以是公鑰或私鑰,支援PKCS#1PKCS#8格式,PEM格式如:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCYw9+M3+REzDtYqwBrs/as/Oy8
GRE5OmnqOV0EfkEiCIjiczbVEFnZ3qRjLbDATfmBxNQ6c6Fga8nX28glEH/aL/RG
2KdpI8KMUqKAszNydsHSeh9MSKcd5zgS4NuE0u+eR7CB8kOWipiLDQmY38kpM36p
RWdNQlpIaTDo5IhJJwIDAQAB
-----END PUBLIC KEY-----
複製程式碼

[方法] .ToXML([false]是否僅僅匯出公鑰)

匯出XML格式祕鑰對。如果RSA包含私鑰,預設會匯出私鑰,設定僅僅匯出公鑰時只會匯出公鑰;不包含私鑰只會匯出公鑰。

[方法] .ToPEM_PKCS1|.ToPEM_PKCS8([false]是否僅僅匯出公鑰)

匯出PEM格式祕鑰對,兩個方法分別匯出PKCS#1PKCS#8格式。如果RSA包含私鑰,預設會匯出私鑰,設定僅僅匯出公鑰時只會匯出公鑰;不包含私鑰只會匯出公鑰。

[方法] .Encode("字串"|bytes)

加密操作,支援任意長度資料。

[方法] .DecodeOrNull("Base64字串"|bytes)

解密操作,解密失敗返回null,支援任意長度資料。

[方法] .Sign("hash", "字串"|bytes)

通過hash演算法(MD5、SHA1等)來對資料進行簽名。

[方法] .Verify("hash", "sign", "字串")

通過hash演算法(MD5、SHA1等)來驗證字串是否和sign簽名一致。

RSA_PEM.cs

此檔案不依賴任何檔案,可以單獨copy來用(RSA_Unit裡面的方法可以忽略)

[靜態方法] .FromPEM("PEM")

通過PEM格式祕鑰對來建立RSACryptoServiceProvider,PEM可以是公鑰或私鑰。

[靜態方法] .ToPEM(RSACryptoServiceProvider, exportPublicOnly, usePKCS8)

將RSA中的金鑰對匯出成PEM格式usePKCS8=false時返回PKCS#1格式,否則返回PKCS#8格式。如果RSA包含私鑰,預設會匯出私鑰,設定僅僅匯出公鑰時只會匯出公鑰;不包含私鑰只會匯出公鑰。

次要檔案

RSA_Unit.cs

封裝的一些通用方法,如:base64。沒有此檔案也可以,引用的地方用別的程式碼實現。

Program.cs

控制檯入口檔案,用來測試的,裡面包含了主要的使用用例。

圖例

RSA工具:

RSA工具

控制檯執行:

控制檯執行

相關文章