C#.NET Framework RSA 私鑰簽名 公鑰驗籤(驗證簽名) ver:20230612
環境說明:
.NET Framework 4.6 的控制檯程式 。
.NET Framework 對於RSA的支援:NET Framework 內建只支援XML格式的私鑰/公鑰。如果要用PKCS1,PKCS8格式的,要用到三方庫BouncyCastle。
核心重點是拿到 .NET 的RSACryptoServiceProvider 物件。
簽名(雙方要協商好編碼):
1.將原始報文用UTF8轉為byte陣列。(引數為byte[] buffer) 原始報文的編碼雙方要協商好。
2.呼叫SignData(byte[] buffer, object halg)方法,其中halg 是HASH演演算法,雙方要協商好,有:SHA256、SHA1、MD5。SHA256對應JAVA的 SHA256withRSA. (其它還有 SHA1withRSA,MD5withRSA)
3.將輸出結果轉為BASE64字串。輸出結果轉字串的編碼,雙方要協商好,不一定用BASE64。
公鑰驗籤:
1.將原始報文用UTF8轉為byte陣列。(引數為byte[] buffer)
2.將簽名串(BASE64字串)用Convert.FromBase64String()轉為byte陣列。(引數為:byte[] signature)
3.使用 VerifyData(byte[] buffer, object halg, byte[] signature); 方法驗籤。其中halg 是HASH演演算法,有:SHA256、SHA1、MD5。
用「支付寶開放平臺開發助手」生成一組公私鑰:
PKCS8私鑰:
MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAMz0Czg6QUtTISa2pUkloeQB/TEpHdqrfyroWpKLW9B/LWFSOGH9nyTk1pPZaeadyEZQ6gay/C0pUAetLraq9bMA/Luxq68b87uG7WX7dKytEO2/87qGpGMRs97H+GlkzWil2QO2KK4cHnAcVicPsmi5aZ72U0BWJFyPhtd+qdmrAgMBAAECgYEAvW67iAbgHt0BASVD9C3iSjpEaVHVlC165o/IVzaTcEx8Bz3Ve0zN8W3JnvIO3ebsG4HiLLr2Nk++9rltOc0eNeGMv7F1e/OFot1wN0ON6s1g4bYh1z5Uz8FcYiMWcqHHICrx+oSFeK9x+I2Zge7enQXcsVnqEhm77ZE5YczSryECQQD9nB58e5efYchF+cYbmURioX18cUMuhQbB9Aq2N55cd689Lg35KZqT8JQTp/8tQSdCJG8d2nU8VKspUKTEAuaDAkEAzuKIIoc9PVJvy90LhIPA9c1S8BPCI7EMCaTZqJ5o3VaR2dqvUZDGX7kL3kYkQ+n7mq3KIECvkEFzA+FOP96XuQJBAJQTKHW0T/YeSKoayUHp/lS8R6F2HCy4PRbXn71+wfbpZqcJEd2OHhQM3tiPOV258esbjMlYeSUNppZL4LgVnXMCQQC7Lvs9Ql+GPDAqo7ToEM1lmICR906QPIBHuX+1sJ3wpYMROWumwPa7ZRH36j6ls+6R5OwcgmpWeuE1gYTrBNsBAkEAn2pEtAljX1foQff6CLozYg/J6J9RmVFcJ6qz0LX3052qNFBQYw8CMHB7VkVNzsDIDC8LX5uP2pzTrdPLew+pPA==
與之匹配的 PKCS1 私鑰,用助手轉換的:
MIICXwIBAAKBgQDM9As4OkFLUyEmtqVJJaHkAf0xKR3aq38q6FqSi1vQfy1hUjhh/Z8k5NaT2WnmnchGUOoGsvwtKVAHrS62qvWzAPy7sauvG/O7hu1l+3SsrRDtv/O6hqRjEbPex/hpZM1opdkDtiiuHB5wHFYnD7JouWme9lNAViRcj4bXfqnZqwIDAQABAoGBAL1uu4gG4B7dAQElQ/Qt4ko6RGlR1ZQteuaPyFc2k3BMfAc91XtMzfFtyZ7yDt3m7BuB4iy69jZPvva5bTnNHjXhjL+xdXvzhaLdcDdDjerNYOG2Idc+VM/BXGIjFnKhxyAq8fqEhXivcfiNmYHu3p0F3LFZ6hIZu+2ROWHM0q8hAkEA/ZwefHuXn2HIRfnGG5lEYqF9fHFDLoUGwfQKtjeeXHevPS4N+Smak/CUE6f/LUEnQiRvHdp1PFSrKVCkxALmgwJBAM7iiCKHPT1Sb8vdC4SDwPXNUvATwiOxDAmk2aieaN1Wkdnar1GQxl+5C95GJEPp+5qtyiBAr5BBcwPhTj/el7kCQQCUEyh1tE/2HkiqGslB6f5UvEehdhwsuD0W15+9fsH26WanCRHdjh4UDN7YjzldufHrG4zJWHklDaaWS+C4FZ1zAkEAuy77PUJfhjwwKqO06BDNZZiAkfdOkDyAR7l/tbCd8KWDETlrpsD2u2UR9+o+pbPukeTsHIJqVnrhNYGE6wTbAQJBAJ9qRLQJY19X6EH3+gi6M2IPyeifUZlRXCeqs9C199OdqjRQUGMPAjBwe1ZFTc7AyAwvC1+bj9qc063Ty3sPqTw=
與之匹配的公鑰:
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDM9As4OkFLUyEmtqVJJaHkAf0xKR3aq38q6FqSi1vQfy1hUjhh/Z8k5NaT2WnmnchGUOoGsvwtKVAHrS62qvWzAPy7sauvG/O7hu1l+3SsrRDtv/O6hqRjEbPex/hpZM1opdkDtiiuHB5wHFYnD7JouWme9lNAViRcj4bXfqnZqwIDAQAB
nuget 中參照 Portable.BouncyCastle。
工具類:
RsaUtil 的作用: 將私鑰(PKCS1,PKCS8)、公鑰、私鑰證書、公鑰證書轉為.NET RSACryptoServiceProvider 物件。
using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using System; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace CommonUtils { /// <summary> /// 將私鑰(PKCS1,PKCS8)、公鑰、私鑰證書、公鑰證書轉為.NET RSACryptoServiceProvider 物件 /// </summary> public static class RsaUtil { #region 載入私鑰 /// <summary> /// 轉換私鑰字串為RSACryptoServiceProvider /// </summary> /// <param name="privateKeyStr">私鑰字串</param> /// <param name="keyFormat">PKCS8,PKCS1</param> /// <param name="signType">RSA 私鑰長度1024 ,RSA2 私鑰長度2048</param> /// <returns></returns> public static RSACryptoServiceProvider LoadPrivateKey(string privateKeyStr, string keyFormat) { string signType = "RSA"; if (privateKeyStr.Length > 1024) { signType = "RSA2"; } //PKCS8,PKCS1 if (keyFormat == "PKCS1") { return LoadPrivateKeyPKCS1(privateKeyStr, signType); } else { return LoadPrivateKeyPKCS8(privateKeyStr); } } /// <summary> /// PKCS1 格式私鑰轉 RSACryptoServiceProvider 物件 /// </summary> /// <param name="strKey">pcsk1 私鑰的文字內容</param> /// <param name="signType">RSA 私鑰長度1024 ,RSA2 私鑰長度2048 </param> /// <returns></returns> public static RSACryptoServiceProvider LoadPrivateKeyPKCS1(string privateKeyPemPkcs1, string signType) { try { privateKeyPemPkcs1 = privateKeyPemPkcs1.Replace("-----BEGIN RSA PRIVATE KEY-----", "").Replace("-----END RSA PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim(); privateKeyPemPkcs1 = privateKeyPemPkcs1.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim(); byte[] data = null; //讀取帶 data = Convert.FromBase64String(privateKeyPemPkcs1); RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(data, signType); return rsa; } catch (Exception ex) { throw ex; } return null; } private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey, string signType) { byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; // --------- Set up stream to decode the asn.1 encoded RSA private key ------ MemoryStream mem = new MemoryStream(privkey); BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 0; int elems = 0; try { twobytes = binr.ReadUInt16(); if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) binr.ReadByte(); //advance 1 byte else if (twobytes == 0x8230) binr.ReadInt16(); //advance 2 bytes else return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0102) //version number return null; bt = binr.ReadByte(); if (bt != 0x00) return null; //------ all private key components are Integer sequences ---- elems = GetIntegerSize(binr); MODULUS = binr.ReadBytes(elems); elems = GetIntegerSize(binr); E = binr.ReadBytes(elems); elems = GetIntegerSize(binr); D = binr.ReadBytes(elems); elems = GetIntegerSize(binr); P = binr.ReadBytes(elems); elems = GetIntegerSize(binr); Q = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DP = binr.ReadBytes(elems); elems = GetIntegerSize(binr); DQ = binr.ReadBytes(elems); elems = GetIntegerSize(binr); IQ = binr.ReadBytes(elems); // ------- create RSACryptoServiceProvider instance and initialize with public key ----- CspParameters CspParameters = new CspParameters(); CspParameters.Flags = CspProviderFlags.UseMachineKeyStore; int bitLen = 1024; if ("RSA2".Equals(signType)) { bitLen = 2048; } RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(bitLen, CspParameters); //RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); RSAParameters RSAparams = new RSAParameters(); RSAparams.Modulus = MODULUS; RSAparams.Exponent = E; RSAparams.D = D; RSAparams.P = P; RSAparams.Q = Q; RSAparams.DP = DP; RSAparams.DQ = DQ; RSAparams.InverseQ = IQ; RSA.ImportParameters(RSAparams); return RSA; } catch (Exception ex) { throw ex; // return null; } finally { binr.Close(); } } private static int GetIntegerSize(BinaryReader binr) { byte bt = 0; byte lowbyte = 0x00; byte highbyte = 0x00; int count = 0; bt = binr.ReadByte(); if (bt != 0x02) //expect integer return 0; bt = binr.ReadByte(); if (bt == 0x81) count = binr.ReadByte(); // data size in next byte else if (bt == 0x82) { highbyte = binr.ReadByte(); // data size in next 2 bytes lowbyte = binr.ReadByte(); byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; count = BitConverter.ToInt32(modint, 0); } else { count = bt; // we already have the data size } while (binr.ReadByte() == 0x00) { //remove high order zeros in data count -= 1; } binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte return count; } /// <summary> /// PKCS8 文字轉RSACryptoServiceProvider 物件 /// </summary> /// <param name="privateKeyPemPkcs8"></param> /// <returns></returns> public static RSACryptoServiceProvider LoadPrivateKeyPKCS8(string privateKeyPemPkcs8) { try { //PKCS8是「BEGIN PRIVATE KEY」 privateKeyPemPkcs8 = privateKeyPemPkcs8.Replace("-----BEGIN RSA PRIVATE KEY-----", "").Replace("-----END RSA PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim(); privateKeyPemPkcs8 = privateKeyPemPkcs8.Replace("-----BEGIN PRIVATE KEY-----", "").Replace("-----END PRIVATE KEY-----", "").Replace("\r", "").Replace("\n", "").Trim(); //pkcs8 文字先轉為 .NET XML 私鑰字串 string privateKeyXml = RSAPrivateKeyJava2DotNet(privateKeyPemPkcs8); RSACryptoServiceProvider publicRsa = new RSACryptoServiceProvider(); publicRsa.FromXmlString(privateKeyXml); return publicRsa; } catch (Exception ex) { throw ex; } } /// <summary> /// PKCS8 私鑰文字 轉 .NET XML 私鑰文字 /// </summary> /// <param name="privateKeyPemPkcs8"></param> /// <returns></returns> public static string RSAPrivateKeyJava2DotNet(string privateKeyPemPkcs8) { RsaPrivateCrtKeyParameters privateKeyParam = (RsaPrivateCrtKeyParameters)PrivateKeyFactory.CreateKey(Convert.FromBase64String(privateKeyPemPkcs8)); return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent><P>{2}</P><Q>{3}</Q><DP>{4}</DP><DQ>{5}</DQ><InverseQ>{6}</InverseQ><D>{7}</D></RSAKeyValue>", Convert.ToBase64String(privateKeyParam.Modulus.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.PublicExponent.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.P.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.Q.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.DP.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.DQ.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.QInv.ToByteArrayUnsigned()), Convert.ToBase64String(privateKeyParam.Exponent.ToByteArrayUnsigned())); } #endregion /// <summary> /// 載入公鑰證書 /// </summary> /// <param name="publicKeyCert">公鑰證書文字內容</param> /// <returns></returns> public static RSACryptoServiceProvider LoadPublicCert(string publicKeyCert) { publicKeyCert = publicKeyCert.Replace("-----BEGIN CERTIFICATE-----", "").Replace("-----END CERTIFICATE-----", "").Replace("\r", "").Replace("\n", "").Trim(); byte[] bytesCerContent = Convert.FromBase64String(publicKeyCert); X509Certificate2 x509 = new X509Certificate2(bytesCerContent); RSACryptoServiceProvider rsaPub = (RSACryptoServiceProvider)x509.PublicKey.Key; return rsaPub; } /// <summary> /// pem 公鑰文字 轉 .NET RSACryptoServiceProvider。 /// </summary> /// <param name="publicKeyPem"></param> /// <returns></returns> public static RSACryptoServiceProvider LoadPublicKey(string publicKeyPem) { publicKeyPem = publicKeyPem.Replace("-----BEGIN PUBLIC KEY-----", "").Replace("-----END PUBLIC KEY-----", "").Replace("\r", "").Replace("\n", "").Trim(); //pem 公鑰文字 轉 .NET XML 公鑰文字。 string publicKeyXml = RSAPublicKeyJava2DotNet(publicKeyPem); RSACryptoServiceProvider publicRsa = new RSACryptoServiceProvider(); publicRsa.FromXmlString(publicKeyXml); return publicRsa; } /// <summary> /// pem 公鑰文字 轉 .NET XML 公鑰文字。 /// </summary> /// <param name="publicKey"></param> /// <returns></returns> private static string RSAPublicKeyJava2DotNet(string publicKey) { RsaKeyParameters publicKeyParam = (RsaKeyParameters)PublicKeyFactory.CreateKey(Convert.FromBase64String(publicKey)); return string.Format("<RSAKeyValue><Modulus>{0}</Modulus><Exponent>{1}</Exponent></RSAKeyValue>", Convert.ToBase64String(publicKeyParam.Modulus.ToByteArrayUnsigned()), Convert.ToBase64String(publicKeyParam.Exponent.ToByteArrayUnsigned())); } } }
呼叫範例:
using CommonUtils; using System; using System.Text; namespace ConsoleNetFxRsaSign { class Program { static void Main(string[] args) { try { //PKCS8格式私鑰 string strPriPkcs8 = "MIICeQIBADANBgkqhkiG9w0BAQEFAASCAmMwggJfAgEAAoGBAMz0Czg6QUtTISa2pUkloeQB/TEpHdqrfyroWpKLW9B/LWFSOGH9nyTk1pPZaeadyEZQ6gay/C0pUAetLraq9bMA/Luxq68b87uG7WX7dKytEO2/87qGpGMRs97H+GlkzWil2QO2KK4cHnAcVicPsmi5aZ72U0BWJFyPhtd+qdmrAgMBAAECgYEAvW67iAbgHt0BASVD9C3iSjpEaVHVlC165o/IVzaTcEx8Bz3Ve0zN8W3JnvIO3ebsG4HiLLr2Nk++9rltOc0eNeGMv7F1e/OFot1wN0ON6s1g4bYh1z5Uz8FcYiMWcqHHICrx+oSFeK9x+I2Zge7enQXcsVnqEhm77ZE5YczSryECQQD9nB58e5efYchF+cYbmURioX18cUMuhQbB9Aq2N55cd689Lg35KZqT8JQTp/8tQSdCJG8d2nU8VKspUKTEAuaDAkEAzuKIIoc9PVJvy90LhIPA9c1S8BPCI7EMCaTZqJ5o3VaR2dqvUZDGX7kL3kYkQ+n7mq3KIECvkEFzA+FOP96XuQJBAJQTKHW0T/YeSKoayUHp/lS8R6F2HCy4PRbXn71+wfbpZqcJEd2OHhQM3tiPOV258esbjMlYeSUNppZL4LgVnXMCQQC7Lvs9Ql+GPDAqo7ToEM1lmICR906QPIBHuX+1sJ3wpYMROWumwPa7ZRH36j6ls+6R5OwcgmpWeuE1gYTrBNsBAkEAn2pEtAljX1foQff6CLozYg/J6J9RmVFcJ6qz0LX3052qNFBQYw8CMHB7VkVNzsDIDC8LX5uP2pzTrdPLew+pPA=="; //PKCS1格式私鑰 string strPriPkcs1 = "MIICXwIBAAKBgQDM9As4OkFLUyEmtqVJJaHkAf0xKR3aq38q6FqSi1vQfy1hUjhh/Z8k5NaT2WnmnchGUOoGsvwtKVAHrS62qvWzAPy7sauvG/O7hu1l+3SsrRDtv/O6hqRjEbPex/hpZM1opdkDtiiuHB5wHFYnD7JouWme9lNAViRcj4bXfqnZqwIDAQABAoGBAL1uu4gG4B7dAQElQ/Qt4ko6RGlR1ZQteuaPyFc2k3BMfAc91XtMzfFtyZ7yDt3m7BuB4iy69jZPvva5bTnNHjXhjL+xdXvzhaLdcDdDjerNYOG2Idc+VM/BXGIjFnKhxyAq8fqEhXivcfiNmYHu3p0F3LFZ6hIZu+2ROWHM0q8hAkEA/ZwefHuXn2HIRfnGG5lEYqF9fHFDLoUGwfQKtjeeXHevPS4N+Smak/CUE6f/LUEnQiRvHdp1PFSrKVCkxALmgwJBAM7iiCKHPT1Sb8vdC4SDwPXNUvATwiOxDAmk2aieaN1Wkdnar1GQxl+5C95GJEPp+5qtyiBAr5BBcwPhTj/el7kCQQCUEyh1tE/2HkiqGslB6f5UvEehdhwsuD0W15+9fsH26WanCRHdjh4UDN7YjzldufHrG4zJWHklDaaWS+C4FZ1zAkEAuy77PUJfhjwwKqO06BDNZZiAkfdOkDyAR7l/tbCd8KWDETlrpsD2u2UR9+o+pbPukeTsHIJqVnrhNYGE6wTbAQJBAJ9qRLQJY19X6EH3+gi6M2IPyeifUZlRXCeqs9C199OdqjRQUGMPAjBwe1ZFTc7AyAwvC1+bj9qc063Ty3sPqTw="; //公鑰 string strPub = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDM9As4OkFLUyEmtqVJJaHkAf0xKR3aq38q6FqSi1vQfy1hUjhh/Z8k5NaT2WnmnchGUOoGsvwtKVAHrS62qvWzAPy7sauvG/O7hu1l+3SsrRDtv/O6hqRjEbPex/hpZM1opdkDtiiuHB5wHFYnD7JouWme9lNAViRcj4bXfqnZqwIDAQAB"; string strDJM = "泰酷拉!123ABC";//待簽名字串 strDJM = "泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC泰酷拉!123ABC";//待加密字串,超過117字元的測試 Console.WriteLine("待簽名字串:" + strDJM); //一、私鑰簽名 //獲取私鑰物件 var rsaPri = RsaUtil.LoadPrivateKey(strPriPkcs1, "PKCS1"); //待簽名字串 轉為byte 陣列 byte[] byToSign = Encoding.UTF8.GetBytes(strDJM); // 編碼要和其它語言一致,一般是:UTF8 byte[] bySigned = rsaPri.SignData(byToSign, "SHA256");//SHA256,對應JAVA,SHA256withRSA,簽名結果是 byte 陣列 string strSigned = Convert.ToBase64String(bySigned);//將byte 陣列轉為字串,方便傳輸,一般是base64字串,其它型別需和對方協商 Console.WriteLine("簽名值:" + strSigned); //二、公鑰驗籤(驗證簽名) //獲取公鑰物件 var rsaPub = RsaUtil.LoadPublicKey(strPub); //將對方的簽名值轉為BYTE陣列。 byte[] byToCheckSign = Convert.FromBase64String(strSigned); //將原始報文轉為byte陣列 byte[] byBody = Encoding.UTF8.GetBytes(strDJM);// 編碼要和其它語言一致,一般是:UTF8 //驗證簽名 bool bCheck = rsaPub.VerifyData(byBody,"SHA256", byToCheckSign);//驗證簽名雙方要保持一致 Console.WriteLine("驗證簽名:" + bCheck.ToString()); } catch (Exception ex) { Console.WriteLine("ex:" + ex.Message); } Console.WriteLine("hello"); Console.ReadKey(); } } }
-