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

背景

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

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

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

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

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

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

签名分析

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

md5加密

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

优点:实现简单

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

下面是参考例子:

java 复制代码
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结果

优点:安全性较高

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

测试代码:

java 复制代码
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,可以添加以下代码

java 复制代码
导入
<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签名目前支持「PKCS1v15」和「RSAPSS」两种鉴权模式,推荐使用后者,安全性更高。

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

总结

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

相关推荐
Lyqfor6 分钟前
云原生学习
java·分布式·学习·阿里云·云原生
程序猿麦小七29 分钟前
今天给在家介绍一篇基于jsp的旅游网站设计与实现
java·源码·旅游·景区·酒店
张某布响丸辣42 分钟前
SQL中的时间类型:深入解析与应用
java·数据库·sql·mysql·oracle
喜欢打篮球的普通人1 小时前
rust模式和匹配
java·算法·rust
java小吕布1 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
慢生活的人。1 小时前
SpringSecurity+jwt+captcha登录认证授权总结
java·认证·rbac·权限·验证
Goboy2 小时前
工欲善其事,必先利其器;小白入门Hadoop必备过程
后端·程序员
向阳12182 小时前
LeetCode40:组合总和II
java·算法·leetcode
云空2 小时前
《InsCode AI IDE:编程新时代的引领者》
java·javascript·c++·ide·人工智能·python·php
慧都小妮子2 小时前
Spire.PDF for .NET【页面设置】演示:复制 PDF 文档中的页面
java·pdf·.net