Karp 的技术博客

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

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

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

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

加密还分对称非对称。对称有:DES3DESTDEABlowfishRC2RC4RC5IDEASKIPJACKAES等,非对称有:RSAElgamal背包算法RabinD-HECC(椭圆曲线加密算法)

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

常见的密钥格式:jksjcep12pfxbksubr等等
常见的证书文件格式:cer,crtrsap7b,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转换,如果你送入了错误的,会永远困在迷惘中。

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

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

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

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

第四个坑:语言的问题

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

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

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

举个栗子(这里的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代码跑不起来是正常的。哈哈

/**
 *
 */
package com.chuangmi.foundation.lakala.service.impl;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;

import javax.annotation.PostConstruct;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.security.cert.X509Certificate;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.chuangmi.foundation.lakala.service.SignService;
import com.chuangmi.foundation.lakala.service.models.CommReq;
import com.chuangmi.foundation.lakala.service.models.CommRequest;
import com.chuangmi.foundation.lakala.service.models.CommRes;
import com.chuangmi.foundation.lakala.service.models.CommResponse;


@Service
public class SignServiceImpl implements SignService {

    private static final Logger logger = LoggerFactory.getLogger(SignService.class);

    @Value("${cer.filePath}")
    private String cerFilePath;

    @Value("${key.filePath}")
    private String keyFilePath;

    @Value("${key.passWord}")
    private String keyPassWord;

    @Value("${key.alias}")
    private String alias;

    @Value("${encrypt.key}")
    private String dESCORPKey;

    /**
     * 加密算法与填充方式
     */
    public static final String AlGORITHM = "DESede/ECB/PKCS5Padding"; // 定义加密算法,可用

    @PostConstruct
    public void init(){
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){

            System.out.println("security provider BC not found, will add provider");

            Security.addProvider(new BouncyCastleProvider());

        }
    }

    /**
     * 加签并加密需要发送的请求报文
     * @param 接口报文
     * @return 加签后可以发送的json密文
     * @throws JSONException
     */
    @Override
    public String signRequestJsonToSend(String jsonStr) throws JSONException {
        JSONObject resultObj = null;
        if(jsonStr.indexOf("\"head\"") >= 0)
        {
            CommReq req = JSON.parseObject(jsonStr, CommReq.class, Feature.OrderedField);
            // 对报文体签名
            String signData = signData(req.getRequest());

            logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);

            req.getHead().setSignData(signData);
            resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
        }
        else if(jsonStr.indexOf("\"comm\"") >= 0)
        {
            CommRequest req = JSON.parseObject(jsonStr, CommRequest.class, Feature.OrderedField);
            // 对报文体签名
            String signData = signData(req.getData());

            logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);

            req.getComm().setSigntx(signData);
            resultObj = JSONObject.parseObject(JSON.toJSONString(req), Feature.OrderedField);
        }

        String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
        logger.info("加签后的报文:" + signedReq);

        //加密
        byte[] key = Base64.decodeBase64(dESCORPKey);
        byte[] encryptData = encrypt(key, signedReq.getBytes());

        String encryptStr = Base64.encodeBase64String(encryptData);

        logger.info("加密成功,密文:" + encryptStr);

        return encryptStr;
    }

    /**
     * 加签并加密需要发送的响应报文
     * @param 接口报文
     * @return 加签后可以发送的json密文
     * @throws JSONException
     */
    @Override
    public String signResponseJsonToSend(String jsonStr) throws JSONException {
        JSONObject resultObj = null;
        if(jsonStr.indexOf("\"head\"") >= 0)
        {
            CommRes response = JSON.parseObject(jsonStr, CommRes.class, Feature.OrderedField);
            resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
        }
        else if(jsonStr.indexOf("\"comm\"") >= 0)
        {
            CommResponse response = JSON.parseObject(jsonStr, CommResponse.class, Feature.OrderedField);
            // 对报文体签名
            String signData = signData(response.getData());

            logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);

            response.getComm().setSigntx(signData);
            resultObj = JSONObject.parseObject(JSON.toJSONString(response), Feature.OrderedField);
        }

        String signedReq = String.valueOf(JSONObject.toJSON(resultObj));
        logger.info("加签后的响应报文:" + signedReq);

        //加密
        byte[] key = Base64.decodeBase64(dESCORPKey);
        byte[] encryptData = encrypt(key, signedReq.getBytes());

        String encryptStr = Base64.encodeBase64String(encryptData);

        logger.info("加密成功的响应报文,密文:" + encryptStr);

        return encryptStr;
    }

    /**
     * 从request提取json data,并解密,验签, 验签成功则返回data,否则返回null
     * @param request
     * @return
     * @throws IOException
     * @throws JSONException
     */
    @Override
    public String verifyReceivedRequest(HttpServletRequest request) throws IOException, JSONException {
        String json = extractJson(request);
        logger.info("接收报文密文:" + json);

        return verifyRequestJson(json);
    }

    /**
     * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
     * @param json
     * @return
     * @throws UnsupportedEncodingException
     * @throws JSONException
     */
    @Override
    public String verifyRequestJson(String json) throws UnsupportedEncodingException, JSONException {
        //解密
        byte[] key = Base64.decodeBase64(dESCORPKey);
        byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
        String orig = new String(decryptBytes, "UTF-8");
        logger.info("【收到的报文请求】接收报文:" + orig);

        // 验签
        JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
        String reqStr = String.valueOf(JSONObject.toJSON(obj));
        if(reqStr.indexOf("\"comm\"") >= 0)
        {
            CommRequest req = JSON.parseObject(reqStr, CommRequest.class, Feature.OrderedField);
            String signtx = req.getComm().getSigntx();

            logger.info("报文中的签名:" + signtx);
            boolean nRet = verifyData(req.getData(), signtx);
            if(nRet)
            {
                req.getComm().setSigntx("");
                return JSON.toJSONString(req);
            }
            else
            {
                return null;
            }
        }
        else if(reqStr.indexOf("\"head\"") >= 0)
        {
            CommReq req = JSON.parseObject(reqStr, CommReq.class, Feature.OrderedField);
            String signData = req.getHead().getSignData();

            logger.info("报文中的签名:" + signData);
            boolean nRet = verifyData(req.getRequest(), signData);
            if(nRet)
            {
                req.getHead().setSignData("");
                return JSON.toJSONString(req);
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }

    /**
     * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
     * @param json
     * @return
     * @throws UnsupportedEncodingException
     * @throws JSONException
     */
    @Override
    public String verifyResponseJson(String json) throws UnsupportedEncodingException, JSONException {
        //解密
        byte[] key = Base64.decodeBase64(dESCORPKey);
        byte[] decryptBytes = decrypt(key, Base64.decodeBase64(json));
        String orig = new String(decryptBytes, "UTF-8");
        logger.info("【收到的响应报文】报文:" + orig);

        // 验签
        JSONObject obj = JSONObject.parseObject(orig, Feature.OrderedField);
        String reqStr = String.valueOf(JSONObject.toJSON(obj));
        if(reqStr.indexOf("\"comm\"") >= 0)
        {
            CommResponse response = JSON.parseObject(reqStr, CommResponse.class, Feature.OrderedField);
            String signtx = response.getComm().getSigntx();

            logger.info("报文中的签名:" + signtx);
            boolean nRet = verifyData(response.getData(), signtx);
            if(nRet)
            {
                response.getComm().setSigntx("");
                return JSON.toJSONString(response);
            }
            else
            {
                return null;
            }
        }
        else if(reqStr.indexOf("\"head\"") >= 0)
        {
            CommRes response = JSON.parseObject(reqStr, CommRes.class, Feature.OrderedField);
            return JSON.toJSONString(response);
        }
        else
        {
            return null;
        }
    }

    public String extractJson(HttpServletRequest request) throws IOException {
        //用于接收对方的jsonString
        StringBuilder jsonString = new StringBuilder();
        BufferedReader reader = request.getReader();
        try {
            String line;
            while ((line = reader.readLine()) != null) {
                jsonString.append(line);
            }
        } finally {
            reader.close();
        }
        String data = jsonString.toString();
        return data;
    }

    /*  使用私钥签名,并返回密文
      * @param param  需要进行签名的数据
      * @return 签名
      */
    private String signData(String param)
    {
        InputStream inputStream = null;
        try {

            String store_password = keyPassWord;// 密钥库密码
            String password = keyPassWord;// 私钥密码
            String keyAlias = alias;// 别名
            // a. 创建针对jks文件的输入流

            inputStream = new FileInputStream(keyFilePath);// CA 文件名 如: D://tmp/encrypt.jks
            // input = getClass().getClassLoader().getResourceAsStream(keyFile);
            // 如果制定classpath下面的证书文件

            // b. 创建KeyStore实例 (store_password密钥库密码)
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, store_password.toCharArray());

            // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());

            // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
            Signature dsa = Signature.getInstance("SHA1withRSA");
            // 加载加密散列码用的私钥
            dsa.initSign(privateKey);
            // 进行散列,对产生的散列码进行加密并返回
            byte[] p = param.getBytes();
            dsa.update(p);

            return Base64.encodeBase64String(dsa.sign());// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码

        } catch (Exception gse) {

            gse.printStackTrace();
            return null;

        } finally {

            try {
                if (inputStream != null)
                    inputStream.close();// 判断
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    /*  用公钥证书对字符串进行签名验证
      * @param urlParam  需要进行签名验证的数据
      * @param signParam 编码后的签名
      * @return 校验签名,true为正确 false为错误
      */
    private boolean verifyData(String urlParam, String signParam)
    {
        boolean verifies = false;

        InputStream in = null;

        try {

            // a. 创建针对cer文件的输入流
            InputStream inputStream = new FileInputStream(cerFilePath);// CA 文件名 如: D://tmp/cerfile.p7b
            // input = getClass().getClassLoader().getResourceAsStream(keyFile);
            // 如果制定classpath下面的证书文件

            // b. 创建KeyStore实例 (store_password密钥库密码)
            X509Certificate cert = X509Certificate.getInstance(inputStream);

            // c. 获取公钥 (keyAlias 为公钥别名)
            PublicKey pubKey = cert.getPublicKey();

            if (pubKey != null) {
                // d. 公钥进行验签
                // 获取Signature实例,指定签名算法(与之前一致)
                Signature dsa = Signature.getInstance("SHA1withRSA");
                // 加载公钥
                dsa.initVerify(pubKey);
                // 更新原数据
                dsa.update(urlParam.getBytes());

                // 公钥验签(true-验签通过;false-验签失败)
                verifies = dsa.verify(Base64.decodeBase64(signParam));// 将签名数据从base64编码字符串转回字节数组
            }

        } catch (Exception gse) {
            gse.printStackTrace();
        } finally {

            try {
                if (in != null)
                    in.close();// 判断
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return verifies;

    }


    // DES,DESede,Blowfish
    /**
     * 使用3des加密明文
     *
     * @param byte[] key: 密钥
     * @param byte[] src: 明文
     *
     */
    private byte[] encrypt(byte[] key, byte[] src) {
        try {

            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){

                System.out.println("security provider BC not found, will add provider");

                Security.addProvider(new BouncyCastleProvider());

            }

            // 生成密钥
            SecretKey deskey = new SecretKeySpec(key, AlGORITHM);
            // 加密
            Cipher c1 = Cipher.getInstance(AlGORITHM);
            c1.init(Cipher.ENCRYPT_MODE, deskey);
            return c1.doFinal(src);// 在单一方面的加密或解密
        } catch (java.security.NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (javax.crypto.NoSuchPaddingException e2) {
            e2.printStackTrace();
        } catch (java.lang.Exception e3) {
            e3.printStackTrace();
        }
        return null;
    }

    /**
     * 使用3des解密密文
     *
     * @param byte[] key: 密钥
     * @param byte[] src: 密文
     *
     */
    private byte[] decrypt(byte[] keybyte, byte[] src) {
        try {

            if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null){

                System.out.println("security provider BC not found, will add provider");

                Security.addProvider(new BouncyCastleProvider());

            }

            // 生成密钥
            SecretKey deskey = new SecretKeySpec(keybyte, AlGORITHM);
            // 解密
            Cipher c1 = Cipher.getInstance(AlGORITHM);
            c1.init(Cipher.DECRYPT_MODE, deskey);
            return c1.doFinal(src);
        } catch (java.security.NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (javax.crypto.NoSuchPaddingException e2) {
            e2.printStackTrace();
        } catch (java.lang.Exception e3) {
            e3.printStackTrace();
        }

        return null;
    }

    public static void main(String[] args) throws JSONException {

        InputStream inputStream = null;
        try {

            String store_password = "123456";// 密钥库密码
            String password = "123456";// 私钥密码
            String keyAlias = "www.lakala.com";// 别名
            // a. 创建针对jks文件的输入流

            inputStream = new FileInputStream("/Users/rinson/eclipse-workspace/lakala/lakala-server/asdc.keystore");// CA 文件名 如: D://tmp/encrypt.jks
            // input = getClass().getClassLoader().getResourceAsStream(keyFile);
            // 如果制定classpath下面的证书文件

            // b. 创建KeyStore实例 (store_password密钥库密码)
            KeyStore keyStore = KeyStore.getInstance("JKS");
            keyStore.load(inputStream, store_password.toCharArray());

            // c. 获取私钥 (keyAlias 为私钥别名,password为私钥密码)
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(keyAlias, password.toCharArray());

            // 实例化一个用SHA算法进行散列,用RSA算法进行加密的Signature.
            Signature dsa = Signature.getInstance("SHA1withRSA");
            // 加载加密散列码用的私钥
            dsa.initSign(privateKey);
            String param = "XXXXX";
            // 进行散列,对产生的散列码进行加密并返回
            dsa.update(param .getBytes());

            System.out.println(Base64.encodeBase64String(dsa.sign()));// 进行签名, 加密后的也是二进制的,但是返回给调用方是字符串,将byte[]转为base64编码

        } catch (Exception gse) {

            gse.printStackTrace();

        } finally {

            try {
                if (inputStream != null)
                    inputStream.close();// 判断
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

PHP

<?php

namespace Common\Lib\Lakala;

class SignService
{
    private $publicPemFilePath='';
    private $privatePemFilePath='';
    private $dESCORPKey = '';

    //初始化
    public function __construct()
    {
        $this->publicPemFilePath = C('lakala_cer_filePath');
        $this->privatePemFilePath=C('lakala_key_filePath');
        $this->dESCORPKey = C('lakala_encrypt_key');
    }

    /**
     * 加签并加密需要发送的请求报文
     * @param $head 是java类CommReqHead,需在调用时传入
     * @param $data 是具体的请求参数数组
     *
     */
    public function ToLakala($head,$data,$url){
        //CommReq
        $ret = [
            'head'=>$head,
            'request'=>$data,
        ];
        $ret = json_encode($ret);
        //会对整个请求body加签加密,返回字符串body体
        $params = $this->signRequestJsonToSend($ret);

        //http request
        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_POST, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, 30);
        curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/json; charset=utf-8', 'Content-Length: ' . strlen($data)));
        curl_setopt($ch, CURLOPT_POSTFIELDS, $params);
        $result = curl_exec($ch);
        curl_close($ch);
        $result = $params;
        //验证返回结果
        $response = $this->verifyResponseJson($result);
        //结果返回
        return $response;
    }


    public function FromLakala(){
        $lakalaSign = new SignService();
        $params = I('');
        $params = $this->verifyRequestJson($params);
        return $params;
    }

    public function FromLakalaResponse($head,$response){
        $ret = [
            'head'=>$head,
            'response'=>$response,
        ];
        $res = $this->signResponseJsonToSend($ret);
        return $res;
    }

    /**
     * 加签并加密需要发送的请求报文
     * @param $jsonStr 接口报文(String类型)
     * @return 加签后可以发送的json密文
     */
    public function signRequestJsonToSend($jsonStr)
    {
        if(strpos($jsonStr,"\"head\"")!= false)
        {
            $req = json_decode(trim($jsonStr), true);
            ksort($req);
            // 对报文体签名
            $signData = $this->signData($req['request']);
            $req['head']['signData']= $signData;
            $req['request']=json_encode($req['request'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
            ksort($req['head']);
            $resultObj = $req;

        }
        else if(strpos($jsonStr,"\"comm\"")!= false)
        {
            $req = json_decode(trim($jsonStr), true);
            ksort($req);
            // 对报文体签名
            $signData = $this->signData($req['data']);
            $req['comm']['signtx']=$signData;
            $req['data']=json_encode($req['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
            ksort($req['head']);
            $resultObj = $req;
        }

        $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);

        //logger.info("加签后的报文:" + signedReq);
        //此处直接放入要加密的数据
        $key = $this->dESCORPKey;
        $encryptData = self::encrypt($key,$signedReq);
        //logger.info("加密成功,密文:" + encryptStr);

        return $encryptData;
    }

    /**
     * 加签并加密需要发送的响应报文
     * @param $jsonStr 接口报文(String类型)
     * @return 加签后可以发送的json密文
     */
    public function signResponseJsonToSend($jsonStr) {
        if(strpos($jsonStr,"\"head\"")!= false)
        {
            $response = json_decode(trim($jsonStr), true);
            $resultObj = json_decode(json_encode($response));
        }
        else if(strpos($jsonStr,"\"comm\"")!= false)
        {
            $response = json_decode(trim($jsonStr), true);
            ksort($response);
            // 对报文体签名
            $signData = $this->signData($response['data']);

            //logger.info("加签成功,原始报文:" + jsonStr + ",报文体签名:" + signData);
            $response['comm']['signTx']=$signData;
               $response['data']=json_encode($response['data'],JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
            ksort($response['comm']);
            $resultObj = $response;
        }

        $signedReq = json_encode($resultObj,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        //logger.info("加签后的响应报文:" + signedReq);

        $key = $this->$dESCORPKey;
        $encryptData = self::encrypt($key, $signedReq);

        //logger.info("加密成功的响应报文,密文:" + encryptStr);
        return $encryptData;
    }

    /**
     * 对响应的报文json解密,验签, 验签成功则返回data,否则返回null
     * @param json (String)
     * @return (String)
     */
    public function verifyResponseJson($json) {
        //解密
        $key = $this->dESCORPKey;
        $decryptBytes = self::decrypt($key, $json);

        //logger.info("【收到的响应报文】报文:" + orig);

        // 验签
        $obj = json_decode($decryptBytes);
        $reqStr = json_encode($obj);
        if(strpos($reqStr,"\"comm\"")!= false)
        {
            $response = json_decode($reqStr,true);
            $signtx = $response['comm']['signtx'];

            //logger.info("报文中的签名:" + signtx);
            $nRet = $this->verifyData($response['data'], $signtx);
            if($nRet)
            {
                $response['comm']['signtx']="";
                return json_encode($response);
            }
            else
            {
                return null;
            }
        }
        else if(strpos($reqStr,"\"head\"")!= false)
        {
            return $reqStr;
        }
        else
        {
            return null;
        }
    }

    /**
     * 对收到的请求json解密,验签, 验签成功则返回data,否则返回null
     * @param json (String)
     * @return (String)
     */
    public function verifyRequestJson($json) {
        //解密
        $key = $this->dESCORPKey;
        $decryptBytes = self::decrypt($key, $json);

        // 验签
        $obj = json_decode($decryptBytes);
        $reqStr = json_encode($obj);
        if(strpos($reqStr,"\"comm\"")!= false)
        {
            $req = json_decode($reqStr,true);
            ksort($req);
            $signtx = $req['comm']['signtx'];

            //logger.info("报文中的签名:" + signtx);
            $nRet = $this->verifyData($req['data'], $signtx);
            if($nRet)
            {
                $req['comm']['signtx']="";
                return json_encode($req);
            }
            else
            {
                return null;
            }
        }
        else if(strpos($reqStr,"\"head\"")!= false)
        {
            $req = json_decode($reqStr,true);
            ksort($req);
            $signData = $req['head']['signData'];
            //logger.info("报文中的签名:" + signData);
            $nRet = $this->verifyData($req['request'], $signData);
            return $nRet;
            if($nRet)
            {
                $req['head']['signData']="";
                return json_encode($req);
            }
            else
            {
                return null;
            }
        }
        else
        {
            return null;
        }
    }
        /*  使用私钥签名,并返回密文
        * @param param  需要进行签名的数据(Array)
        * @return 签名(加签字符串String)
        */
    private function signData($param)
    {
        $content = json_encode($param,JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES);
        $privateKey=openssl_pkey_get_private(file_get_contents($this->privatePemFilePath));
        openssl_sign($content, $signature, $privateKey);
        openssl_free_key($privateKey);
        return base64_encode($signature);

    }

    /*  用公钥证书对字符串进行签名验证
      * @param urlParam  需要进行签名验证的数据(直接从解密报文中取出的String)
      * @param signParam 编码后的签名(直接从解密报文中取出的String)
      * @return 校验签名,true为正确 false为错误
      */
    private function verifyData($urlParam,$signParam)
    {
        $signature = base64_decode($signParam);
        $pubkeyid = openssl_pkey_get_public(file_get_contents($publicPemFilePath));
        // state whether signature is okay or not
        $verifies = openssl_verify($urlParam, $signature, $pubkeyid);
        return $verifies;
    }

    /**
     * @param $key获取的密钥字符串(直接从配置文件中取出)
     * @param $data (字符串)
     * @return string
     */
    public function encrypt($key,$data){
        $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
        $encData = openssl_encrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
        $encData = base64_encode($encData);
        return $encData;
    }

    /**
     * @param $key获取的密钥字符串(直接从配置文件中取出)
     * @param $data (字符串)
     * @return string
     */
    public function decrypt($key,$data){
        $decode_key = base64_decode($key);//此处需要BASE64解码(变为2进制)
        $data = base64_decode($data);//此处需要BASE64解码(变为2进制)
        $decData = openssl_decrypt($data, 'DES-EDE3', $decode_key, OPENSSL_RAW_DATA);
        return $decData;
    }

}

php

版权属于:karp
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
更新于: 2024年10月17日 08:48
9

目录

来自 《记一次Java加密加签算法到php的坑》
774 文章数
0 评论量
9 分类数
779 页面数
已在风雨中度过 9年279天5小时45分