篡改、重放(重发)的

发布时间:2025-06-24 20:18:43  作者:北方职教升学中心  阅读量:024


通常来说呢,请求方会把「数字签名和报文原文」一并发送给接收方。

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

加签:用Hash函数把原始报文生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名。一般常用的加密算法对称加密算法(如:AES),或者哈希算法处理(如:MD5)

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

采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。如果使用的是HTTP协议的请求/响应(Request OR Response),它是明文传输的,都是可以被截获、

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

    在这里插入图片描述

    客户端操作

    请求参数:

    字段类型必传说明
    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…"的格式拼成一个字符串

    StringsortStr=" age=11&app_id=1234&date_time=1656926899731&name=xxx"

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

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

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

    Stringsign ="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、

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

    数据在网络传输过程中,容易被抓包。篡改、重放(重发)的。另外,用对方提供的公钥对数字签名进行解密,得到摘要B,对比A和B是否相同,就可以得知报文有没有被篡改过。 (非对称加密是更安全的做法,加密是算法RSA或SM2)

    非对称加密算法需要两个密钥来进行加密和解密,这两个密钥是公开密钥(public key,简称公钥)和私有密钥(private key,简称私钥)。所以需要进行数据的加密验签,所以需要考虑以下几点。比如查询订单接口,就可以对订单号进行加密。

    在这里插入图片描述
    验签:接收方拿到原始报文和数字签名后,用「同一个Hash函数」从报文中生成摘要A。密码等)

实现方式

常见的方式,就是对关键字段加密。

非对称加密:非对称加密算法需要两个密钥(公开密钥和私有密钥)。d都通过则正常请求业务

