蚂蚁金服是为什么支付签名使用RSA2?看加签验签代码

1,728 阅读3分钟

什么是数字签名?

一个很好的说明文档可以参考What is a Digital Signature?,中文翻译可以参考:数字签名是什么?

  • 简单来说,签名主要包含两个过程:摘要和非对称加密,首先对需要签名的数据做摘要(类似于常见的 MD5)后得到摘要结果,然后通过签名者的私钥对摘要结果进行非对称加密即可得到签名结果。

开放平台支持的签名算法

  • 由于计算能力的飞速发展,从安全性角度考虑,蚂蚁金服在原来 SHA1WithRSA 签名算法的基础上,新增了支持 SHA256WithRSA 的签名算法。该算法在摘要算法上比 SHA1WithRSA 有更强的安全能力。

  • 目前 SHA1WithRSA 的签名算法会继续提供支持,但为了您的应用安全,强烈建议使用 SHA256WithRSA 的签名算法。

RSA/RSA2 进行签名和验签

package com.byttersoft.hibernate.erp.szmy.util;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
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.List;
import java.util.Map;

import org.apache.commons.codec.binary.Base64;

import com.byttersoft.framework.util.StringUtil;
/**
 * RSA的签名及验签
 * @author zhouyy
 *
 */
public class RSA {

    private static final String SIGN_TYPE_RSA = "RSA";

    private static final String SIGN_TYPE_RSA2 = "RSA2";

    private static final String SIGN_ALGORITHMS = "SHA1WithRSA";

    private static final String SIGN_SHA256RSA_ALGORITHMS = "SHA256WithRSA";

    private static final int DEFAULT_BUFFER_SIZE = 8192;

    /**
     * RSA/RSA2 生成签名
     * 
     * @param map
     *            包含 sign_type、privateKey、charset
     * @return
     * @throws Exception
     */
    public static String rsaSign(Map map) throws Exception {
        PrivateKey priKey = null;
        java.security.Signature signature = null;
        String signType = map.get("sign_type").toString();
        String privateKey = map.get("privateKey").toString();
        String charset = map.get("charset").toString();
        String content = getSignContent(map);
        map.put("content", content);
        System.out.println("请求参数生成的字符串为:" + content);
        if (SIGN_TYPE_RSA.equals(signType)) {
            priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
            signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
        } else if (SIGN_TYPE_RSA2.equals(signType)) {
            priKey = getPrivateKeyFromPKCS8(SIGN_TYPE_RSA, new ByteArrayInputStream(privateKey.getBytes()));
            signature = java.security.Signature.getInstance(SIGN_SHA256RSA_ALGORITHMS);
        } else {
            throw new Exception("不是支持的签名类型 : : signType=" + signType);
        }
        signature.initSign(priKey);

        if (StringUtil.isEmpty(charset)) {
            signature.update(content.getBytes());
        } else {
            signature.update(content.getBytes(charset));
        }

        byte[] signed = signature.sign();

        return new String(Base64.encodeBase64(signed));

    }

    /**
     * 验签方法
     * 
     * @param content
     *            参数的合成字符串格式: key1=value1&key2=value2&key3=value3...
     * @param sign
     * @param publicKey
     * @param charset
     * @param signType
     * @return
     */
    public static boolean rsaCheck(Map map, String sign) throws Exception {
        java.security.Signature signature = null;
        String signType = map.get("sign_type").toString();
        String privateKey = map.get("privateKey").toString();
        String charset = map.get("charset").toString();
        String content = map.get("content").toString();
        String publicKey = map.get("publicKey").toString();
        System.out.println(">>验证的签名为:" + sign);
        System.out.println(">>生成签名的参数为:" + content);
        PublicKey pubKey = getPublicKeyFromX509("RSA", new ByteArrayInputStream(publicKey.getBytes()));
        if (SIGN_TYPE_RSA.equals(signType)) {
            signature = java.security.Signature.getInstance(SIGN_ALGORITHMS);
        } else if (SIGN_TYPE_RSA2.equals(signType)) {
            signature = java.security.Signature.getInstance(SIGN_SHA256RSA_ALGORITHMS);
        } else {
            throw new Exception("不是支持的签名类型 : signType=" + signType);
        }
        signature.initVerify(pubKey);

        if (StringUtil.isEmpty(charset)) {
            signature.update(content.getBytes());
        } else {
            signature.update(content.getBytes(charset));
        }

        return signature.verify(Base64.decodeBase64(sign.getBytes()));
    }

    public static PrivateKey getPrivateKeyFromPKCS8(String algorithm, InputStream ins) throws Exception {
        if (ins == null || StringUtil.isEmpty(algorithm)) {
            return null;
        }

        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        byte[] encodedKey = readText(ins).getBytes();

        encodedKey = Base64.decodeBase64(encodedKey);

        return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(encodedKey));
    }

    public static PublicKey getPublicKeyFromX509(String algorithm, InputStream ins) throws Exception {
        KeyFactory keyFactory = KeyFactory.getInstance(algorithm);

        StringWriter writer = new StringWriter();
        io(new InputStreamReader(ins), writer, -1);

        byte[] encodedKey = writer.toString().getBytes();

        encodedKey = Base64.decodeBase64(encodedKey);

        return keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
    }

    /**
     * 把参数合成成字符串
     * 
     * @param sortedParams
     * @return
     */
    public static String getSignContent(Map<String, String> sortedParams) {
        StringBuffer content = new StringBuffer();
        // app_id,method,charset,sign_type,version,bill_type,timestamp,bill_date
        String[] sign_param = sortedParams.get("sign_param").split(",");// 生成签名所需的参数
        List<String> keys = new ArrayList<String>();
        for (int i = 0; i < sign_param.length; i++) {
            keys.add(sign_param[i]);
        }
        Collections.sort(keys);
        int index = 0;
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            /*if ("biz_content".equals(key)) {
                content.append(
                        (index == 0 ? "" : "&") + key + "={\"bill_date\":\"" + sortedParams.get("bill_date") + "\",")
                        .append("\"bill_type\":\"" + sortedParams.get("bill_type") + "\"}");
                index++;
            } else {*/
            String value = sortedParams.get(key);
            if (StringUtil.isNotEmpty(key) && StringUtil.isNotEmpty(value)) {
                content.append((index == 0 ? "" : "&") + key + "=" + value);
                index++;
            }    
//            }
        }
        return content.toString();
    }

    private static String readText(InputStream ins) throws IOException {
        Reader reader = new InputStreamReader(ins);
        StringWriter writer = new StringWriter();

        io(reader, writer, -1);
        return writer.toString();
    }

    private static void io(Reader in, Writer out, int bufferSize) throws IOException {
        if (bufferSize == -1) {
            bufferSize = DEFAULT_BUFFER_SIZE >> 1;
        }

        char[] buffer = new char[bufferSize];
        int amount;

        while ((amount = in.read(buffer)) >= 0) {
            out.write(buffer, 0, amount);
        }
    }

}