likes
comments
collection
share

请求腾讯接口升级RSA身份签名模式

作者站长头像
站长
· 阅读数 5

背景

腾讯在推后台请求接口切换身份鉴权方式,背景大概是这样:

目前的接口请求身份校验方式使用的是access_token校验方式,该方式存在如下问题和风险:

a、token有效期2小时,需要接入方维护票据

b、ip白名单校验,当请求方更改出口ip时需要及时同步我们,否则会无法请求我们的接口

c、密钥或access_token泄露,都会导致安全风险

现在希望升级身份校验方式,升级为RSA身份签名方式,可以避免上述问题。

签名分析

目前业务中用到的签名验证方式有三种:

md5加密

1、请求参数加md5加盐加密,这个是最简单的实现,根据参数排序,再拼上secret_key,用md5加密生成。双方约定好秘钥就可以校验签名。

优点:实现简单

缺点:md5可以通过暴力破解,如果像前段时间的俄罗斯把国防部会议密码设置成1234就很容易破解了- -

下面是参考例子:

public static String md5Encode(String input, String secret_key) throws Exception{  
        JSONObject json = JSON.parseObject(input);  
        TreeMap<String, String> data = new TreeMap<String, String>();  
        for (String key : json.keySet()) {  
            if ("sign".equals(key)) {  
                //sign不参与签名  
                continue;  
            }  
            data.put(key, json.getString(key));  
        }  
        List<String> params = new ArrayList<String>();  
        // 重组参数  
        for (String key : data.keySet()) {  
            String value = String.format("%s=%s", key, data.get(key));  
            params.add(value);  
        }  
        // 组合参数和签名 secret_key  
        String temp = URLEncoder.encode(StringUtils.join(params, "&").toLowerCase() + "&key=" + secret_key);  
        String result = encode(temp);  
  
        return new String(result);  
    }

token认证

2、请求获取token接口,保存到redis中,token过期重新请求token,访问其他接口需要带着token参数验证。

优点: token定时刷新,就算token泄露也只会有一段时间的安全风险。

缺点: token容易泄露,可以通过token+ md5加盐方式校验数据

RSA鉴权

3、使用非对称加密算法RSA签名,生成公钥与私钥,公钥提供出去验证签名,私钥自己保存用于加密签名,生成秘钥链接: www.metools.info/code/c80.ht…

操作步骤:

第一步:与该接口负责人确认,需要参与签名计算的url 参数

第二步:参与签名计算的url参数,按照字母序以key1=value1&key2=value2 排列拼接得到:签名body

第三步:计算body sha256签名

第四步:计算RSA签名

sign = RSASSA-PKCS1-V1_5_SHA256(pviKey, sign)

第五步:urlencode and base64encode

sign = urlencode(base64encode(sign))

第六步:url上带上sign结果

优点:安全性较高

缺点:性能较慢,算法较复杂

测试代码:

1、添加依赖
<dependency>

<groupId>org.bouncycastle</groupId>

<artifactId>bcprov-jdk15on</artifactId>

<version>1.70</version>

</dependency>

2、测试代码(仅供参考)

import org.bouncycastle.asn1.ASN1Object;

import org.bouncycastle.asn1.ASN1ObjectIdentifier;

import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;

import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;

import org.bouncycastle.asn1.x509.AlgorithmIdentifier;

import java.io.IOException;

import java.net.URLEncoder;

import java.nio.charset.StandardCharsets;

import java.security.KeyFactory;

import java.security.NoSuchAlgorithmException;

import java.security.PrivateKey;

import java.security.Signature;

import java.security.spec.InvalidKeySpecException;

import java.security.spec.KeySpec;

import java.security.spec.PKCS8EncodedKeySpec;

import java.util.Base64;

/**

 * 签名工具

 */

public class SignUtils {

/**

     * 生成私钥对象

     *

     * @param pkcs1Base64Key PKCS#1格式私钥

     * @return

     * @throws IOException

     * @throws NoSuchAlgorithmException

     * @throws InvalidKeySpecException

     */

public static PrivateKey getPrivateKey(String pkcs1Base64Key) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {

byte[] pkcs1Bytes = Base64.getDecoder().decode(pkcs1Base64Key);

AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.pkcs_1);

ASN1Object asn1Object = ASN1ObjectIdentifier.fromByteArray(pkcs1Bytes);

PrivateKeyInfo privateKeyInfo = new PrivateKeyInfo(algorithmIdentifier, asn1Object);

byte[] pkcs8Bytes = privateKeyInfo.getEncoded();

KeyFactory keyFactory = KeyFactory.getInstance("RSA");

KeySpec privateKeySpec = new PKCS8EncodedKeySpec(pkcs8Bytes);

return keyFactory.generatePrivate(privateKeySpec);

}

/**

     * 计算签名

     *

     * @param message 参与签名的数据

     * @param privateKey 私钥对象

     * @return

     * @throws Exception

     */

public static String signMessage(String message, PrivateKey privateKey) throws Exception {

Signature signature = Signature.getInstance("SHA256withRSA");

        signature.initSign(privateKey);

        signature.update(message.getBytes(StandardCharsets.UTF_8));

return URLEncoder.encode(Base64.getEncoder().encodeToString(signature.sign()), StandardCharsets.UTF_8);

}

// 测试

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

// PKCS#1格式私钥

String privateKeyString = """

                -----BEGIN RSA PRIVATE KEY-----

                xxx

                -----END RSA PRIVATE KEY-----                        

                """;

        privateKeyString = privateKeyString.replace("\n", "")

.replace("-----BEGIN RSA PRIVATE KEY-----", "")

.replace("-----END RSA PRIVATE KEY-----", "")

.replace(" ", "");

// 构建私钥对象(可在初始化时创建,不用每次生成)

PrivateKey privateKey = getPrivateKey(privateKeyString);

// 参与签名数据

String message = "Q-UA=QV=1&PR=VIDEO&PT=TEST&CHID=10011";

// 构建签名

String sign = signMessage(message, privateKey);

System.out.println(sign);

}

}

上面的例子是腾讯给的,没有用过,应该也是可以的。java网上很多例子默认是支持PKCS8格式的,如果想要用PKCS1,可以添加以下代码

导入
<dependency>

<groupId>org.bouncycastle</groupId>

<artifactId>bcprov-jdk15on</artifactId>

<version>1.70</version>

</dependency>

static{

java.security.Security.addProvider( new org.bouncycastle.jce.provider.BouncyCastleProvider() );

}

问题分析

在接入RSA签名的时候遇到了一点问题,RSA对比md5和token方式还是复杂一点。在选择秘钥生成的时候可以选择秘钥长度和秘钥格式,这里要与合作方规定好,我们规定的是长度1024,格式PKCS1。

请求腾讯接口升级RSA身份签名模式

在对接文档里面看到:RSA签名目前支持「PKCS1v15」和「RSAPSS」两种鉴权模式,推荐使用后者,安全性更高。

我又看了一下这两个模式是什么东西。。java的参数好像没有这个选择,一般java都是用SHA256withRSA,后面发现这个底层好像就是PKCS1v15

请求腾讯接口升级RSA身份签名模式

总结

看腾讯的对接文档是真的快乐,基本demo都写好了,只需要接入。大家可以按需使用合适的签名方式。除了MD5加盐、Token验证、RSA加密这三种还有其他常见的签名方式吗?评论区可以交流。