zoukankan      html  css  js  c++  java
  • 记一次Java加密加签算法到php的坑

    此文为本人原创首发于 http://www.35coder.com/convert_encryption_codes_to_php/

    写代码的经历中,总少不了与外部的程序对接,一旦有这样的事,往往周期会很长,很麻烦,因为你要考虑的事会多了很多,其中安全性的加密解密就是重要的一项。写代码,可以出Bug,但逼格不能弱。什么是逼格?和别人对接一下,连加密解密都没有,连验证签名都没有,别人一眼就望穿你,这就是眼界的问题了。

    这次的故事是对接一个大的支付系统,对方也是第一个对接我们,然后定了接口和加解密算法,给了个Java的Demo,问了声,有没有PHP的,没有,歇菜,自己来吧。
    代码说多不多,说少不少,为了先说事,代码放在最后面。

    第一个坑:加密算法多多,你到底要闹咋样?

    码农兄弟们可以先下去看一眼代码,然后说说它用了啥算法?
    接口传输的安全性算法,首先要区分是加签名还是加密?区别是,签名+原文可以验证收到的信息是否被篡改,不过别指望有了签名就可以还原出原文来。加密就是不让别人看到原文是啥,然后给把钥匙,要让接收的人解密看出原文来。两者的算法基本上来说,就是完全不同的。

    加密还分对称非对称。对称有:DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等,非对称有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)

    还有,你以为拿个公钥就够了?当然不是,还要一对。更坑的是,可能有不同的格式。对方给了我一个keystore格式的,发觉php完全不支持,想办法转成了pem格式的。

    常见的密钥格式:jks,jce,p12,pfx,bks,ubr等等
    常见的证书文件格式:cer,crt,rsa,p7b,p7r,p7c,pem,p10,csr等等

    最后还有一点,这次碰到的算法具体的参数。
    我这次遇到的是3DES加密,`AlGORITHM = "DESede/ECB/PKCS5Padding";`,后面的类型和填充方式都不能差一点。

    第二个坑:到底什么是密钥?

    你会说这个很简单啊
    Java里就是像这样: PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray()); 
    php里就是像这样: $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath)); 
    你以为你以为的就是你以为的吗?前面说了,即使算法一样,密钥格式不一样,开发语言一样,用法也完全不一样。

    上面的只是格式不同,下面的还有编码的不同:
    看起来我从代码里读出的密码是这个,但其实送入算法中的可不是,还要做一个Base64转换,如果你送入了错误的,会永远困在迷惘中。

     1     $this->dESCORPKey = C('lakala_encrypt_key');
     2     $key = $this->$dESCORPKey;
     3     $encryptData = self::encrypt($key, $signedReq);
     4     ...
     5     public function encrypt($key,$data){
     6         $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
     7         $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
     8         $encData = base64_encode($encData);
     9         return $encData;
    10     }

    PS:网上有在线加密解密算法的工具,往往和代码出来的结果不一致,除了各种参数都需要写对以外,特别注意密码(密钥)的输入格式,要不要Base64编码或者解码。

    第三个坑:带中文的字符串格式


    看一眼下面的代码,你就会知道,php里有很多json_encode,json_decode,java代码里有很多getByte()这样转换成字节的操作,一个经典的问题就来了,你如果传入了中文,中文按什么格式进行编码?编码不同,决定了加密算法操作时二进制的不同,也决定了最终输出的不同。
    在写代码的时候查阅了java的getByte()方法,默认值竟然是读取机器的字符格式!!!所以,写代码的时候一定要注意,最好加上具体格式,不要用默认值,要这么写:`getByte("UTF-8")`,否则换了机器表现不一样,你会死的很难看。

    第四个坑:语言的问题


    虽然上帝说人人平等,但现实远远复杂的多。
    虽然加密解密本质上说就是一种运算变换,什么语言都可以实现的,可你不会从底层开始搞的,那就涉及语言的问题。
    最简单的例子:java里具体运算时都会把String转换成byte进行操作。PHP就不行。

    加密算法这个玩意虽然复杂,不过前人已经给我们造了很多轮子了,网上随便一搜就可以找到很多资料,问题是没有成系统,特别各个语言间的轮子差异还蛮大的。如何做适配,在网上的资料非常的难找。

    PHP里加密方法还有mcrypt和openssl两套,互相之间的写法还差异蛮大的,让人头疼。

    举个栗子(这里的Java解析使用了fastjson):

    1 java中:
    2 CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);
    3 PHP中:
    4 $req = json_decode(trim($jsonStr), true);
    5 ksort($req);

    看起来很像了吧?才不是呢!以下是输入的Json

    { 
    "head": { 
    "serviceSn": "B5D79F38B96040B7B992B6BE329D9975", 
    "serviceId": "MPBF001", 
    "channelId": "05", 
    "inputSource": "I002", 
    "opId": "", 
    "requestTime": "20180628142105", 
    "versionId": "1.0.0", 
    "businessChannel": "LKLZFLLF" 
    }, 
    "request": {
    "userId":"40012345678",
    "userName": "AA", 
    "userMobile": "18675529912", 
    "idNo": "110101198609096078" 
    } 
    }

    问题出在具体的类型定义,以及Json解析的深度
    JAVA中有定义具体按哪个类型解析

    1 public class CommReq implements Serializable {
    2 private static final long serialVersionUID = 1L;
    3 private CommReqHead head;
    4 private String request;
    5 }

    JAVA这种强类型语言,必须定义类型,将request定义成String型,导致JSON解析出来的结果:
    "request":"{"userId":"40012345678","userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } ";
    而PHP是弱类型语言,直接嵌套进去也解析出来了
    "request": {"userId":"40012345678","userName": "AA", "userMobile": "18675529912", "idNo": "110101198609096078" } }
    如果PHP要和JAVA真正保持一致(因为一旦不一致,加密结果就不一样了)

    1 $req = json_decode(trim($jsonStr), true);
    2 ksort($req);
    3 req['request']=json_encode(req['request']);

    前面也提到了密钥类型的问题,其实也和语言有关,PHP只支持PEM格式,所以,还用工具对keystore进行了转换,转换之后发现几个密码都已经不需要了。生成公钥PEM和私钥PEM加上加密解密Key就可以了。

    小结

    回顾来看,其实只是解决了很小的一个问题,将一段JAVA代码转换成了PHP代码,甚至中间复杂的算法细节都调用原来就有的模块,更不用怀疑这些模块写的算法的正确性,但调试这样一个东西,却的的确确花费了非常大的精力。技术没有任何中间地带,只有行或者不行,容不得半分作假。开发必须要注重细节,细节到位了才不会出Bug,这点在加密解密这件事上,尤其的明显,输入差一个字符,输出完全不同。开发没有很容易的事,只有我做过,我熟悉的事。

    代码在这里哦。钥匙就不提供了,这样直接copy代码跑不起来是正常的。哈哈

    # JAVA

      1 /**
      2  * 
      3  */
      4 package com.chuangmi.foundation.lakala.service.impl;
      5 
      6 import java.io.BufferedReader;
      7 import java.io.FileInputStream;
      8 import java.io.IOException;
      9 import java.io.InputStream;
     10 import java.io.UnsupportedEncodingException;
     11 import java.security.KeyStore;
     12 import java.security.PrivateKey;
     13 import java.security.PublicKey;
     14 import java.security.Security;
     15 import java.security.Signature;
     16 
     17 import javax.annotation.PostConstruct;
     18 import javax.crypto.Cipher;
     19 import javax.crypto.SecretKey;
     20 import javax.crypto.spec.SecretKeySpec;
     21 import javax.security.cert.X509Certificate;
     22 import javax.servlet.http.HttpServletRequest;
     23 
     24 import org.apache.commons.codec.binary.Base64;
     25 import org.bouncycastle.jce.provider.BouncyCastleProvider;
     26 import org.slf4j.Logger;
     27 import org.slf4j.LoggerFactory;
     28 import org.springframework.beans.factory.annotation.Value;
     29 import org.springframework.stereotype.Service;
     30 
     31 import com.alibaba.fastjson.JSON;
     32 import com.alibaba.fastjson.JSONException;
     33 import com.alibaba.fastjson.JSONObject;
     34 import com.alibaba.fastjson.parser.Feature;
     35 import com.chuangmi.foundation.lakala.service.SignService;
     36 import com.chuangmi.foundation.lakala.service.models.CommReq;
     37 import com.chuangmi.foundation.lakala.service.models.CommRequest;
     38 import com.chuangmi.foundation.lakala.service.models.CommRes;
     39 import com.chuangmi.foundation.lakala.service.models.CommResponse;
     40 
     41 
     42 @Service
     43 public class SignServiceImpl implements SignService {
     44     
     45     private static final Logger logger = LoggerFactory.getLogger(SignService.class);
     46     
     47     @Value("${cer.filePath}")
     48     private String cerFilePath;
     49 
     50     @Value("${key.filePath}")
     51     private String keyFilePath;
     52 
     53     @Value("${key.passWord}")
     54     private String keyPassWord;
     55     
     56     @Value("${key.alias}")
     57     private String alias;
     58     
     59     @Value("${encrypt.key}")
     60     private String dESCORPKey;
     61     
     62     /**
     63      * 加密算法与填充方式
     64      */
     65     public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; // 定义加密算法,可用
     66     
     67     @PostConstruct   
     68     public void init(){  
     69         if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){
     70 
     71             System.out.println("security provider BC not found, will add provider");
     72     
     73             Security.addProvider(new BouncyCastleProvider());
     74 
     75         }
     76     }
     77 
     78     /**
     79      * 加签并加密需要发送的请求报文
     80      * @param 接口报文
     81      * @return 加签后可以发送的json密文
     82      * @throws JSONException 
     83      */
     84     @Override
     85     public String signRequestJsonToSend(String jsonStr) throws JSONException {
     86         JSONObject resultObj = null;
     87         if(jsonStr.indexOf(""head"") >= 0)
     88         {
     89             CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);
     90             // 对报文体签名
     91             String signData = signData(req.getRequest());
     92             
     93             logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
     94             
     95             req.getHead().setSignData(signData);
     96             resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
     97         }
     98         else if(jsonStr.indexOf(""comm"") >= 0)
     99         {
    100             CommRequest req = JSON.parseObject(jsonStr, CommRequest.class, Feature.OrderedField);
    101             // 对报文体签名
    102             String signData = signData(req.getData());
    103             
    104             logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
    105             
    106             req.getComm().setSigntx(signData);
    107             resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
    108         }
    109         
    110         String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
    111         logger.info("加签后的报文:" + signedReq);
    112         
    113         //加密
    114         byte[] key = Base64.decodeBase64(dESCORPKey);
    115         byte[] encryptData = encrypt(key, signedReq.getBytes());
    116         
    117         String encryptStr = Base64.encodeBase64String(encryptData);
    118         
    119         logger.info("加密成功,密文:" + encryptStr);
    120         
    121         return encryptStr;
    122     }
    123     
    124     /**
    125      * 加签并加密需要发送的响应报文
    126      * @param 接口报文
    127      * @return 加签后可以发送的json密文
    128      * @throws JSONException 
    129      */
    130     @Override
    131     public String signResponseJsonToSend(String jsonStr) throws JSONException {
    132         JSONObject resultObj = null;
    133         if(jsonStr.indexOf(""head"") >= 0)
    134         {
    135             CommRes response = JSON.parseObject(jsonStr, CommRes.class, Feature.OrderedField);
    136             resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
    137         }
    138         else if(jsonStr.indexOf(""comm"") >= 0)
    139         {
    140             CommResponse response = JSON.parseObject(jsonStr, CommResponse.class, Feature.OrderedField);
    141             // 对报文体签名
    142             String signData = signData(response.getData());
    143             
    144             logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
    145             
    146             response.getComm().setSigntx(signData);
    147             resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
    148         }
    149         
    150         String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
    151         logger.info("加签后的响应报文:" + signedReq);
    152         
    153         //加密
    154         byte[] key = Base64.decodeBase64(dESCORPKey);
    155         byte[] encryptData = encrypt(key, signedReq.getBytes());
    156         
    157         String encryptStr = Base64.encodeBase64String(encryptData);
    158         
    159         logger.info("加密成功的响应报文,密文:" + encryptStr);
    160         
    161         return encryptStr;
    162     }
    163 
    164     /**
    165      * 从request提取json data,并解密,验签, 验签成功则返回data,否则返回null
    166      * @param request
    167      * @return
    168      * @throws IOException
    169      * @throws JSONException
    170      */
    171     @Override
    172     public String verifyReceivedRequest(HttpServletRequest request) throws IOException, JSONException {
    173         String json = extractJson(request);
    174         logger.info("接收报文密文:" + json);
    175         
    176         return verifyRequestJson(json);
    177     }
    178 
    179     /**
    180      * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
    181      * @param json
    182      * @return
    183      * @throws UnsupportedEncodingException
    184      * @throws JSONException
    185      */
    186     @Override
    187     public String verifyRequestJson(String json) throws UnsupportedEncodingException, JSONException {
    188         //解密
    189         byte[] key = Base64.decodeBase64(dESCORPKey);
    190         byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
    191         String orig = new String(decryptBytes, "UTF-8");
    192         logger.info("【收到的报文请求】接收报文:" + orig);
    193         
    194         // 验签
    195         JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
    196         String reqStr = String.valueOf(JSONObject.toJSON(obj));
    197         if(reqStr.indexOf(""comm"") >= 0)
    198         {
    199             CommRequest req = JSON.parseObject(reqStr, CommRequest.class, Feature.OrderedField);
    200             String signtx = req.getComm().getSigntx();
    201             
    202             logger.info("报文中的签名:" + signtx);
    203             boolean nRet = verifyData(req.getData(), signtx);
    204             if(nRet)
    205             {
    206                 req.getComm().setSigntx("");
    207                 return JSON.toJSONString(req);
    208             }
    209             else
    210             {
    211                 return null;
    212             }
    213         }
    214         else if(reqStr.indexOf(""head"") >= 0)
    215         {
    216             CommReq req = JSON.parseObject(reqStr, CommReq.class, Feature.OrderedField);
    217             String signData = req.getHead().getSignData();
    218             
    219             logger.info("报文中的签名:" + signData);
    220             boolean nRet = verifyData(req.getRequest(), signData);
    221             if(nRet)
    222             {
    223                 req.getHead().setSignData("");
    224                 return JSON.toJSONString(req);
    225             }
    226             else
    227             {
    228                 return null;
    229             }
    230         }
    231         else
    232         {
    233             return null;
    234         }
    235     }
    236     
    237     /**
    238      * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
    239      * @param json
    240      * @return
    241      * @throws UnsupportedEncodingException
    242      * @throws JSONException
    243      */
    244     @Override
    245     public String verifyResponseJson(String json) throws UnsupportedEncodingException, JSONException {
    246         //解密
    247         byte[] key = Base64.decodeBase64(dESCORPKey);
    248         byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
    249         String orig = new String(decryptBytes, "UTF-8");
    250         logger.info("【收到的响应报文】报文:" + orig);
    251         
    252         // 验签
    253         JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
    254         String reqStr = String.valueOf(JSONObject.toJSON(obj));
    255         if(reqStr.indexOf(""comm"") >= 0)
    256         {
    257             CommResponse response = JSON.parseObject(reqStr, CommResponse.class, Feature.OrderedField);
    258             String signtx = response.getComm().getSigntx();
    259             
    260             logger.info("报文中的签名:" + signtx);
    261             boolean nRet = verifyData(response.getData(), signtx);
    262             if(nRet)
    263             {
    264                 response.getComm().setSigntx("");
    265                 return JSON.toJSONString(response);
    266             }
    267             else
    268             {
    269                 return null;
    270             }
    271         }
    272         else if(reqStr.indexOf(""head"") >= 0)
    273         {
    274             CommRes response = JSON.parseObject(reqStr, CommRes.class, Feature.OrderedField);
    275             return JSON.toJSONString(response);
    276         }
    277         else
    278         {
    279             return null;
    280         }
    281     }
    282     
    283     public String extractJson(HttpServletRequest request) throws IOException {
    284         //用于接收对方的jsonString
    285         StringBuilder jsonString = new StringBuilder();
    286         BufferedReader reader = request.getReader();
    287         try {
    288             String line;
    289             while ((line = reader.readLine()) != null) {
    290                 jsonString.append(line);
    291             }
    292         } finally {
    293             reader.close();
    294         }
    295         String data = jsonString.toString();
    296         return data;
    297     }
    298     
    299     /*  使用私钥签名,并返回密文
    300       * @param param  需要进行签名的数据
    301       * @return 签名
    302       */
    303     private String signData(String param) 
    304     {
    305         InputStream inputStream = null;
    306         try {
    307 
    308             String store_password = keyPassWord;// 密钥库密码
    309             String password = keyPassWord;// 私钥密码
    310             String keyAlias = alias;// 别名
    311             // a. 创建针对jks文件的输入流
    312 
    313             inputStream = new FileInputStream(keyFilePath);// CA 文件名 如: D://tmp/encrypt.jks
    314             // input = getClass().getClassLoader().getResourceAsStream(keyFile);
    315             // 如果制定classpath下面的证书文件
    316 
    317             // b. 创建KeyStore实例 (store_password密钥库密码)
    318             KeyStore keyStore = KeyStore.getInstance("JKS");
    319             keyStore.load(inputStream, store_password.toCharArray());
    320 
    321             // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
    322             PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());
    323 
    324             // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
    325             Signature dsa = Signature.getInstance("SHA1withRSA");
    326             // 加载加密散列码用的私钥
    327             dsa.initSign(privateKey);
    328             // 进行散列,对产生的散列码进行加密并返回
    329             byte[] p = param.getBytes();
    330             dsa.update(p);
    331             
    332             return Base64.encodeBase64String(dsa.sign());// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码
    333 
    334         } catch (Exception gse) {
    335 
    336             gse.printStackTrace();
    337             return null;
    338 
    339         } finally {
    340 
    341             try {
    342                 if (inputStream != null)
    343                     inputStream.close();// 判断
    344             } catch (Exception e) {
    345                 e.printStackTrace();
    346             }
    347         }
    348 
    349     } 
    350         
    351     /*  用公钥证书对字符串进行签名验证
    352       * @param urlParam  需要进行签名验证的数据
    353       * @param signParam 编码后的签名
    354       * @return 校验签名,true为正确 false为错误
    355       */
    356     private boolean verifyData(String urlParam, String signParam) 
    357     {
    358         boolean verifies = false;
    359 
    360         InputStream in = null;
    361 
    362         try {
    363 
    364             // a. 创建针对cer文件的输入流
    365             InputStream inputStream = new FileInputStream(cerFilePath);// CA 文件名 如: D://tmp/cerfile.p7b
    366             // input = getClass().getClassLoader().getResourceAsStream(keyFile);
    367             // 如果制定classpath下面的证书文件
    368 
    369             // b. 创建KeyStore实例 (store_password密钥库密码)
    370             X509Certificate cert = X509Certificate.getInstance(inputStream);
    371 
    372             // c. 获取公钥 (keyAlias 为公钥别名)
    373             PublicKey pubKey = cert.getPublicKey();
    374 
    375             if (pubKey != null) {
    376                 // d. 公钥进行验签
    377                 // 获取Signature实例,指定签名算法(与之前一致)
    378                 Signature dsa = Signature.getInstance("SHA1withRSA");
    379                 // 加载公钥
    380                 dsa.initVerify(pubKey);
    381                 // 更新原数据
    382                 dsa.update(urlParam.getBytes());
    383 
    384                 // 公钥验签(true-验签通过;false-验签失败)
    385                 verifies = dsa.verify(Base64.decodeBase64(signParam));// 将签名数据从base64编码字符串转回字节数组
    386             }
    387 
    388         } catch (Exception gse) {
    389             gse.printStackTrace();
    390         } finally {
    391 
    392             try {
    393                 if (in != null)
    394                     in.close();// 判断
    395             } catch (Exception e) {
    396                 e.printStackTrace();
    397             }
    398         }
    399         return verifies;
    400 
    401     }
    402     
    403     
    404     // DES,DESede,Blowfish
    405     /**
    406      * 使用3des加密明文
    407      * 
    408      * @param byte[] key: 密钥
    409      * @param byte[] src: 明文
    410      * 
    411      */
    412     private byte[] encrypt(byte[] key, byte[] src) {
    413         try {
    414             
    415             if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){
    416 
    417                 System.out.println("security provider BC not found, will add provider");
    418         
    419                 Security.addProvider(new BouncyCastleProvider());
    420 
    421             }
    422             
    423             // 生成密钥
    424             SecretKey deskey = new SecretKeySpec(key, AlGORITHM);
    425             // 加密
    426             Cipher c1 = Cipher.getInstance(AlGORITHM);
    427             c1.init(Cipher.ENCRYPT_MODE, deskey);
    428             return c1.doFinal(src);// 在单一方面的加密或解密
    429         } catch (java.security.NoSuchAlgorithmException e1) {
    430             e1.printStackTrace();
    431         } catch (javax.crypto.NoSuchPaddingException e2) {
    432             e2.printStackTrace();
    433         } catch (java.lang.Exception e3) {
    434             e3.printStackTrace();
    435         }
    436         return null;
    437     }
    438 
    439     /**
    440      * 使用3des解密密文
    441      * 
    442      * @param byte[] key: 密钥
    443      * @param byte[] src: 密文
    444      * 
    445      */
    446     private byte[] decrypt(byte[] keybyte, byte[] src) {
    447         try {
    448             
    449             if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){
    450 
    451                 System.out.println("security provider BC not found, will add provider");
    452         
    453                 Security.addProvider(new BouncyCastleProvider());
    454 
    455             }
    456             
    457             // 生成密钥
    458             SecretKey deskey = new SecretKeySpec(keybyte, AlGORITHM);
    459             // 解密
    460             Cipher c1 = Cipher.getInstance(AlGORITHM);
    461             c1.init(Cipher.DECRYPT_MODE, deskey);
    462             return c1.doFinal(src);
    463         } catch (java.security.NoSuchAlgorithmException e1) {
    464             e1.printStackTrace();
    465         } catch (javax.crypto.NoSuchPaddingException e2) {
    466             e2.printStackTrace();
    467         } catch (java.lang.Exception e3) {
    468             e3.printStackTrace();
    469         }
    470 
    471         return null;
    472     }
    473     
    474     public static void main(String[] args) throws JSONException {
    475         
    476         InputStream inputStream = null;
    477         try {
    478 
    479             String store_password = "123456";// 密钥库密码
    480             String password = "123456";// 私钥密码
    481             String keyAlias = "www.lakala.com";// 别名
    482             // a. 创建针对jks文件的输入流
    483 
    484             inputStream = new FileInputStream("/Users/rinson/eclipse-workspace/lakala/lakala-server/asdc.keystore");// CA 文件名 如: D://tmp/encrypt.jks
    485             // input = getClass().getClassLoader().getResourceAsStream(keyFile);
    486             // 如果制定classpath下面的证书文件
    487 
    488             // b. 创建KeyStore实例 (store_password密钥库密码)
    489             KeyStore keyStore = KeyStore.getInstance("JKS");
    490             keyStore.load(inputStream, store_password.toCharArray());
    491 
    492             // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
    493             PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());
    494 
    495             // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
    496             Signature dsa = Signature.getInstance("SHA1withRSA");
    497             // 加载加密散列码用的私钥
    498             dsa.initSign(privateKey);
    499             String param = "XXXXX";
    500             // 进行散列,对产生的散列码进行加密并返回
    501             dsa.update(param .getBytes());
    502             
    503             System.out.println(Base64.encodeBase64String(dsa.sign()));// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码
    504 
    505         } catch (Exception gse) {
    506 
    507             gse.printStackTrace();
    508 
    509         } finally {
    510 
    511             try {
    512                 if (inputStream != null)
    513                     inputStream.close();// 判断
    514             } catch (Exception e) {
    515                 e.printStackTrace();
    516             }
    517         }
    518     }
    519 }
    View Code

    # PHP

      1 <?php
      2 
      3 namespace CommonLibLakala;
      4 
      5 class SignService
      6 {
      7     private $publicPemFilePath='';
      8     private $privatePemFilePath='';
      9     private $dESCORPKey = '';
     10 
     11     //初始化
     12     public function __construct()
     13     {
     14         $this->publicPemFilePath = C('lakala_cer_filePath');
     15         $this->privatePemFilePath=C('lakala_key_filePath');
     16         $this->dESCORPKey = C('lakala_encrypt_key');
     17     }
     18 
     19     /**
     20      * 加签并加密需要发送的请求报文
     21      * @param $head 是java类CommReqHead,需在调用时传入
     22      * @param $data 是具体的请求参数数组
     23      * 
     24      */
     25     public function ToLakala($head,$data,$url){
     26         //CommReq
     27         $ret = [
     28             'head'=>$head,
     29             'request'=>$data,
     30         ];
     31         $ret = json_encode($ret);
     32         //会对整个请求body加签加密,返回字符串body体
     33         $params = $this->signRequestJsonToSend($ret);
     34         
     35         //http request
     36         $ch = curl_init($url);
     37         curl_setopt($ch, CURLOPT_POST, 1);
     38         curl_setopt($ch, CURLOPT_HEADER, 0);
     39         curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
     40         curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
     41         curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
     42         curl_setopt($ch, CURLOPT_TIMEOUT, 30);
     43         curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($data)));
     44         curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
     45         $result = curl_exec($ch);
     46         curl_close($ch);
     47         $result = $params;
     48         //验证返回结果
     49         $response = $this->verifyResponseJson($result);
     50         //结果返回
     51         return $response;
     52     }
     53 
     54 
     55     public function FromLakala(){
     56         $lakalaSign = new SignService();
     57         $params = I('');
     58         $params = $this->verifyRequestJson($params);
     59         return $params;
     60     }
     61 
     62     public function FromLakalaResponse($head,$response){
     63         $ret = [
     64             'head'=>$head,
     65             'response'=>$response,
     66         ];
     67         $res = $this->signResponseJsonToSend($ret);
     68         return $res;
     69     }
     70 
     71     /**
     72      * 加签并加密需要发送的请求报文
     73      * @param $jsonStr 接口报文(String类型)
     74      * @return 加签后可以发送的json密文
     75      */
     76     public function signRequestJsonToSend($jsonStr)
     77     {    
     78         if(strpos($jsonStr,""head"")!= false)
     79         {
     80             $req = json_decode(trim($jsonStr), true);
     81             ksort($req);
     82             // 对报文体签名
     83             $signData = $this->signData($req['request']);
     84             $req['head']['signData']= $signData;
     85             $req['request']=json_encode($req['request'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
     86             ksort($req['head']);
     87             $resultObj = $req;
     88 
     89         }
     90         else if(strpos($jsonStr,""comm"")!= false)
     91         {
     92             $req = json_decode(trim($jsonStr), true);
     93             ksort($req);
     94             // 对报文体签名
     95             $signData = $this->signData($req['data']);
     96             $req['comm']['signtx']=$signData;
     97             $req['data']=json_encode($req['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
     98             ksort($req['head']);
     99             $resultObj = $req;
    100         }
    101 
    102         $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    103 
    104         //logger.info("加签后的报文:" + signedReq);
    105         //此处直接放入要加密的数据
    106         $key = $this->dESCORPKey;
    107         $encryptData = self::encrypt($key,$signedReq);
    108         //logger.info("加密成功,密文:" + encryptStr);
    109         
    110         return $encryptData;
    111     }
    112     
    113     /**
    114      * 加签并加密需要发送的响应报文
    115      * @param $jsonStr 接口报文(String类型)
    116      * @return 加签后可以发送的json密文
    117      */
    118     public function signResponseJsonToSend($jsonStr) {
    119         if(strpos($jsonStr,""head"")!= false)
    120         {
    121             $response = json_decode(trim($jsonStr), true);
    122             $resultObj = json_decode(json_encode($response));
    123         }
    124         else if(strpos($jsonStr,""comm"")!= false)
    125         {
    126             $response = json_decode(trim($jsonStr), true);
    127             ksort($response);
    128             // 对报文体签名
    129             $signData = $this->signData($response['data']);
    130             
    131             //logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
    132             $response['comm']['signTx']=$signData;
    133                $response['data']=json_encode($response['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    134             ksort($response['comm']);
    135             $resultObj = $response;
    136         }
    137         
    138         $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    139         //logger.info("加签后的响应报文:" + signedReq);
    140         
    141         $key = $this->$dESCORPKey;
    142         $encryptData = self::encrypt($key, $signedReq);
    143         
    144         //logger.info("加密成功的响应报文,密文:" + encryptStr);
    145         return $encryptData;
    146     }
    147 
    148     /**
    149      * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
    150      * @param json (String)
    151      * @return (String)
    152      */
    153     public function verifyResponseJson($json) {
    154         //解密
    155         $key = $this->dESCORPKey;
    156         $decryptBytes = self::decrypt($key, $json);
    157         
    158         //logger.info("【收到的响应报文】报文:" + orig);
    159         
    160         // 验签
    161         $obj = json_decode($decryptBytes);
    162         $reqStr = json_encode($obj);
    163         if(strpos($reqStr,""comm"")!= false)
    164         {
    165             $response = json_decode($reqStr,true);
    166             $signtx = $response['comm']['signtx'];
    167             
    168             //logger.info("报文中的签名:" + signtx);
    169             $nRet = $this->verifyData($response['data'], $signtx);
    170             if($nRet)
    171             {
    172                 $response['comm']['signtx']="";
    173                 return json_encode($response);
    174             }
    175             else
    176             {
    177                 return null;
    178             }
    179         }
    180         else if(strpos($reqStr,""head"")!= false)
    181         {
    182             return $reqStr;
    183         }
    184         else
    185         {
    186             return null;
    187         }
    188     }
    189 
    190     /**
    191      * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
    192      * @param json (String)
    193      * @return (String)
    194      */
    195     public function verifyRequestJson($json) {
    196         //解密
    197         $key = $this->dESCORPKey;
    198         $decryptBytes = self::decrypt($key, $json);
    199         
    200         // 验签
    201         $obj = json_decode($decryptBytes);
    202         $reqStr = json_encode($obj);
    203         if(strpos($reqStr,""comm"")!= false)
    204         {
    205             $req = json_decode($reqStr,true);
    206             ksort($req);
    207             $signtx = $req['comm']['signtx'];
    208             
    209             //logger.info("报文中的签名:" + signtx);
    210             $nRet = $this->verifyData($req['data'], $signtx);
    211             if($nRet)
    212             {
    213                 $req['comm']['signtx']="";
    214                 return json_encode($req);
    215             }
    216             else
    217             {
    218                 return null;
    219             }
    220         }
    221         else if(strpos($reqStr,""head"")!= false)
    222         {
    223             $req = json_decode($reqStr,true);
    224             ksort($req);
    225             $signData = $req['head']['signData'];
    226             //logger.info("报文中的签名:" + signData);
    227             $nRet = $this->verifyData($req['request'], $signData);
    228             return $nRet;
    229             if($nRet)
    230             {
    231                 $req['head']['signData']="";
    232                 return json_encode($req);
    233             }
    234             else
    235             {
    236                 return null;
    237             }
    238         }
    239         else
    240         {
    241             return null;
    242         }
    243     }
    244         /*  使用私钥签名,并返回密文
    245         * @param param  需要进行签名的数据(Array)
    246         * @return 签名(加签字符串String)
    247         */
    248     private function signData($param) 
    249     {    
    250         $content = json_encode($param,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
    251         $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath));
    252         openssl_sign($content, $signature, $privateKey);
    253         openssl_free_key($privateKey);
    254         return base64_encode($signature);  
    255 
    256     } 
    257 
    258     /*  用公钥证书对字符串进行签名验证
    259       * @param urlParam  需要进行签名验证的数据(直接从解密报文中取出的String)
    260       * @param signParam 编码后的签名(直接从解密报文中取出的String)
    261       * @return 校验签名,true为正确 false为错误
    262       */
    263     private function verifyData($urlParam,$signParam) 
    264     {
    265         $signature = base64_decode($signParam);
    266         $pubkeyid = openssl_pkey_get_public(file_get_contents($publicPemFilePath));
    267         // state whether signature is okay or not
    268         $verifies = openssl_verify($urlParam, $signature, $pubkeyid);
    269         return $verifies;
    270     }
    271 
    272     /**
    273      * @param $key获取的密钥字符串(直接从配置文件中取出)
    274      * @param $data (字符串)
    275      * @return string
    276      */
    277     public function encrypt($key,$data){
    278         $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
    279         $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
    280         $encData = base64_encode($encData);
    281         return $encData;
    282     }
    283 
    284     /**
    285      * @param $key获取的密钥字符串(直接从配置文件中取出)
    286      * @param $data (字符串)
    287      * @return string
    288      */
    289     public function decrypt($key,$data){
    290         $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
    291         $data = base64_decode($data);//此处需要BASE64解码(变为2进制)
    292         $decData = openssl_decrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
    293         return $decData;
    294     }
    295 
    296 }
    297 
    298 ?>
    View Code
  • 相关阅读:
    js 生成指定范围之内的随机数
    vue项目在ie浏览器打开做提示
    vue 瀑布流组件
    docker 移动文件到其他目录
    学习hyperf遇到的问题
    Linux 部署elasticsearch
    Git 常用命令
    Linux svn定时更新
    eclipse 导入web项目后,线程假死
    向量基本概念
  • 原文地址:https://www.cnblogs.com/rinson/p/9330764.html
Copyright © 2011-2022 走看看