签名与验签

本文介绍如何在半个小时之内生成调用到位平台接口的签名,及签名加密规则,实现轻松调用到位平台的相关接口。

RSA公私钥生成

一、签名计算基本设定
  1. 到位平台的所有接口中的所有参数的编码都是UTF-8,所以跟到位平台交互或者计算签名的时候字符编码都是UTF-8,暂时没有支持其他编码的打算,请开发者自行对于自己的代码编码进行转换。
  2. 请求接口的参数列表均为键值对,键名为字符串,键值也必须为字符串。
  3. 键值中如果为复杂数据类型,比如结构体、数组、对象都必须先转化成为字符串。
二、筛选进行签名的参数

参数名为rsaSign不需要参与签名。

三、对参数进行排序

将筛选的参数按照第一个字符的键值ASCII码递增排序(字母升序排序),如果遇到相同字符则按照第二个字符的键值ASCII码递增排序,以此类推。

四、对参数进行拼接组成待签名字符串
  1. 使用 = 和 & 将参数连接起来,将排序后的参数与其对应值,组合成“参数=参数值”的格式,并且把这些参数用&字符连接起来,此时生成的字符串为待签名字符串。
    amount=100&orderId=2017011215064442155179691603&serviceId=304f5ea4f3a74eec8e2cd7ff0b668628&userId=e285290a152f4e05a71058c48899b622
  2. 使用各自语言对应的SHA1WithRSA签名生成函数(如php: openssl_sign),传入待签名字符串、业务方私钥,由SHA1算法中得出sign,然后base64encode。示例生成的签名为:
    rsaSign=AxDNwRHnyqgaJ/r7vApS4cw0kqwEqyZMyhfIua9kg7RrZhtCyrtLxc2U0pRV6HZBBA6y17AreOWxdrGbJX2Nj9r6mLrCXPXk1ZCLvs+laK561K4Ssh7sgTzE72NerbAQQwThhyI6Fxy6oISNS29HjUzb52nMfQuYqb7+tpN0UaU=
五、JAVA相关的签名生成的参考代码

1、JAVA生成签名的工具类

import java.io.ByteArrayInputStream;  
import java.io.InputStream;  
import java.security.KeyFactory;  
import java.security.PrivateKey;  
import java.security.PublicKey;  
import java.security.spec.PKCS8EncodedKeySpec;  
import java.security.spec.X509EncodedKeySpec;  
import java.util.ArrayList;  
import java.util.Collections;  
import java.util.Enumeration;  
import java.util.HashMap;  
import java.util.List;  
import java.util.Map;

import javax.servlet.ServletRequest;  
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.binary.Base64;  
import org.apache.commons.io.IOUtils;  
import org.apache.commons.lang.StringUtils;

public class SignatureUtils {  
    /**
     * 生成签名
     * 
     * @param sortedParams
     * @param privateKey
     * @return
     */
    public static String genSign(Map<String, String> sortedParams, String privateKey) {
        String sortedParamsContent = getSignContent(sortedParams);
        return rsaSign(sortedParamsContent, privateKey, "UTF-8");
    }

    /**
     * 验证签名
     * 
     * @param param
     * @param publicKey
     * @return
     */
    public static boolean checkSign(Map<String, String> param, String publicKey) {
        if (param == null || param.isEmpty()) {
            return false;
        }
        String sign = param.get("sign");
        if (StringUtils.isEmpty(sign)) {
            return false;
        }
        if (param.containsKey("sign")) {
            param.remove("sign");
        }
        String content = getSignContent(param);
        return doCheck(content, sign, publicKey);
    }

