using System; using System.Collections.Generic; using System.Text; using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace API.Tools { /// <summary> /// 類名:RSAFromPkcs8 /// 功能:RSA解密、簽名、驗簽 /// 詳細:該類對Java生成的密鑰進行解密和簽名以及驗簽專用類,不需要修改 /// 版本:2.0 /// 修改日期:2011-05-10 /// 說明: /// 以下代碼只是為了方便商戶測試而提供的樣例代碼,商戶可以根據自己網站的需要,按照技術文檔編寫,並非一定要使用該代碼。 /// 該代碼僅供學習和研究支付寶接口使用,只是提供一個參考。 /// </summary> public sealed class Signatory { /// <summary> /// 簽名(C#解釋證書導出的,會出現“數據不正確”的錯誤) /// </summary> /// <param name="privateKey">私鑰</param> /// <param name="content">需要簽名的內容</param> /// <param name="encode">編碼格式</param> /// <returns></returns> public static string sign(string privateKey, string content, Encoding encode) { byte[] Data = encode.GetBytes(content); RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey); MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider(); byte[] signData = rsa.SignData(Data, m5); return Convert.ToBase64String(signData); } /// <summary> /// 驗證簽名 /// </summary> /// <param name="publicKey">公鑰</param> /// <param name="content">需要驗證的內容</param> /// <param name="signedString">簽名結果</param> /// <param name="encode">編碼格式</param> /// <returns></returns> public static bool verify(string publicKey, string content, string signedString, Encoding encode) { bool result = false; byte[] Data = encode.GetBytes(content); byte[] data = Convert.FromBase64String(signedString); RSAParameters paraPub = ConvertFromPublicKey(publicKey); RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider(); rsaPub.ImportParameters(paraPub); MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider(); result = rsaPub.VerifyData(Data, m5, data); return result; } /// <summary> /// 解析java生成的pem文件私鑰 /// </summary> /// <param name="pemstr"></param> /// <returns></returns> private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr) { byte[] pkcs8privatekey; pkcs8privatekey = Convert.FromBase64String(pemstr); if (pkcs8privatekey != null) { RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey); return rsa; } else return null; } private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8) { byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; byte[] seq = new byte[15]; MemoryStream mem = new MemoryStream(pkcs8); int lenstream = (int)mem.Length; BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading byte bt = 0; ushort twobytes = 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; bt = binr.ReadByte(); if (bt != 0x02) return null; twobytes = binr.ReadUInt16(); if (twobytes != 0x0001) return null; seq = binr.ReadBytes(15); //read the Sequence OID if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct return null; bt = binr.ReadByte(); if (bt != 0x04) //expect an Octet string return null; bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count if (bt == 0x81) binr.ReadByte(); else if (bt == 0x82) binr.ReadUInt16(); //------ at this stage, the remaining sequence should be the RSA private key byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position)); RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey); return rsacsp; } catch (Exception) { return null; } finally { binr.Close(); } } private static bool CompareBytearrays(byte[] a, byte[] b) { if (a.Length != b.Length) return false; int i = 0; foreach (byte c in a) { if (c != b[i]) return false; i++; } return true; } private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) { 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 ----- 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) { 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; } private static RSAParameters ConvertFromPublicKey(string pemFileConent) { byte[] keyData = Convert.FromBase64String(pemFileConent); if (keyData.Length < 162) { throw new ArgumentException("pem file content is incorrect."); } byte[] pemModulus = new byte[128]; byte[] pemPublicExponent = new byte[3]; Array.Copy(keyData, 29, pemModulus, 0, 128); Array.Copy(keyData, 159, pemPublicExponent, 0, 3); RSAParameters para = new RSAParameters(); para.Modulus = pemModulus; para.Exponent = pemPublicExponent; return para; } } }
上面的類在平常使用的時候,是沒有問題的,但,我們在對接第三方支付(易聯支付)時,遇到一個非常刺手的,簽名不通過。
- 簽名證書,是從北京數字認證拿到,里面有一個文件xxxxx-Signature.pfx 和證書密碼,我們需要從該文件中,使用openssl.exe工具,導出RSA公鑰和私鑰
- 導出的公鑰,發給合作平台配上,私鑰用於數據簽名
- 簽名時,使用導出的私鑰進行。
問題來了,調用上面程序的sign()方法,解釋私鑰時,總報“數據不正解”或者“無效的私鑰”之類的。更奇怪的是,該私鑰在java下使用沒有問題。聯系了對方,對方試了好多幾證書,只有我們的這個有問題。好吧,算倒霉吧。
解決方法:
.net里可以直接使用證書進行RSA簽名:
/// <summary> /// 簽名,直接使用證書簽名 /// </summary> /// <param name="filePath"></param> /// <param name="password"></param> /// <param name="content"></param> /// <param name="encode"></param> /// <returns></returns> public static string sign(string filePath,string password, string content, Encoding encode) { byte[] Data = encode.GetBytes(content); X509Certificate2 myX509 = new X509Certificate2(filePath, password, X509KeyStorageFlags.Exportable); var rsa = (RSACryptoServiceProvider)myX509.PrivateKey; MD5CryptoServiceProvider m5 = new MD5CryptoServiceProvider(); byte[] signData = rsa.SignData(Data, m5); return Convert.ToBase64String(signData); }
問題解決!