Java http加签、验签实现方案

为什么要加密验签? 防止报文明文传输

数据在网络传输过程中,容易被抓包。如果使用的是HTTP协议的请求/响应(Request OR Response),它是明文传输的,都是可以被截获、篡改、重放(重发)的。所以需要进行数据的加密验签,所以需要考虑以下几点。

  1. 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
  2. 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
  3. 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
  4. 防数据信息泄漏(案例:截获用户登录请求,截获到账号、密码等)

实现方式

常见的方式,就是对关键字段加密。比如查询订单接口,就可以对订单号进行加密。一般常用的加密算法对称加密算法(如:AES),或者哈希算法处理(如:MD5)

对称加密:加密和解密使用相同秘钥的加密算法

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。

非对称加密:非对称加密算法需要两个密钥(公开密钥和私有密钥)。公钥和私钥是成对存在的,如果用公钥对数据加密,只有对应的私钥才能解密。 (非对称加密是更安全的做法,加密是算法RSA或SM2)

非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。

加签验签:使用Hash算法(如 MD5或者SHA-256)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,得到报文对应的sign

加签:用Hash函数把原始报文生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名。通常来说呢,请求方会把「数字签名和报文原文」一并发送给接收方。

在这里插入图片描述
验签:接收方拿到原始报文和数字签名后,用「同一个Hash函数」从报文中生成摘要A。另外,用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。

在这里插入图片描述

客户端操作

请求参数:

字段类型必传说明
signString接口签名,用户接口验证
app_idString开放平台的APP_ID,例如:1234
date_timeString当前时间戳
keyString开发平台的APP_KEY,例如:XA12#Da
nameString业务参数
ageString业务参数

业务参数消息体数据格式:Content-Type 指定为 application/json

1.将请求参数中除sign外的多个键值对,根据键按照字典序排序,并按照"key1=value1&key2=value2…"的格式拼成一个字符串

String sortStr=" age=11&app_id=1234&date_time=1656926899731&name=xxx"

2.将key拼接在第一步中排序后的字符串后面得到待签名字符串

String sortStr ="age=11&app_id=1234&date_time=1656926899731&name=xxxkey=XA12#Da"

3.使用md5算法加密待加密字符串并转为大写即为sign

String sign ="57A132B7585F77B1948812275BE945B8"

4.将sign添加到请求参数中

https://www.baidu.com/test/get?age=11&app_id=1234&date_time=1656926899731&name=xxx&sign=57A132B7585F77B1948812275BE945B8

需要注意以下重要规则:

◆ 请求参数中有中文时,中文需要经过url编码,但计算签名时不需要;

◆ 请求参数的值为空则不参与签名;

◆ 参数名区分大小写;

◆ sign参数不参与签名;

服务端操作
1.接收到请求参数,转JSON格式
2.验签
   2.1拿出用户签名
   2.2根据APP_ID 拿去数据库中的KEY,使用该KEY进行重签参数
   2.3如果重签结果和用户签名一致则通过,否则返回签名错误
   2.4校验参数中的时间戳,如果时间戳 超过当前时间5分钟则签名失效
3.如果c、d都通过则正常请求业务

package com.chinaunicom.utils;


import cn.hutool.crypto.SecureUtil;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * @author yming wang
 * @date 2024/3/4 13:48
 * @desc
 */
@Slf4j
public class SignUtil {

    /**
     * sign有效期
     */
    private static final int TIMES = 111 * 60 * 1000;

    public static boolean check(JSONObject params, String appKey, String sign) {
        try {
            //公钥验签
            sign = RsaUtils.decryptByPublicKey(sign, appKey);
            if (!sign.equals(getSign(params, appKey))) {
                log.info("签名内容正确");
                return false;
            }
            Long expireTime = params.getLong("timestamp");
            Long currTime = System.currentTimeMillis();
            if ((currTime - expireTime) < 0 || (currTime - expireTime) > TIMES) {
                log.info("签名时间已过期");
                return false;
            }
            log.info("验签成功");
            return true;
        } catch (Exception e) {
            log.error("验签发生异常:", e);
        }
        return false;
    }

