java/php/c#版rsa签名以及java验签实现openssl生成

来源:互联网 发布:网络传输协议 http 编辑:程序博客网 时间:2024/05/16 23:37
在开放平台领域,需要给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,文件内容如下:

-----BEGIN RSA PRIVATE KEY-----  MIICXgIBAAKBgQDtd1lKsX6ylsAEWFi7E/ut8krJy9PQ7sGYKhIm9TvIdZiq5xzy  aw8NOLzKZ1k486MePYG4tSuoaxSbwuPLwVUzYFvnUZo7aWCIGKn16UWTM4nxc/+d  wce+bhcKrlLbTWi8l580LTE7GxclTh8z7gHq59ivhaoGbK7FNxlUfB4TSQIDAQAB  AoGBAIgTk0x1J+hI8KHMypPxoJCOPoMi1S9uEewTd7FxaB+4G5Mbuv/Dj62A7NaD  oKI9IyUqE9L3ppvtOLMFXCofkKU0p4j7MEJdZ+CjVvgextkWa80nj/UZiM1oOL6Y  HwH4ZtPtY+pFCTK1rdn3+070qBB9tnVntbN/jq0Ld7f0t7UNAkEA9ryI0kxJL9Pu  pO9NEeWuCUo4xcl9x/M9+mtkfY3VoDDDV1E/eUjmoTfANYwrjcddiQrO0MLyEdoo  tiLpN77qOwJBAPZhtv/+pqMVTrLxWnVKLZ4ZVTPPgJQQkFdhWwYlz7oKzB3VbQRt  /jLFXUyCN2eCP7rglrXnaz7AYBftF0ajHEsCQQDDNfkeQULqN0gpcDdOwKRIL1Pp  kHgWmWlg1lTETVJGEi6Kx/prL/VgeiZ1dzgCTUjAoy9r1cEFxM/PAqH3+/F/AkEA  zsTCp6Q2hLblDRewKq7OCdiIwKpr5dbgy/RQR6CD7EYTdxYeH5GPu1wXKJY/mQae  JV9GG/LS9h7MhkfbONS6cQJAdBEb5vloBDLcSQFDQO/VZ9SKFHCmHLXluhhIizYK  Gzgf3OXEGNDSAC3qy+ZTnLd3N5iYrVbK52UoiLOLhhNMqA==  -----END RSA PRIVATE KEY-----  

c)生成公钥
在bin目录下,执行以下命令:
openssl rsa -in rsa_private_key.pem -pubout -out rsa_public_key.pem
会在bin目录下看到新生成的公钥文件rsa_public_key.pem,文件内容如下:

-----BEGIN PUBLIC KEY-----  MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDtd1lKsX6ylsAEWFi7E/ut8krJ  y9PQ7sGYKhIm9TvIdZiq5xzyaw8NOLzKZ1k486MePYG4tSuoaxSbwuPLwVUzYFvn  UZo7aWCIGKn16UWTM4nxc/+dwce+bhcKrlLbTWi8l580LTE7GxclTh8z7gHq59iv  haoGbK7FNxlUfB4TSQIDAQAB  -----END PUBLIC KEY-----  
2. 客户端签名
  2.1 java版签名实现
/**      * rsa签名      *       * @param content      *            待签名的字符串      * @param privateKey      *            rsa私钥字符串      * @param charset      *            字符编码      * @return 签名结果      * @throws Exception      *             签名失败则抛出异常      */      public String rsaSign(String content, String privateKey, String charset) throws SignatureException {          try {              PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(privateKey.getBytes()));                Signature signature = Signature.getInstance("SHA1WithRSA");              signature.initSign(priKey);              if (StringUtils.isEmpty(charset)) {                  signature.update(content.getBytes());              } else {                  signature.update(content.getBytes(charset));              }                byte[] signed = signature.sign();              return new String(Base64.encodeBase64(signed));          } catch (Exception e) {              throw new SignatureException("RSAcontent = " + content + "; charset = " + charset, e);          }      }        public PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {          if (ins == null || StringUtils.isEmpty(algorithm)) {              return null;          }            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);          byte[] encodedKey = StreamUtil.readText(ins).getBytes();          encodedKey = Base64.decodeBase64(encodedKey);          return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));      }  

注意:参数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签名实现

