本文为转载,请转载请注明地址: 原文地址为 http://xw-z1985.iteye.com/blog/1837376
在开放平台领域,需要给isv提供sdk,签名是Sdk中需要提供的功能之一。由于isv使用的开发语言不是单一的,因此sdk需要提供多种语言的版 本。譬如java、php、c#。另外,在电子商务尤其是支付领域,对安全性的要求比较高,所以会采用非对称密钥RSA
本文主要介绍如何基于java、php、c#在客户端使用rsa签名,然后在服务端使用Java验签。
- 基于openssl生成RSA公私钥对
a)从网上下载openssl工具:http://www.slproweb.com/products/Win32OpenSSL.html
b)生成私钥
进入到openssl的bin目录下,执行以下命令:
openssl genrsa -out rsa_private_key.pem 1024
会在bin目录下看到新生成的私钥文件rsa_private_key.pem,文件内容如下:
Xml代码
1 -----BEGIN RSA PRIVATE KEY----- 2 MIICXgIBAAKBgQDtd1lKsX6ylsAEWFi7E/ut8krJy9PQ7sGYKhIm9TvIdZiq5xzy 3 aw8NOLzKZ1k486MePYG4tSuoaxSbwuPLwVUzYFvnUZo7aWCIGKn16UWTM4nxc/+d 4 wce+bhcKrlLbTWi8l580LTE7GxclTh8z7gHq59ivhaoGbK7FNxlUfB4TSQIDAQAB 5 AoGBAIgTk0x1J+hI8KHMypPxoJCOPoMi1S9uEewTd7FxaB+4G5Mbuv/Dj62A7NaD 6 oKI9IyUqE9L3ppvtOLMFXCofkKU0p4j7MEJdZ+CjVvgextkWa80nj/UZiM1oOL6Y 7 HwH4ZtPtY+pFCTK1rdn3+070qBB9tnVntbN/jq0Ld7f0t7UNAkEA9ryI0kxJL9Pu 8 pO9NEeWuCUo4xcl9x/M9+mtkfY3VoDDDV1E/eUjmoTfANYwrjcddiQrO0MLyEdoo 9 tiLpN77qOwJBAPZhtv/+pqMVTrLxWnVKLZ4ZVTPPgJQQkFdhWwYlz7oKzB3VbQRt 10 /jLFXUyCN2eCP7rglrXnaz7AYBftF0ajHEsCQQDDNfkeQULqN0gpcDdOwKRIL1Pp 11 kHgWmWlg1lTETVJGEi6Kx/prL/VgeiZ1dzgCTUjAoy9r1cEFxM/PAqH3+/F/AkEA 12 zsTCp6Q2hLblDRewKq7OCdiIwKpr5dbgy/RQR6CD7EYTdxYeH5GPu1wXKJY/mQae 13 JV9GG/LS9h7MhkfbONS6cQJAdBEb5vloBDLcSQFDQO/VZ9SKFHCmHLXluhhIizYK 14 Gzgf3OXEGNDSAC3qy+ZTnLd3N5iYrVbK52UoiLOLhhNMqA== 15 -----END RSA PRIVATE KEY-----
c)生成公钥
在bin目录下,执行以下命令:
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
会在bin目录下看到新生成的公钥文件rsa_public_key.pem,文件内容如下:
Xml代码
1 -----BEGIN PUBLIC KEY----- 2 MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtd1lKsX6ylsAEWFi7E/ut8krJ 3 y9PQ7sGYKhIm9TvIdZiq5xzyaw8NOLzKZ1k486MePYG4tSuoaxSbwuPLwVUzYFvn 4 UZo7aWCIGKn16UWTM4nxc/+dwce+bhcKrlLbTWi8l580LTE7GxclTh8z7gHq59iv 5 haoGbK7FNxlUfB4TSQIDAQAB 6 -----END PUBLIC KEY-----
2. 客户端签名
2.1 java版签名实现
Java代码
1 /** 2 * rsa签名 4 * @param content 5 * 待签名的字符串 6 * @param privateKey 7 * rsa私钥字符串 8 * @param charset 9 * 字符编码 10 * @return 签名结果 11 * @throws Exception 12 * 签名失败则抛出异常 13 */ 14 public String rsaSign(String content, String privateKey, String charset) throws SignatureException { 15 try { 16 PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(privateKey.getBytes())); 17 18 Signature signature = Signature.getInstance("SHA1WithRSA"); 19 signature.initSign(priKey); 20 if (StringUtils.isEmpty(charset)) { 21 signature.update(content.getBytes()); 22 } else { 23 signature.update(content.getBytes(charset)); 24 } 25 26 byte[] signed = signature.sign(); 27 return new String(Base64.encodeBase64(signed)); 28 } catch (Exception e) { 29 throw new SignatureException("RSAcontent = " + content + "; charset = " + charset, e); 30 } 31 } 32 33 public PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception { 34 if (ins == null || StringUtils.isEmpty(algorithm)) { 35 return null; 36 } 37 38 KeyFactory keyFactory = KeyFactory.getInstance(algorithm); 39 byte[] encodedKey = StreamUtil.readText(ins).getBytes(); 40 encodedKey = Base64.decodeBase64(encodedKey); 41 return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey)); 42 }
注意:参数privateKey是Pem私钥文件中去除头(-----BEGIN RSA PRIVATE KEY-----)和尾(-----END RSA PRIVATE KEY-----)以及换行符后的字符串。
如果签名报以下错误:
java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: IOException : algid parse error, not a sequence
则说明rsa私钥的格式不是pksc8格式,需要使用以下命令转换一下:
openssl pkcs8 -topk8 -inform PEM -in rsa_private_key.pem -outform PEM -nocrypt
然后再提取去除头和尾以及换行符后字符串作为java版用的rsa私钥
2.2 php签名实现
Php代码
1 function sign($content, $rsaPrivateKeyPem) { 2 $priKey = file_get_contents($rsaPrivateKeyPem); 3 $res = openssl_get_privatekey($priKey); 4 openssl_sign($content, $sign, $res); 5 openssl_free_key($res); 6 $sign = base64_encode($sign); 7 return $sign; 8 }
注意:$rsaPrivateKeyPem为pem私钥文件路径
2.3 c#签名实现(引用了国外某位仁兄的方案)
C#代码
1 using System; 2 using System.Text; 3 using System.Security.Cryptography; 4 using System.Web; 5 using System.IO; 6 7 namespace Aop.Api.Util 8 { 9 /// <summary> 10 /// RSA签名工具类。 11 /// </summary> 12 public class RSAUtil 13 { 14 15 public static string RSASign(string data, string privateKeyPem) 16 { 17 RSACryptoServiceProvider rsaCsp = LoadCertificateFile(privateKeyPem); 18 byte[] dataBytes = Encoding.UTF8.GetBytes(data); 19 byte[] signatureBytes = rsaCsp.SignData(dataBytes, "SHA1"); 20 return Convert.ToBase64String(signatureBytes); 21 } 22 23 private static byte[] GetPem(string type, byte[] data) 24 { 25 string pem = Encoding.UTF8.GetString(data); 26 string header = String.Format("-----BEGIN {0}-----\\n", type); 27 string footer = String.Format("-----END {0}-----", type); 28 int start = pem.IndexOf(header) + header.Length; 29 int end = pem.IndexOf(footer, start); 30 string base64 = pem.Substring(start, (end - start)); 31 return Convert.FromBase64String(base64); 32 } 33 34 private static RSACryptoServiceProvider LoadCertificateFile(string filename) 35 { 36 using (System.IO.FileStream fs = System.IO.File.OpenRead(filename)) 37 { 38 byte[] data = new byte[fs.Length]; 39 byte[] res = null; 40 fs.Read(data, 0, data.Length); 41 if (data[0] != 0x30) 42 { 43 res = GetPem("RSA PRIVATE KEY", data); 44 } 45 try 46 { 47 RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(res); 48 return rsa; 49 } 50 catch (Exception ex) 51 { 52 } 53 return null; 54 } 55 } 56 57 private static RSACryptoServiceProvider DecodeRSAPrivateKey(byte[] privkey) 58 { 59 byte[] MODULUS, E, D, P, Q, DP, DQ, IQ; 60 61 // --------- Set up stream to decode the asn.1 encoded RSA private key ------ 62 MemoryStream mem = new MemoryStream(privkey); 63 BinaryReader binr = new BinaryReader(mem); //wrap Memory Stream with BinaryReader for easy reading 64 byte bt = 0; 65 ushort twobytes = 0; 66 int elems = 0; 67 try 68 { 69 twobytes = binr.ReadUInt16(); 70 if (twobytes == 0x8130) //data read as little endian order (actual data order for Sequence is 30 81) 71 binr.ReadByte(); //advance 1 byte 72 else if (twobytes == 0x8230) 73 binr.ReadInt16(); //advance 2 bytes 74 else 75 return null; 76 77 twobytes = binr.ReadUInt16(); 78 if (twobytes != 0x0102) //version number 79 return null; 80 bt = binr.ReadByte(); 81 if (bt != 0x00) 82 return null; 83 84 85 //------ all private key components are Integer sequences ---- 86 elems = GetIntegerSize(binr); 87 MODULUS = binr.ReadBytes(elems); 88 89 elems = GetIntegerSize(binr); 90 E = binr.ReadBytes(elems); 91 92 elems = GetIntegerSize(binr); 93 D = binr.ReadBytes(elems); 94 95 elems = GetIntegerSize(binr); 96 P = binr.ReadBytes(elems); 97 98 elems = GetIntegerSize(binr); 99 Q = binr.ReadBytes(elems); 100 101 elems = GetIntegerSize(binr); 102 DP = binr.ReadBytes(elems); 103 104 elems = GetIntegerSize(binr); 105 DQ = binr.ReadBytes(elems); 106 107 elems = GetIntegerSize(binr); 108 IQ = binr.ReadBytes(elems); 109 110 111 // ------- create RSACryptoServiceProvider instance and initialize with public key ----- 112 CspParameters CspParameters = new CspParameters(); 113 CspParameters.Flags = CspProviderFlags.UseMachineKeyStore; 114 RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024, CspParameters); 115 RSAParameters RSAparams = new RSAParameters(); 116 RSAparams.Modulus = MODULUS; 117 RSAparams.Exponent = E; 118 RSAparams.D = D; 119 RSAparams.P = P; 120 RSAparams.Q = Q; 121 RSAparams.DP = DP; 122 RSAparams.DQ = DQ; 123 RSAparams.InverseQ = IQ; 124 RSA.ImportParameters(RSAparams); 125 return RSA; 126 } 127 catch (Exception ex) 128 { 129 return null; 130 } 131 finally 132 { 133 binr.Close(); 134 } 135 } 136 137 private static int GetIntegerSize(BinaryReader binr) 138 { 139 byte bt = 0; 140 byte lowbyte = 0x00; 141 byte highbyte = 0x00; 142 int count = 0; 143 bt = binr.ReadByte(); 144 if (bt != 0x02) //expect integer 145 return 0; 146 bt = binr.ReadByte(); 147 148 if (bt == 0x81) 149 count = binr.ReadByte(); // data size in next byte 150 else 151 if (bt == 0x82) 152 { 153 highbyte = binr.ReadByte(); // data size in next 2 bytes 154 lowbyte = binr.ReadByte(); 155 byte[] modint = { lowbyte, highbyte, 0x00, 0x00 }; 156 count = BitConverter.ToInt32(modint, 0); 157 } 158 else 159 { 160 count = bt; // we already have the data size 161 } 162 163 while (binr.ReadByte() == 0x00) 164 { //remove high order zeros in data 165 count -= 1; 166 } 167 binr.BaseStream.Seek(-1, SeekOrigin.Current); //last ReadByte wasn‘t a removed zero, so back up a byte 168 return count; 169 } 170 } 171 }
注:privateKeyPem为私钥文件路径
3. 服务端java验签
Java代码
1 /** 2 * rsa验签 3 * 4 * @param content 被签名的内容 5 * @param sign 签名后的结果 6 * @param publicKey rsa公钥 7 * @param charset 字符集 8 * @return 验签结果 9 * @throws SignatureException 验签失败,则抛异常 10 */ 11 boolean doCheck(String content, String sign, String publicKey, String charset) throws SignatureException { 12 try { 13 PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes())); 14 15 Signature signature = Signature.getInstance("SHA1WithRSA"); 16 signature.initVerify(pubKey); 17 signature.update(getContentBytes(content, charset)); 18 return signature.verify(Base64.decodeBase64(sign.getBytes())); 19 } catch (Exception e) { 20 throw new SignatureException("RSA验证签名[content = " + content + "; charset = " + charset 21 + "; signature = " + sign + "]发生异常!", e); 22 } 23 } 24 25 private PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws NoSuchAlgorithmException { 26 try { 27 KeyFactory keyFactory = KeyFactory.getInstance(algorithm); 28 29 StringWriter writer = new StringWriter(); 30 StreamUtil.io(new InputStreamReader(ins), writer); 31 byte[] encodedKey = writer.toString().getBytes(); 32 33 // 先base64解码 34 encodedKey = Base64.decodeBase64(encodedKey); 35 return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); 36 } catch (IOException ex) { 37 // 不可能发生 38 } catch (InvalidKeySpecException ex) { 39 // 不可能发生 40 } 41 return null; 42 } 43 44 private byte[] getContentBytes(String content, String charset) throws UnsupportedEncodingException { 45 if (StringUtil.isEmpty(charset)) { 46 return content.getBytes(); 47 } 48 49 return content.getBytes(charset); 50 }
注意:参数publicKey是Pem公钥文件中去除头(-----BEGIN RSA PRIVATE KEY-----)和尾(-----END RSA PRIVATE KEY-----)以及换行符后的字符串。