packagecom.chinaunicom.utils;importcn.hutool.crypto.SecureUtil;importcom.alibaba.fastjson.JSONObject;importlombok.extern.slf4j.Slf4j;importorg.apache.commons.lang3.StringUtils;importjava.util.*;/** * @author yming wang * @date 2024/3/4 13:48 * @desc */@Slf4jpublicclassSignUtil{/**     * sign有效期     */privatestaticfinalintTIMES=111*60*1000;publicstaticbooleancheck(JSONObjectparams,StringappKey,Stringsign){try{//公钥验签sign =RsaUtils.decryptByPublicKey(sign,appKey);if(!sign.equals(getSign(params,appKey))){log.info("签名内容正确");returnfalse;}LongexpireTime =params.getLong("timestamp");LongcurrTime =System.currentTimeMillis();if((currTime -expireTime)<0||(currTime -expireTime)>TIMES){log.info("签名时间已过期");returnfalse;}log.info("验签成功");returntrue;}catch(Exceptione){log.error("验签发生异常:",e);}returnfalse;}/**     * @param params     * @return java.lang.String     * @params:     * @author yming wang     * @date 2024/3/4 14:44     * @desc 加签算法: 原始报文 ---hash算法---> 消息摘要 ---RSA私钥加密---> 数字签名     * 验签算法:数字签名 ---RSA公钥解密--> 消息摘要 ---> 根据参数重新摘要 ---> 对比摘要喜喜     */publicstaticStringgetSign(JSONObjectparams,StringappKey){//将参数进行升序StringsortParams =sortParams(params,appKey);//将参数进行hash生成消息摘要Stringsign =SecureUtil.md5(sortParams);returnsign;}/**     * @param params     * @param appKey     * @return java.lang.String     * @params:     * @author yming wang     * @date 2024/3/4 15:25     * @desc 将参数进行升序     */publicstaticStringsortParams(JSONObjectparams,StringappKey){List<Map.Entry<String,Object>>entries =newArrayList<>(params.entrySet());Collections.sort(entries,Comparator.comparing(Map.Entry::getKey));StringBufferstr =newStringBuffer();for(Map.Entry<String,Object>entry :entries){Objectvalue =entry.getValue();if(value !=null&&StringUtils.isNotBlank(value.toString())){str.append(entry.getKey());str.append("=");str.append(value);str.append("&");}}//md5加上盐值避免根绝request body参数生成signstr.append("appKey");str.append("=");str.append(appKey);returnstr.toString();}publicstaticvoidmain(String[]args)throwsException{StringprivateKey ="privateKey ";StringpublicKey ="publicKey ";JSONObjectdata =newJSONObject();data.put("appId","10002");data.put("username","用户名");data.put("account","用户账号");Stringpwd =RsaUtils.encryptByPrivateKey("用户密码",privateKey);log.info("encryptPwd:{}",pwd);data.put("password",pwd);longtimestamp =System.currentTimeMillis();data.put("timestamp",timestamp);//消息摘要Stringsign =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));}}
packagecom.chinaunicom.utils;importlombok.extern.slf4j.Slf4j;importjavax.crypto.Cipher;importjava.security.*;importjava.security.spec.PKCS8EncodedKeySpec;importjava.security.spec.X509EncodedKeySpec;importjava.util.Base64;importjava.util.HashMap;importjava.util.Map;/** * @program: CSDN * @description: yming wang * @author: wyming * @create: 2021-06-08 09:30:14 **/@Slf4jpublicclassRsaUtils{/**     * 签名算法名称     */privatestaticfinalStringRSA_KEY_ALGORITHM="RSA";/**     * 标准签名算法名称     */privatestaticfinalStringRSA_SIGNATURE_ALGORITHM="SHA1withRSA";privatestaticfinalStringRSA2_SIGNATURE_ALGORITHM="SHA256withRSA";/**     * RSA密钥长度,默认密钥长度是1024,密钥长度必须是64的倍数,在512到65536位之间,不管是RSA还是RSA2长度推荐使用2048     */privatestaticfinalintKEY_SIZE=2048;/**     * 生成密钥对     *     * @return 返回包含公私钥的map     */publicstaticMap<String,String>generateKey(){KeyPairGeneratorkeygen;try{keygen =KeyPairGenerator.getInstance(RSA_KEY_ALGORITHM);}catch(NoSuchAlgorithmExceptione){thrownewRuntimeException("RSA初始化密钥出现错误,算法异常");}SecureRandomsecrand =newSecureRandom();//初始化随机产生器secrand.setSeed("Alian".getBytes());//初始化密钥生成器keygen.initialize(KEY_SIZE,secrand);KeyPairkeyPair =keygen.genKeyPair();//获取公钥并转成base64编码byte[]pub_key =keyPair.getPublic().getEncoded();StringpublicKeyStr =Base64.getEncoder().encodeToString(pub_key);//获取私钥并转成base64编码byte[]pri_key =keyPair.getPrivate().getEncoded();StringprivateKeyStr =Base64.getEncoder().encodeToString(pri_key);//创建一个Map返回结果Map<String,String>keyPairMap =newHashMap<>();keyPairMap.put("publicKeyStr",publicKeyStr);keyPairMap.put("privateKeyStr",privateKeyStr);returnkeyPairMap;}/**     * 公钥加密(用于数据加密)     *     * @param data         加密前的字符串     * @param publicKeyStr base64编码后的公钥     * @return base64编码后的字符串     * @throws Exception     */publicstaticStringencryptByPublicKey(Stringdata,StringpublicKeyStr)throwsException{//Java原生base64解码byte[]pubKey =Base64.getDecoder().decode(publicKeyStr);//创建X509编码密钥规范X509EncodedKeySpecx509KeySpec =newX509EncodedKeySpec(pubKey);//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//根据X509编码密钥规范产生公钥对象PublicKeypublicKey =keyFactory.generatePublic(x509KeySpec);//根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)Ciphercipher =Cipher.getInstance(keyFactory.getAlgorithm());//用公钥初始化此Cipher对象(加密模式)cipher.init(Cipher.ENCRYPT_MODE,publicKey);//对数据加密byte[]encrypt =cipher.doFinal(data.getBytes());//返回base64编码后的字符串returnBase64.getEncoder().encodeToString(encrypt);}/**     * 私钥解密(用于数据解密)     *     * @param data          解密前的字符串     * @param privateKeyStr 私钥     * @return 解密后的字符串     * @throws Exception     */publicstaticStringdecryptByPrivateKey(Stringdata,StringprivateKeyStr)throwsException{//Java原生base64解码byte[]priKey =Base64.getDecoder().decode(privateKeyStr);//创建PKCS8编码密钥规范PKCS8EncodedKeySpecpkcs8KeySpec =newPKCS8EncodedKeySpec(priKey);//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//根据PKCS8编码密钥规范产生私钥对象PrivateKeyprivateKey =keyFactory.generatePrivate(pkcs8KeySpec);//根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)Ciphercipher =Cipher.getInstance(keyFactory.getAlgorithm());//用私钥初始化此Cipher对象(解密模式)cipher.init(Cipher.DECRYPT_MODE,privateKey);//对数据解密byte[]decrypt =cipher.doFinal(Base64.getDecoder().decode(data));//返回字符串returnnewString(decrypt);}/**     * 私钥加密(用于数据签名)     *     * @param data          加密前的字符串     * @param privateKeyStr base64编码后的私钥     * @return base64编码后后的字符串     * @throws Exception     */publicstaticStringencryptByPrivateKey(Stringdata,StringprivateKeyStr)throwsException{//Java原生base64解码byte[]priKey =Base64.getDecoder().decode(privateKeyStr);//创建PKCS8编码密钥规范PKCS8EncodedKeySpecpkcs8KeySpec =newPKCS8EncodedKeySpec(priKey);//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//根据PKCS8编码密钥规范产生私钥对象PrivateKeyprivateKey =keyFactory.generatePrivate(pkcs8KeySpec);//根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)Ciphercipher =Cipher.getInstance(keyFactory.getAlgorithm());//用私钥初始化此Cipher对象(加密模式)cipher.init(Cipher.ENCRYPT_MODE,privateKey);//对数据加密byte[]encrypt =cipher.doFinal(data.getBytes());//返回base64编码后的字符串returnBase64.getEncoder().encodeToString(encrypt);}/**     * 公钥解密(用于数据验签)     *     * @param data         解密前的字符串     * @param publicKeyStr base64编码后的公钥     * @return 解密后的字符串     * @throws Exception     */publicstaticStringdecryptByPublicKey(Stringdata,StringpublicKeyStr)throwsException{//Java原生base64解码byte[]pubKey =Base64.getDecoder().decode(publicKeyStr);//创建X509编码密钥规范X509EncodedKeySpecx509KeySpec =newX509EncodedKeySpec(pubKey);//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//根据X509编码密钥规范产生公钥对象PublicKeypublicKey =keyFactory.generatePublic(x509KeySpec);//根据转换的名称获取密码对象Cipher(转换的名称:算法/工作模式/填充模式)Ciphercipher =Cipher.getInstance(keyFactory.getAlgorithm());//用公钥初始化此Cipher对象(解密模式)cipher.init(Cipher.DECRYPT_MODE,publicKey);//对数据解密byte[]decrypt =cipher.doFinal(Base64.getDecoder().decode(data));//返回字符串returnnewString(decrypt);}/**     * RSA签名     *     * @param data     待签名数据     * @param priKey   私钥     * @param signType RSA或RSA2     * @return 签名     * @throws Exception     */publicstaticStringsign(byte[]data,byte[]priKey,StringsignType)throwsException{//创建PKCS8编码密钥规范PKCS8EncodedKeySpecpkcs8KeySpec =newPKCS8EncodedKeySpec(priKey);//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//根据PKCS8编码密钥规范产生私钥对象PrivateKeyprivateKey =keyFactory.generatePrivate(pkcs8KeySpec);//标准签名算法名称(RSA还是RSA2)Stringalgorithm =RSA_KEY_ALGORITHM.equals(signType)?RSA_SIGNATURE_ALGORITHM:RSA2_SIGNATURE_ALGORITHM;//用指定算法产生签名对象SignatureSignaturesignature =Signature.getInstance(algorithm);//用私钥初始化签名对象Signaturesignature.initSign(privateKey);//将待签名的数据传送给签名对象(须在初始化之后)signature.update(data);//返回签名结果字节数组byte[]sign =signature.sign();//返回Base64编码后的字符串returnBase64.getEncoder().encodeToString(sign);}/**     * RSA校验数字签名     *     * @param data     待校验数据     * @param sign     数字签名     * @param pubKey   公钥     * @param signType RSA或RSA2     * @return boolean 校验成功返回true,失败返回false     */publicstaticbooleanverify(byte[]data,byte[]sign,byte[]pubKey,StringsignType)throwsException{//返回转换指定算法的KeyFactory对象KeyFactorykeyFactory =KeyFactory.getInstance(RSA_KEY_ALGORITHM);//创建X509编码密钥规范X509EncodedKeySpecx509KeySpec =newX509EncodedKeySpec(pubKey);//根据X509编码密钥规范产生公钥对象PublicKeypublicKey =keyFactory.generatePublic(x509KeySpec);//标准签名算法名称(RSA还是RSA2)Stringalgorithm =RSA_KEY_ALGORITHM.equals(signType)?RSA_SIGNATURE_ALGORITHM:RSA2_SIGNATURE_ALGORITHM;//用指定算法产生签名对象SignatureSignaturesignature =Signature.getInstance(algorithm);//用公钥初始化签名对象,用于验证签名signature.initVerify(publicKey);//更新签名内容signature.update(data);//得到验证结果returnsignature.verify(sign);}publicstaticvoiddemo()throwsException{Map<String,String>stringStringMap =generateKey();StringpublicKeyStr =stringStringMap.get("publicKeyStr");StringprivateKeyStr =stringStringMap.get("privateKeyStr");System.out.println("-----------------生成的公钥和私钥------------------------------");System.out.println("获取到的公钥:"+publicKeyStr);System.out.println("获取到的私钥:"+privateKeyStr);// 待加密数据Stringdata ="tranSeq=1920542585&amount=100&payType=wechat";// 公钥加密System.out.println("---------公钥--------加密和解密------------------------------");System.out.println("待加密的数据:"+data);Stringencrypt =RsaUtils.encryptByPublicKey(data,publicKeyStr);System.out.println("加密后数据:"+encrypt);// 私钥解密Stringdecrypt =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);}}