    /**
     * @param params
     * @return java.lang.String
     * @params:
     * @author yming wang
     * @date 2024/3/4 14:44
     * @desc 加签算法: 原始报文 ---hash算法---> 消息摘要 ---RSA私钥加密---> 数字签名
     * 验签算法:数字签名 ---RSA公钥解密--> 消息摘要 ---> 根据参数重新摘要 ---> 对比摘要喜喜
     */
    public static String getSign(JSONObject params, String appKey) {
        //将参数进行升序
        String sortParams = sortParams(params, appKey);
        //将参数进行hash生成消息摘要
        String sign = SecureUtil.md5(sortParams);
        return sign;
    }

    /**
     * @param params
     * @param appKey
     * @return java.lang.String
     * @params:
     * @author yming wang
     * @date 2024/3/4 15:25
     * @desc 将参数进行升序
     */
    public static String sortParams(JSONObject params, String appKey) {
        List<Map.Entry<String, Object>> entries = new ArrayList<>(params.entrySet());
        Collections.sort(entries, Comparator.comparing(Map.Entry::getKey));
        StringBuffer str = new StringBuffer();
        for (Map.Entry<String, Object> entry : entries) {
            Object value = entry.getValue();
            if (value != null && StringUtils.isNotBlank(value.toString())) {
                str.append(entry.getKey());
                str.append("=");
                str.append(value);
                str.append("&");
            }
        }
        //md5加上盐值避免根绝request body参数生成sign
        str.append("appKey");
        str.append("=");
        str.append(appKey);
        return str.toString();
    }

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


        String privateKey = "privateKey ";
        String publicKey = "publicKey ";

        JSONObject data = new JSONObject();
        data.put("appId", "10002");
        data.put("username", "用户名");
        data.put("account", "用户账号");
        String pwd = RsaUtils.encryptByPrivateKey("用户密码", privateKey);
        log.info("encryptPwd:{}", pwd);
        data.put("password", pwd);
        long timestamp = System.currentTimeMillis();
        data.put("timestamp", timestamp);

        //消息摘要
        String sign = getSign(data, publicKey);
        log.info("timestamp:{}", timestamp);
        log.info("isTrue:{}", sign.equals(getSign(data, publicKey)));
        log.info("消息摘要:{}", sign);
        //生成数字证书
        sign = RsaUtils.encryptByPrivateKey(sign, privateKey);
        log.info("生成数字证书:{}", sign);
        log.info("打印请求参数:{}", data);

        log.info("验签:{}", check(data, publicKey, sign));
    }
}
package com.chinaunicom.utils;

import lombok.extern.slf4j.Slf4j;

import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: CSDN
 * @description: yming wang
 * @author: wyming
 * @create: 2021-06-08 09:30:14
 **/
@Slf4j
public class RsaUtils {

    /**
     * 签名算法名称
     */
    private static final String RSA_KEY_ALGORITHM = "RSA";

    /**
     * 标准签名算法名称
     */
    private static final String RSA_SIGNATURE_ALGORITHM = "SHA1withRSA";
    private static final String RSA2_SIGNATURE_ALGORITHM = "SHA256withRSA";

    /**
     * RSA密钥长度,默认密钥长度是1024,密钥长度必须是64的倍数,在512到65536位之间,不管是RSA还是RSA2长度推荐使用2048
     */
    private static final int KEY_SIZE = 2048;

    /**
     * 生成密钥对
     *
     * @return 返回包含公私钥的map
     */
    public static Map<String, String> generateKey() {
        KeyPairGenerator keygen;
        try {
            keygen = KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("RSA初始化密钥出现错误,算法异常");
        }
        SecureRandom secrand = new SecureRandom();
        //初始化随机产生器
        secrand.setSeed("Alian".getBytes());
        //初始化密钥生成器
        keygen.initialize(KEY_SIZE, secrand);
        KeyPair keyPair = keygen.genKeyPair();
        //获取公钥并转成base64编码
        byte[] pub_key = keyPair.getPublic().getEncoded();
        String publicKeyStr = Base64.getEncoder().encodeToString(pub_key);
        //获取私钥并转成base64编码
        byte[] pri_key = keyPair.getPrivate().getEncoded();
        String privateKeyStr = Base64.getEncoder().encodeToString(pri_key);
        //创建一个Map返回结果
        Map<String, String> keyPairMap = new HashMap<>();
        keyPairMap.put("publicKeyStr", publicKeyStr);
        keyPairMap.put("privateKeyStr", privateKeyStr);
        return keyPairMap;
    }

