今天有同学对接一个支付平台,涉及到RSA的签名和验签。由于对方是java的sdk,翻成c#语言时,搞了半天也没搞定。网上搜的东西都是各种copy还不解决问题。 碰巧,我之前对接过连连银通的网银支付和代付。连连那边提供了c#版的RSA加密算法,立即破了这位同学的难题。在这里也分享出来,以便大家日后备用。
1 using System; 2 using System.Collections.Generic; 3 using System.Text; 4 using System.IO; 5 using System.Security.Cryptography; 6 7 ///8 /// 类名:RSAFromPkcs8 9 /// 功能:RSA解密、签名、验签 10 /// 详细:该类对Java生成的密钥进行解密和签名以及验签专用类,不需要修改 11 /// 版本:1.0 12 /// 日期:2013-09-30 13 /// 说明: 14 /// 以下代码只是为了方便商户测试而提供的样例代码,商户可以根据自己网站的需要,按照技术文档编写,并非一定要使用该代码。 15 /// 16 public sealed class RSAFromPkcs8 17 { 18 /// 19 /// 签名 20 /// 21 /// 待签名字符串 22 /// 私钥 23 /// 编码格式 24 /// 签名后字符串 25 public static string sign(string content, string privateKey, string input_charset = "utf-8") 26 { 27 byte[] Data = Encoding.GetEncoding(input_charset).GetBytes(content); 28 RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey); 29 //SHA1 crypto = new SHA1CryptoServiceProvider(); 30 MD5 crypto = new MD5CryptoServiceProvider(); 31 byte[] signData = rsa.SignData(Data, crypto); 32 return Convert.ToBase64String(signData); 33 } 34 35 /// 36 /// 验签 37 /// 38 /// 待验签字符串 39 /// 签名 40 /// 公钥 41 /// 编码格式 42 /// true(通过),false(不通过) 43 public static bool verify(string content, string signedString, string publicKey, string input_charset = "utf-8") 44 { 45 bool result = false; 46 byte[] Data = Encoding.GetEncoding(input_charset).GetBytes(content); 47 byte[] data = Convert.FromBase64String(signedString); 48 RSAParameters paraPub = ConvertFromPublicKey(publicKey); 49 RSACryptoServiceProvider rsaPub = new RSACryptoServiceProvider(); 50 rsaPub.ImportParameters(paraPub); 51 //SHA1 crypto = new SHA1CryptoServiceProvider(); 52 MD5 crypto = new MD5CryptoServiceProvider(); 53 result = rsaPub.VerifyData(Data, crypto, data); 54 return result; 55 } 56 57 /// 58 /// 解密 59 /// 60 /// 加密字符串 61 /// 私钥 62 /// 编码格式 63 /// 明文 64 public static string decryptData(string resData, string privateKey, string input_charset) 65 { 66 byte[] DataToDecrypt = Convert.FromBase64String(resData); 67 List<byte> result = new List<byte>(); 68 for (int j = 0; j < DataToDecrypt.Length / 128; j++) 69 { 70 byte[] buf = new byte[128]; 71 for (int i = 0; i < 128; i++) 72 { 73 buf[i] = DataToDecrypt[i + 128 * j]; 74 } 75 result.AddRange(decrypt(buf, privateKey, input_charset)); 76 } 77 byte[] source = result.ToArray(); 78 char[] asciiChars = new char[Encoding.GetEncoding(input_charset).GetCharCount(source, 0, source.Length)]; 79 Encoding.GetEncoding(input_charset).GetChars(source, 0, source.Length, asciiChars, 0); 80 return new string(asciiChars); 81 } 82 83 #region 内部方法 84 85 private static byte[] decrypt(byte[] data, string privateKey, string input_charset) 86 { 87 RSACryptoServiceProvider rsa = DecodePemPrivateKey(privateKey); 88 //SHA1 sh = new SHA1CryptoServiceProvider(); 89 return rsa.Decrypt(data, false); 90 } 91 92 private static RSACryptoServiceProvider DecodePemPrivateKey(String pemstr) 93 { 94 byte[] pkcs8privatekey; 95 pkcs8privatekey = Convert.FromBase64String(pemstr); 96 if (pkcs8privatekey != null) 97 { 98 RSACryptoServiceProvider rsa = DecodePrivateKeyInfo(pkcs8privatekey); 99 return rsa; 100 } 101 else 102 return null; 103 } 104 105 private static RSACryptoServiceProvider DecodePrivateKeyInfo(byte[] pkcs8) 106 { 107 byte[] SeqOID = { 0x30, 0x0D, 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01, 0x05, 0x00 }; 108 byte[] seq = new byte[15]; 109 110 MemoryStream mem = new MemoryStream(pkcs8); 111 int lenstream = (int)mem.Length; 112 BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading 113 byte bt = 0; 114 ushort twobytes = 0; 115 116 try 117 { 118 twobytes = binr.ReadUInt16(); 119 if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) 120 binr.ReadByte(); //advance 1 byte 121 else if (twobytes == 0x8230) 122 binr.ReadInt16(); //advance 2 bytes 123 else 124 return null; 125 126 bt = binr.ReadByte(); 127 if (bt != 0x02) 128 return null; 129 130 twobytes = binr.ReadUInt16(); 131 132 if (twobytes != 0x0001) 133 return null; 134 135 seq = binr.ReadBytes(15); //read the Sequence OID 136 if (!CompareBytearrays(seq, SeqOID)) //make sure Sequence for OID is correct 137 return null; 138 139 bt = binr.ReadByte(); 140 if (bt != 0x04) //expect an Octet string 141 return null; 142 143 bt = binr.ReadByte(); //read next byte, or next 2 bytes is 0x81 or 0x82; otherwise bt is the byte count 144 if (bt == 0x81) 145 binr.ReadByte(); 146 else 147 if (bt == 0x82) 148 binr.ReadUInt16(); 149 //------ at this stage, the remaining sequence should be the RSA private key 150 151 byte[] rsaprivkey = binr.ReadBytes((int)(lenstream - mem.Position)); 152 RSACryptoServiceProvider rsacsp = DecodeRSAPrivateKey(rsaprivkey); 153 return rsacsp; 154 } 155 156 catch (Exception) 157 { 158 return null; 159 } 160 161 finally { binr.Close(); } 162 163 } 164 165 private static bool CompareBytearrays(byte[] a, byte[] b) 166 { 167 if (a.Length != b.Length) 168 return false; 169 int i = 0; 170 foreach (byte c in a) 171 { 172 if (c != b[i]) 173 return false; 174 i++; 175 } 176 return true; 177 } 178 179 private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) 180 { 181 byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; 182 183 // --------- Set up stream to decode the asn.1 encoded RSA private key ------ 184 MemoryStream mem = new MemoryStream(privkey); 185 BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading 186 byte bt = 0; 187 ushort twobytes = 0; 188 int elems = 0; 189 try 190 { 191 twobytes = binr.ReadUInt16(); 192 if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) 193 binr.ReadByte(); //advance 1 byte 194 else if (twobytes == 0x8230) 195 binr.ReadInt16(); //advance 2 bytes 196 else 197 return null; 198 199 twobytes = binr.ReadUInt16(); 200 if (twobytes != 0x0102) //version number 201 return null; 202 bt = binr.ReadByte(); 203 if (bt != 0x00) 204 return null; 205 206 207 //------ all private key components are Integer sequences ---- 208 elems = GetIntegerSize(binr); 209 MODULUS = binr.ReadBytes(elems); 210 211 elems = GetIntegerSize(binr); 212 E = binr.ReadBytes(elems); 213 214 elems = GetIntegerSize(binr); 215 D = binr.ReadBytes(elems); 216 217 elems = GetIntegerSize(binr); 218 P = binr.ReadBytes(elems); 219 220 elems = GetIntegerSize(binr); 221 Q = binr.ReadBytes(elems); 222 223 elems = GetIntegerSize(binr); 224 DP = binr.ReadBytes(elems); 225 226 elems = GetIntegerSize(binr); 227 DQ = binr.ReadBytes(elems); 228 229 elems = GetIntegerSize(binr); 230 IQ = binr.ReadBytes(elems); 231 232 // ------- create RSACryptoServiceProvider instance and initialize with public key ----- 233 RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(); 234 RSAParameters RSAparams = new RSAParameters(); 235 RSAparams.Modulus = MODULUS; 236 RSAparams.Exponent = E; 237 RSAparams.D = D; 238 RSAparams.P = P; 239 RSAparams.Q = Q; 240 RSAparams.DP = DP; 241 RSAparams.DQ = DQ; 242 RSAparams.InverseQ = IQ; 243 RSA.ImportParameters(RSAparams); 244 return RSA; 245 } 246 catch (Exception) 247 { 248 return null; 249 } 250 finally { binr.Close(); } 251 } 252 253 private static int GetIntegerSize(BinaryReader binr) 254 { 255 byte bt = 0; 256 byte lowbyte = 0x00; 257 byte highbyte = 0x00; 258 int count = 0; 259 bt = binr.ReadByte(); 260 if (bt != 0x02) //expect integer 261 return 0; 262 bt = binr.ReadByte(); 263 264 if (bt == 0x81) 265 count = binr.ReadByte(); // data size in next byte 266 else 267 if (bt == 0x82) 268 { 269 highbyte = binr.ReadByte(); // data size in next 2 bytes 270 lowbyte = binr.ReadByte(); 271 byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; 272 count = BitConverter.ToInt32(modint, 0); 273 } 274 else 275 { 276 count = bt; // we already have the data size 277 } 278 279 280 281 while (binr.ReadByte() == 0x00) 282 { //remove high order zeros in data 283 count -= 1; 284 } 285 binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn't a removed zero, so back up a byte 286 return count; 287 } 288 289 #endregion 290 291 #region 解析.net 生成的Pem 292 private static RSAParameters ConvertFromPublicKey(string pemFileConent) 293 { 294 295 byte[] keyData = Convert.FromBase64String(pemFileConent); 296 if (keyData.Length < 162) 297 { 298 throw new ArgumentException("pem file content is incorrect."); 299 } 300 byte[] pemModulus = new byte[128]; 301 byte[] pemPublicExponent = new byte[3]; 302 Array.Copy(keyData, 29, pemModulus, 0, 128); 303 Array.Copy(keyData, 159, pemPublicExponent, 0, 3); 304 RSAParameters para = new RSAParameters(); 305 para.Modulus = pemModulus; 306 para.Exponent = pemPublicExponent; 307 return para; 308 } 309 310 private static RSAParameters ConvertFromPrivateKey(string pemFileConent) 311 { 312 byte[] keyData = Convert.FromBase64String(pemFileConent); 313 if (keyData.Length < 609) 314 { 315 throw new ArgumentException("pem file content is incorrect."); 316 } 317 318 int index = 11; 319 byte[] pemModulus = new byte[128]; 320 Array.Copy(keyData, index, pemModulus, 0, 128); 321 322 index += 128; 323 index += 2;//141 324 byte[] pemPublicExponent = new byte[3]; 325 Array.Copy(keyData, index, pemPublicExponent, 0, 3); 326 327 index += 3; 328 index += 4;//148 329 byte[] pemPrivateExponent = new byte[128]; 330 Array.Copy(keyData, index, pemPrivateExponent, 0, 128); 331 332 index += 128; 333 index += ((int)keyData[index + 1] == 64 ? 2 : 3);//279 334 byte[] pemPrime1 = new byte[64]; 335 Array.Copy(keyData, index, pemPrime1, 0, 64); 336 337 index += 64; 338 index += ((int)keyData[index + 1] == 64 ? 2 : 3);//346 339 byte[] pemPrime2 = new byte[64]; 340 Array.Copy(keyData, index, pemPrime2, 0, 64); 341 342 index += 64; 343 index += ((int)keyData[index + 1] == 64 ? 2 : 3);//412/413 344 byte[] pemExponent1 = new byte[64]; 345 Array.Copy(keyData, index, pemExponent1, 0, 64); 346 347 index += 64; 348 index += ((int)keyData[index + 1] == 64 ? 2 : 3);//479/480 349 byte[] pemExponent2 = new byte[64]; 350 Array.Copy(keyData, index, pemExponent2, 0, 64); 351 352 index += 64; 353 index += ((int)keyData[index + 1] == 64 ? 2 : 3);//545/546 354 byte[] pemCoefficient = new byte[64]; 355 Array.Copy(keyData, index, pemCoefficient, 0, 64); 356 357 RSAParameters para = new RSAParameters(); 358 para.Modulus = pemModulus; 359 para.Exponent = pemPublicExponent; 360 para.D = pemPrivateExponent; 361 para.P = pemPrime1; 362 para.Q = pemPrime2; 363 para.DP = pemExponent1; 364 para.DQ = pemExponent2; 365 para.InverseQ = pemCoefficient; 366 return para; 367 } 368 #endregion 369 370 }