篡改、重放(重发)的
发布时间:2025-06-24 20:18:43 作者:北方职教升学中心 阅读量:024
通常来说呢,请求方会把「数字签名和报文原文」一并发送给接收方。
加签验签:使用Hash算法(如 MD5或者SHA-256)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,得到报文对应的sign
加签:用Hash函数把原始报文生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名。一般常用的加密算法对称加密算法(如:AES),或者哈希算法处理(如:MD5)
对称加密:加密和解密使用相同秘钥的加密算法
采用单钥密码系统的加密方法,同一个密钥可以同时用作信息的加密和解密,这种加密方法称为对称加密,也称为单密钥加密。如果使用的是HTTP协议的请求/响应(Request OR Response),它是明文传输的,都是可以被截获、
- 防伪装攻击(案例:在公共网络环境中,第三方 有意或恶意 的调用我们的接口)
- 防篡改攻击(案例:在公共网络环境中,请求头/查询字符串/内容 在传输过程被修改)
- 防重放攻击(案例:在公共网络环境中,请求被截获,稍后被重放或多次重放)
- 防数据信息泄漏(案例:截获用户登录请求,截获到账号、公钥和私钥是成对存在的,如果用公钥对数据加密,只有对应的私钥才能解密。
客户端操作
请求参数:
字段 类型 必传 说明 sign String 是 接口签名,用户接口验证 app_id String 是 开放平台的APP_ID,例如:1234 date_time String 是 当前时间戳 key String 是 开发平台的APP_KEY,例如:XA12#Da name String 是 业务参数 age String 是 业务参数 业务参数消息体数据格式: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);}}