    /**
     * 公钥加密(用于数据加密)
     *
     * @param data         加密前的字符串
     * @param publicKeyStr base64编码后的公钥
     * @return base64编码后的字符串
     * @throws Exception
     */
    public static String encryptByPublicKey(String data, String publicKeyStr) throws Exception {
        //Java原生base64解码
        byte[] pubKey = Base64.getDecoder().decode(publicKeyStr);
        //创建X509编码密钥规范
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //根据X509编码密钥规范产生公钥对象
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        //根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        //用公钥初始化此Cipher对象(加密模式)
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        //对数据加密
        byte[] encrypt = cipher.doFinal(data.getBytes());
        //返回base64编码后的字符串
        return Base64.getEncoder().encodeToString(encrypt);
    }

    /**
     * 私钥解密(用于数据解密)
     *
     * @param data          解密前的字符串
     * @param privateKeyStr 私钥
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptByPrivateKey(String data, String privateKeyStr) throws Exception {
        //Java原生base64解码
        byte[] priKey = Base64.getDecoder().decode(privateKeyStr);
        //创建PKCS8编码密钥规范
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //根据PKCS8编码密钥规范产生私钥对象
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        //用私钥初始化此Cipher对象(解密模式)
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        //对数据解密
        byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(data));
        //返回字符串
        return new String(decrypt);
    }

    /**
     * 私钥加密(用于数据签名)
     *
     * @param data          加密前的字符串
     * @param privateKeyStr base64编码后的私钥
     * @return base64编码后后的字符串
     * @throws Exception
     */
    public static String encryptByPrivateKey(String data, String privateKeyStr) throws Exception {
        //Java原生base64解码
        byte[] priKey = Base64.getDecoder().decode(privateKeyStr);
        //创建PKCS8编码密钥规范
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //根据PKCS8编码密钥规范产生私钥对象
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        //用私钥初始化此Cipher对象(加密模式)
        cipher.init(Cipher.ENCRYPT_MODE, privateKey);
        //对数据加密
        byte[] encrypt = cipher.doFinal(data.getBytes());
        //返回base64编码后的字符串
        return Base64.getEncoder().encodeToString(encrypt);
    }

    /**
     * 公钥解密(用于数据验签)
     *
     * @param data         解密前的字符串
     * @param publicKeyStr base64编码后的公钥
     * @return 解密后的字符串
     * @throws Exception
     */
    public static String decryptByPublicKey(String data, String publicKeyStr) throws Exception {
        //Java原生base64解码
        byte[] pubKey = Base64.getDecoder().decode(publicKeyStr);
        //创建X509编码密钥规范
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //根据X509编码密钥规范产生公钥对象
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        //根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)
        Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
        //用公钥初始化此Cipher对象(解密模式)
        cipher.init(Cipher.DECRYPT_MODE, publicKey);
        //对数据解密
        byte[] decrypt = cipher.doFinal(Base64.getDecoder().decode(data));
        //返回字符串
        return new String(decrypt);
    }

    /**
     * RSA签名
     *
     * @param data     待签名数据
     * @param priKey   私钥
     * @param signType RSA或RSA2
     * @return 签名
     * @throws Exception
     */
    public static String sign(byte[] data, byte[] priKey, String signType) throws Exception {
        //创建PKCS8编码密钥规范
        PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(priKey);
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //根据PKCS8编码密钥规范产生私钥对象
        PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
        //标准签名算法名称(RSA还是RSA2)
        String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
        //用指定算法产生签名对象Signature
        Signature signature = Signature.getInstance(algorithm);
        //用私钥初始化签名对象Signature
        signature.initSign(privateKey);
        //将待签名的数据传送给签名对象(须在初始化之后)
        signature.update(data);
        //返回签名结果字节数组
        byte[] sign = signature.sign();
        //返回Base64编码后的字符串
        return Base64.getEncoder().encodeToString(sign);
    }