    /**
     * 
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        List<String> keys = new ArrayList<String>(sortedParams.keySet());
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = sortedParams.get(key);
            if (StringUtils.isNotEmpty(key) && StringUtils.isNotEmpty(value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }
        }
        return content.toString();
    }

    /**
     * sha1WithRsa 加签
     * @param content
     * @param privateKey
     * @param charset
     * @return
     * @throws DaowayApiException
     */
    public static String rsaSign(String content, String privateKey, String charset) {
        try {
            PrivateKey priKey = getPrivateKeyFromPKCS8("RSA", new ByteArrayInputStream(privateKey.getBytes()));
            java.security.Signature signature = java.security.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) {
            System.out.println(e);
            return null;
        }
    }

    public static boolean doCheck(String content, String sign, String publicKey) {
        try {
            KeyFactory keyFactory = KeyFactory.getInstance("RSA");

            byte[] encodedKey = Base64.decodeBase64(publicKey);
            PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));

            java.security.Signature signature = java.security.Signature.getInstance("SHA1WithRSA");

            signature.initVerify(pubKey);
            signature.update(content.getBytes("UTF-8"));

            boolean bverify = signature.verify(Base64.decodeBase64(sign));
            return bverify;
        } catch (Exception e) {
            return false;
        }
    }

    public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
        if (ins == null || StringUtils.isEmpty(algorithm)) {
            return null;
        }
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
        byte[] encodedKey = IOUtils.toByteArray(ins);
        encodedKey = Base64.decodeBase64(encodedKey);
        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
    }
}

2、调用JAVA签名生成类生成签名的参考代码

/**
 * 第一部分:赋值私钥
 * @notice1:因为JAVA中的字节流、字符流、双字符流等概念非常复杂,建议使用常量存储私钥文件
 * @notice2:私钥文件为不换行且不带私钥开头和结尾的字符串
 */

String PRIVATE_KEY = "MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAM8vojS3eWYEnB0Xl73+8+D/xdzeWTvbZc0SPO6nKmm3WxBYX/fFI6S7DhhK0QAUKjbSD4hDyqLkgy8azi8ETvYSYIoTjdR55nNklCNQ9RAtPVeuAAFzN0h2DmfY3/F7FsUFg9Qd/9YaGpU+CEnZDThjvWxBg22HvlN09xKfAYltAgMBAAECgYAr8wDHifv4hhXPngeUSBbXran9NjVbmyi3HZ1LSq6WikyI5RZGas0qznso8AXxrFVgF6Mv1qGPeEXToi4GjzVoX5ocfUoSlqE5xmhdfmc4aqKz/BlncCVlgNnlQEp5oHpGiIzVEpabC4OiBMRAhi/Brvu14GOUkP1VEZmfuCQCCQJBAPKytmmzznsDaiO15AeorPi/nUNDMLoOoiFwZgUxXWW7PI+uZq1ja5NpMjuRu3eVt3dFexB7x+ZnBb9tWTGQtDMCQQDaiqc4vR1eiSpVMf+rB6+Xbj+dDrtoTaH66YrBKXE5tbWPlsm1MWWpmDREFntU+f3yAQqjgVAtCULmp8odkCvfAkEAge9aJ+dDIarnVW0ZQ1x0Fs0Hli5P1Rzmgn6ZsCgIt+Fxf/9AK44x1v8YDLpuIoz+Z5XEWEPc9yaq9hzGBvpQ7wJAErDLDnI2IdCvWyv0hscYgGYAcMlCw+/ny5LPuCd4NIxS493skF+SJ0gKKEyX7bOXwWvPYh58Ie3p19o/0flzlwJBAJ8Ut/aPdzIFIlvR8BdQ7O/6BCf2490vWjNrzu+TOWCEeEM4IMfgXSg3chhExJg8TXwU0IbiB5fnDeIreWbPPWY=";


/**
 * 第二部分:计算签名
 */
/**
 * 1.通过方法获取到所有需要需要参与签名的参数HashMap
 */
HashMap params = new HashMap();


/**
 * 2.从常量中读取privateKey,然后计算RSA签名
 */
String rsaSign = SignatureUtils.genSign(params,PRIVATE_KEY);