function sign($content, $rsaPrivateKeyPem) {          $priKey = file_get_contents($rsaPrivateKeyPem);          $res = openssl_get_privatekey($priKey);          openssl_sign($content, $sign, $res);          openssl_free_key($res);          $sign = base64_encode($sign);          return $sign;      }  

注意:$rsaPrivateKeyPem为pem私钥文件路径
  2.3 c#签名实现(引用了国外某位仁兄的方案)

using System;  using System.Text;  using System.Security.Cryptography;  using System.Web;  using System.IO;    namespace Aop.Api.Util  {      /// <summary>      /// RSA签名工具类。      /// </summary>      public class RSAUtil      {            public static string RSASign(string data, string privateKeyPem)          {              RSACryptoServiceProvider rsaCsp = LoadCertificateFile(privateKeyPem);              byte[] dataBytes = Encoding.UTF8.GetBytes(data);              byte[] signatureBytes = rsaCsp.SignData(dataBytes, "SHA1");              return Convert.ToBase64String(signatureBytes);          }            private static byte[] GetPem(string type, byte[] data)          {              string pem = Encoding.UTF8.GetString(data);              string header = String.Format("-----BEGIN {0}-----\\n", type);              string footer = String.Format("-----END {0}-----", type);              int start = pem.IndexOf(header) + header.Length;              int end = pem.IndexOf(footer, start);              string base64 = pem.Substring(start, (end - start));              return Convert.FromBase64String(base64);          }            private static RSACryptoServiceProvider LoadCertificateFile(string filename)          {              using (System.IO.FileStream fs = System.IO.File.OpenRead(filename))              {                  byte[] data = new byte[fs.Length];                  byte[] res = null;                  fs.Read(data, 0, data.Length);                  if (data[0] != 0x30)                  {                      res = GetPem("RSA PRIVATE KEY", data);                  }                  try                  {                      RSACryptoServiceProvider rsa = DecodeRSAPrivateKey(res);                      return rsa;                  }                  catch (Exception ex)                  {                  }                  return null;              }          }            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 -----                  CspParameters CspParameters = new CspParameters();                  CspParameters.Flags = CspProviderFlags.UseMachineKeyStore;                  RSACryptoServiceProvider RSA = new RSACryptoServiceProvider(1024, CspParameters);                  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)              {                  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;          }      }  }  

注:privateKeyPem为私钥文件路径
  3. 服务端java验签

/** 
     * rsa验签 
     *  
     * @param content 被签名的内容 
     * @param sign 签名后的结果 
     * @param publicKey rsa公钥 
     * @param charset 字符集 
     * @return 验签结果 
     * @throws SignatureException 验签失败,则抛异常 
     */  
    boolean doCheck(String content, String sign, String publicKey, String charset) throws SignatureException {  
        try {  
            PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes()));  
  
            Signature signature = Signature.getInstance("SHA1WithRSA");  
            signature.initVerify(pubKey);  
            signature.update(getContentBytes(content, charset));  
            return signature.verify(Base64.decodeBase64(sign.getBytes()));  
        } catch (Exception e) {  
            throw new SignatureException("RSA验证签名[content = " + content + "; charset = " + charset  
                                         + "; signature = " + sign + "]发生异常!", e);  
        }  
    }  
  
    private PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws NoSuchAlgorithmException {  
        try {  
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);  
  
            StringWriter writer = new StringWriter();  
            StreamUtil.io(new InputStreamReader(ins), writer);  
            byte[] encodedKey = writer.toString().getBytes();  
  
            // 先base64解码  
            encodedKey = Base64.decodeBase64(encodedKey);  
            return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));  
        } catch (IOException ex) {  
            // 不可能发生  
        } catch (InvalidKeySpecException ex) {  
            // 不可能发生  
        }  
        return null;  
    }  
  
    private byte[] getContentBytes(String content, String charset) throws UnsupportedEncodingException {  
        if (StringUtil.isEmpty(charset)) {  
            return content.getBytes();  
        }  
  
        return content.getBytes(charset);  
    }  


注意:参数publicKey是Pem公钥文件中去除头(-----BEGIN RSA PRIVATE KEY-----)和尾(-----END RSA PRIVATE KEY-----)以及换行符后的字符串。









0 0