当前位置: 首页 > news >正文

加密解密加签验签——接口安全的最后一道防线

加密解密加签验签——接口安全的最后一道防线

密评来了

等保密评(密码应用安全性评估)要求下发。

整改项

  • 用户信息存储:明文 → 加密
  • 数据传输:明文 → HTTPS + 加密
  • 接口调用:无认证 → 加签验签
  • 登录口令:明文传输 → SM3 摘要

加密方案演进

算法研究

加密算法的问题 加密研究 加密算法实现 vc解密算法实现

当时还在做技术预研,试过各种算法:

  • RSA 1024/2048
  • AES 128/256
  • DES/3DES
  • MD5/SHA

结论:政务系统要求用国密算法,不能用国际算法。

接口加密

新接口加密功能开发及动态库改写

接口升级,首次引入加密:

// 动态库中的加密函数(VC编写)intEncryptData(constchar*plainText,// 明文char*cipherText,// 密文(输出)constchar*key// 密钥);intDecryptData(constchar*cipherText,// 密文char*plainText,// 明文(输出)constchar*key// 密钥);

问题:密钥硬编码在动态库里,每发一个版本都要重新编译。

电子签名

电子签名文件生产函数编写

PDF电子签名,用于医保凭证:

publicbyte[]signPdf(byte[]pdfData,X509Certificatecert,PrivateKeykey){// 1. 计算PDF摘要MessageDigestmd=MessageDigest.getInstance("SM3");byte[]digest=md.digest(pdfData);// 2. 用私钥签名Signaturesig=Signature.getInstance("SM2");sig.initSign(key);sig.update(digest);byte[]signature=sig.sign();// 3. 嵌入PDFPdfSignersigner=newPdfSigner();signer.sign(pdfData,cert,signature);returnsigner.getSignedPdf();}

全面密评改造

1. HTTPS 证书问题
解决认证https不能访问的问题 证书不符合要求

问题:自签名证书被浏览器拦截。

解决:用 OpenSSL 生成合法证书。

# 生成RSA私钥和自签名证书openssl req-newkeyrsa:2048-nodes-keyoutrsa_private.key\-x509-days365-outcert.crt# 导出为PFX格式(Tomcat使用)openssl pkcs12-export-outcertificate.pfx\-inkeyrsa_private.key-incert.crt

注意:生产环境必须用 CA 颁发的证书,不能自签名。

2. 用户信息加密存储
用户信息加密解密开发、部署

需求:数据库中存储的用户信息(姓名、身份证、手机号)必须加密。

// 加密存储方案publicclassUserInfoEncryption{// 存储时加密publicvoidsaveUser(Useruser){StringencryptedName=SM4.encrypt(user.getName(),secretKey);StringencryptedIdCard=SM4.encrypt(user.getIdCard(),secretKey);jdbcTemplate.update("INSERT INTO t_user (name, id_card, ...) VALUES (?, ?, ...)",encryptedName,encryptedIdCard);}// 查询时解密publicUsergetUser(Longid){Map<String,Object>row=jdbcTemplate.queryForMap("SELECT * FROM t_user WHERE id = ?",id);Useruser=newUser();user.setName(SM4.decrypt((String)row.get("name"),secretKey));user.setIdCard(SM4.decrypt((String)row.get("id_card"),secretKey));returnuser;}}

问题:加密后无法模糊查询(LIKE '%张%')。

解决

// 方案1:加密字段精确查询SELECT*FROMt_userWHEREname=SM4.encrypt('张三',key)// 方案2:增加明文索引字段(脱敏后)ALTERTABLEt_userADDname_indexVARCHAR(50);// name_index = "张**"(只存姓氏,不存全名)// 方案3:使用可搜索加密(Searchable Encryption)// 但国密不支持,暂不采用
3. 接口加签验签
加密、解密、加签、验签测试 加密、解密、加签、验签方法确定、代码编写、更新数据、查询数据测试

完整方案

publicclassApiSecurity{// ========== 加签(发送方) ==========publicStringsignRequest(Map<String,String>params,StringsecretKey){// 1. 参数排序TreeMap<String,String>sortedParams=newTreeMap<>(params);// 2. 拼接字符串StringBuildersb=newStringBuilder();for(Map.Entry<String,String>entry:sortedParams.entrySet()){sb.append(entry.getKey()).append("=").append(entry.getValue()).append("&");}sb.append("key=").append(secretKey);// 3. SM3 摘要byte[]digest=SM3.digest(sb.toString().getBytes(StandardCharsets.UTF_8));// 4. 转十六进制字符串returnHex.encodeHexString(digest);}// ========== 验签(接收方) ==========publicbooleanverifySign(Map<String,String>params,Stringsign,StringsecretKey){// 1. 去除签名字段Map<String,String>paramsWithoutSign=newHashMap<>(params);paramsWithoutSign.remove("sign");// 2. 重新计算签名StringcalculatedSign=signRequest(paramsWithoutSign,secretKey);// 3. 比对returncalculatedSign.equals(sign);}// ========== 接口加密 ==========publicStringencryptRequest(Stringdata,StringsessionKey){// 1. 生成随机密钥byte[]randomKey=SM4.generateKey();// 2. 用会话密钥加密随机密钥byte[]encryptedKey=SM4.encrypt(randomKey,sessionKey);// 3. 用随机密钥加密数据byte[]encryptedData=SM4.encrypt(data.getBytes(),randomKey);// 4. 组装returnBase64.encode(encryptedKey)+"."+Base64.encode(encryptedData);}}

验签流程

请求方 接收方 │ │ │ 1. 参数排序 │ │ 2. 拼接 key=value&key=value │ │ 3. SM3 摘要 │ │ 4. 得到签名 sign │ │ │ │ ──── 请求参数 + sign ──────▶ │ │ │ │ │ 1. 去除 sign 字段 │ │ 2. 同样方式计算签名 │ │ 3. 比对两个签名 │ │ │ ◀──── 响应 + sign ────────── │ │ │ │ 验签响应 │
4. 登录口令密文传输
登录口令密文传输改造(历史数据查询) 前端sm3算法加密口令(实际为计算摘要) 后端解密存储在数据中的口令,再sm3算法加密 比较是否相同

改造前

// 登录时明文传输$.ajax({url:"/login",data:{username:"admin",password:"123456"// 明文!抓包就能看到}});

改造后

// 前端:SM3 摘要后传输asyncfunctionlogin(username,password){// 1. 获取随机盐值constsalt=awaitgetSalt(username);// 2. 计算 SM3(password + salt)consthash=sm3(password+salt);// 3. 传输摘要(不是原文)$.ajax({url:"/login",data:{username:username,passwordHash:hash,salt:salt}});}
// 后端:验证逻辑publicbooleanverifyPassword(Stringusername,StringpasswordHash,Stringsalt){// 1. 从数据库取出加密存储的口令StringstoredPassword=userDao.getPassword(username);// 2. 解密存储的口令(SM4解密)StringdecryptedPassword=SM4.decrypt(storedPassword,secretKey);// 3. 计算 SM3(解密口令 + salt)StringexpectedHash=SM3.digest(decryptedPassword+salt);// 4. 比对returnexpectedHash.equals(passwordHash);}

国密算法选择

为什么用国密?

等保密评要求:政务系统必须使用国密算法

算法对照表

用途国际算法国密算法说明
对称加密AESSM4分组加密,128位密钥
非对称加密RSASM2基于椭圆曲线,256位密钥
摘要SHA-256SM3256位摘要
随机数DRBGSM3派生用于生成密钥

实际使用对比

// AES 加密(旧)Ciphercipher=Cipher.getInstance("AES/CBC/PKCS5Padding");cipher.init(Cipher.ENCRYPT_MODE,aesKey,iv);byte[]encrypted=cipher.doFinal(plainText);// SM4 加密(新)SMS4sm4=newSMS4();byte[]encrypted=sm4.encrypt(plainText,sm4Key,sm4Iv);

SM2 vs RSA 性能

算法密钥生成签名(100次)验签(100次)安全性
RSA-2048慢(2s)慢(1s)快(0.3s)中等
SM2-256快(0.1s)快(0.3s)慢(1s)高(同等RSA-3072)

性能问题

加密对性能的影响

测试数据: - 100万条用户信息加密存储 - SM4加密每条耗时:0.1ms - SM4解密每条耗时:0.1ms - 总耗时:100万 × 0.1ms = 100秒 优化后: - 批量加密:1000条/批 - 并行处理:4线程 - 总耗时:100万 × 0.1ms / 4 / 1000 = 25秒

查询性能对比

操作明文加密后下降
精确查询2ms5ms2.5x
范围查询10ms无法-
模糊查询15ms无法-
批量查询5ms15ms3x

教训:加密不是免费的,需要为性能下降买单。

常见坑

坑1:编码不一致

// 前端JavaScriptconsthash=sm3("张三");// 输出:e8d7...(UTF-8编码)// 后端Javabyte[]digest=SM3.digest("张三".getBytes(StandardCharsets.UTF_8));Stringhash=Hex.encodeHexString(digest);// 输出:e8d7...(一致)// 如果后端用了GBKbyte[]digest=SM3.digest("张三".getBytes("GBK"));// 输出:f3a2...(不一致!)

坑2:Base64 vs Hex

// Base64:大小写敏感,有+和/字符// Hex:只含0-9a-f,URL安全// 建议:传输用Base64URL(无+无/无=)Stringsafe=Base64.getUrlEncoder().withoutPadding().encodeToString(data);

坑3:密钥泄露

# 密钥被上传到Git仓库 git add . git commit -m "add config" git push # 密钥文件被公开! # 预防措施 echo "*.key" >> .gitignore echo "keystore.*" >> .gitignore

坑4:证书过期

# 证书过期,服务不可用 javax.net.ssl.SSLHandshakeException: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed # 监控证书过期时间 openssl x509 -in cert.pem -noout -enddate # 设置提前30天告警

经验教训

1. 加密不是万能的

  • 加密解决的是存储和传输安全
  • 业务安全需要权限控制、审计日志配合
  • 数据脱敏和加密是两回事

2. 国密算法生态不完善

  • Java 默认不支持 SM2/SM3/SM4,需要 BouncyCastle
  • 部分数据库不支持加密函数
  • 硬件加密机(HSM)兼容性差

3. 性能损耗不可忽视

  • 加密后无法做数据库内计算
  • 模糊查询需要额外设计
  • 批量操作需要分页

4. 密钥管理是最薄弱的环节

最常见的密钥管理问题: 1. 密钥硬编码 → 泄露 2. 密钥定期轮换 → 旧数据无法解密 3. 密钥多环境 → 开发、测试、生产混用 4. 密钥备份 → 丢失后数据永久丢失

最后的话

加密解密加签验签,看起来是纯技术问题,实际上是一个管理问题

技术上,SM2/SM3/SM4 算法都是现成的,BouncyCastle 库也成熟。真正难的是:

  1. 密钥怎么管——谁持有密钥?泄露了怎么办?
  2. 性能怎么保——加密后查询慢了10倍,业务能接受吗?
  3. 兼容怎么搞——旧数据没加密,新数据加密了,怎么平滑过渡?

最实在:

加密、解密、加签、验签方法确定、代码编写、更新数据、查询数据测试

"方法确定"放在"代码编写"前面——先想清楚再做,加密这事,一步错步步错。

http://www.jsqmd.com/news/842735/

相关文章:

  • docker部署postgresql和nginx
  • 【亲测免费】 探索卷积神经网络之美:一键绘制专业结构图的利器
  • MiddleClick:提升效率的鼠标中间点击神器
  • 2026年4月解放碑好吃的火锅推荐,速来了解,火锅/社区火锅/火锅店/特色美食/美食,火锅品牌推荐 - 品牌推荐师
  • 2026年评价高的黄浦区办公室装修/浦东新区办公室装修/上海办公室休闲区装修装修公司 - 品牌宣传支持者
  • 手把手教你用STM32G4的SPI扩展CAN接口:MCP2518FD驱动移植与配置避坑指南
  • 2026年亲测10款免费降AI工具:毕业生收藏,将AI率降至6%【附直达链接】 - 降AI实验室
  • 英雄联盟R3nzSkin换肤工具:3分钟实现安全免费的全皮肤体验
  • Haneke最佳实践:10个技巧让你的图片缓存更高效
  • 【亲测免费】 野人家园串口调试助手UartAssist 5.0.14:工控开发者的得力助手
  • 企业业务智能体构建实操:RAG+Agent+OpenClaw业务应用和构建深度实操
  • ‌甲骨文解码压力测试:让AI争论商朝占卜真实性‌
  • Dominate最佳实践:代码组织、性能优化和调试技巧大全
  • Windows Cleaner:终极C盘救星,免费开源工具解决空间不足难题
  • 汽车总线开发利器:VBA工具从入门到实战解析
  • 保姆级教程:用YOLOv8在TT100K数据集上训练你的第一个交通标志检测模型(附完整代码)
  • 探秘游戏安全:驱动级防护与图标守护的开源宝藏
  • 台州仿石材、别墅外墙装饰怎么选?润达铝业冲孔雕花热转印木纹氟碳喷涂铝单板一站式定制服务 - 栗子测评
  • 把 Key User 自定义字段纳入 abapGit 管理,让扩展交付真正可追踪
  • 5分钟快速上手Mermaid Live Editor:免费在线图表编辑终极指南
  • 构建容灾备份方案时利用Taotoken的多模型路由能力
  • Bubble Navigation实战:构建现代化电商App导航系统的终极指南
  • ROFL播放器:英雄联盟游戏回放分析工具终极指南
  • 数据缺失处理全攻略:从诊断到高级填补的实战工具箱
  • 【免费下载】 让您的无线网络更稳定:Realtek 8188GU 无线网卡驱动推荐
  • SAP UI5 里没有 BehaviorSubject,但有更贴近企业 UI 的状态流
  • pyftpdlib安全最佳实践:防止DDoS攻击和未授权访问的终极指南
  • hoist-non-react-statics 在大型项目中的应用:企业级 React 开发实战
  • Timex多语言支持:利用Gettext实现国际化时间显示
  • 基于深度学习的pdf水印去除代码教程