    /**
     * RSA校验数字签名
     *
     * @param data     待校验数据
     * @param sign     数字签名
     * @param pubKey   公钥
     * @param signType RSA或RSA2
     * @return boolean 校验成功返回true,失败返回false
     */
    public static boolean verify(byte[] data, byte[] sign, byte[] pubKey, String signType) throws Exception {
        //返回转换指定算法的KeyFactory对象
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_KEY_ALGORITHM);
        //创建X509编码密钥规范
        X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(pubKey);
        //根据X509编码密钥规范产生公钥对象
        PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
        //标准签名算法名称(RSA还是RSA2)
        String algorithm = RSA_KEY_ALGORITHM.equals(signType) ? RSA_SIGNATURE_ALGORITHM : RSA2_SIGNATURE_ALGORITHM;
        //用指定算法产生签名对象Signature
        Signature signature = Signature.getInstance(algorithm);
        //用公钥初始化签名对象,用于验证签名
        signature.initVerify(publicKey);
        //更新签名内容
        signature.update(data);
        //得到验证结果
        return signature.verify(sign);
    }

    public static void demo() throws Exception {

        Map<String, String> stringStringMap = generateKey();
        String publicKeyStr = stringStringMap.get("publicKeyStr");
        String privateKeyStr = stringStringMap.get("privateKeyStr");

        System.out.println("-----------------生成的公钥和私钥------------------------------");
        System.out.println("获取到的公钥:" + publicKeyStr);
        System.out.println("获取到的私钥:" + privateKeyStr);
        // 待加密数据
        String data = "tranSeq=1920542585&amount=100&payType=wechat";
        // 公钥加密
        System.out.println("---------公钥--------加密和解密------------------------------");
        System.out.println("待加密的数据:" + data);
        String encrypt = RsaUtils.encryptByPublicKey(data, publicKeyStr);
        System.out.println("加密后数据:" + encrypt);
        // 私钥解密
        String decrypt = RsaUtils.decryptByPrivateKey(encrypt, privateKeyStr);
        System.out.println("解密后数据:" + decrypt);

        // 私钥加密
        System.out.println("----------私钥-------加密和解密------------------------------");
        System.out.println("待加密的数据:" + data);
        encrypt = RsaUtils.encryptByPrivateKey(data, privateKeyStr);
        System.out.println("加密后数据:" + encrypt);
        // 私钥解密
        decrypt = RsaUtils.decryptByPublicKey(encrypt, publicKeyStr);
        System.out.println("解密后数据:" + decrypt);
    }
}

  • 4
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是C#JavaSM2的示例代码: C#示例代码: ```csharp using System; using System.Security.Cryptography; using System.Text; namespace SM2Demo { class Program { static void Main(string[] args) { // 待名的数据 string data = "Hello World!"; // 载SM2证书 CngKey key = CngKey.Open("SM2Test"); // 创建SM2名对象 ECDsaCng sm2 = new ECDsaCng(key); // 计算名 byte[] signature = sm2.SignData(Encoding.UTF8.GetBytes(data)); // 输出名结果 Console.WriteLine("Signature: " + Convert.ToBase64String(signature)); } } } ``` Java示例代码: ```java import java.security.*; import java.security.spec.ECGenParameterSpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.util.Base64; public class SM2Demo { public static void main(String[] args) throws Exception { // 待的数据 String data = "Hello World!"; // 载SM2证书 KeyFactory keyFactory = KeyFactory.getInstance("EC"); byte[] privateKeyBytes = Base64.getDecoder().decode("MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgJzvJZJZJ5zJzJZJ5\n" + "zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ\n" + "5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ\n" + "5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJw=="); PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes); PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec); byte[] publicKeyBytes = Base64.getDecoder().decode("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DfQADZGk+JzJZJ5zJzJZJ5zJzJZJ5zJzJ\n" + "ZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJ\n" + "ZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJ\n" + "ZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJw=="); X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes); PublicKey publicKey = keyFactory.generatePublic(publicKeySpec); // 创建SM2对象 Signature signature = Signature.getInstance("SM3withSM2"); signature.initVerify(publicKey); // 名 byte[] signatureBytes = Base64.getDecoder().decode("MEUCIQDQJzvJZJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJgIgQJzvJZJZJ5zJz\n" + "JZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJz\n" + "JZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJzJZJ5zJw=="); signature.update(data.getBytes()); boolean result = signature.verify(signatureBytes); // 输出结果 System.out.println("Verify result: " + result); } } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值