接口安全设计

需要使用国密算法,查阅网上资料,需引入Bouncy Castle依赖

<dependency>
  <groupId>org.bouncycastle</groupId>
  <artifactId>bcprov-jdk15to18</artifactId>
  <version>1.69</version>
</dependency>

并引入hutools-crypto来使用SM2,SM3,SM4 Hutool参考文档-国密算法工具-SmUtil

// 生成sm4的key
byte[] key = SecureUtil.generateKey("SM4").getEncoded();
String sm4KeyBase64 = Base64.getEncoder().encodeToString(key);
String sm4KeyBase64U = new String(sm4k.getBytes(), StandardCharsets.UTF_8);

// 生成sm2的key值对
KeyPair pair = SecureUtil.generateKeyPair("SM2");
byte[] privateKey = pair.getPrivate().getEncoded();
String privateKeyBase64 = Base64.getEncoder().encodeToString(privateKey);
String privateKeyBase64U = new String(privateKeyS.getBytes(), StandardCharsets.UTF_8);

byte[] publicKey = pair.getPublic().getEncoded();
String publicKeyBase64 = Base64.getEncoder().encodeToString(publicKey);
String publicKeyBase64U = new String(publicKeyS.getBytes(), StandardCharsets.UTF_8);

字符集,编码解码,加解密

字符集是bytes和string互相转换,如果字符集没选好,就容易出乱码。一般用UTF_8. 编码一般有Hex(Base16)和Base64,编码解码是为了避免有一些特殊字符,影响传输。 Base64UrlSafe(可以避免url中特殊字符出问题) 加解密是为了避免信息泄漏。

前后端编码方式选择

前端 后端hutools-crypto、Bouncy Castle,既可以base16,又可以base64. hex,base64,urlencode编码方案对比 SM4算法主要包含5种基本模式:ECB、CBC、CFB、OFB,CTR(后4种都是ECB算法模块衍生而来);与MAC结合还诞生了GCM,CCM等高级模式。 SM4分组密码算法介绍_浩雪峰的博客-CSDN博客_sm4分组密码算法

考虑性能和安全性,没有一种模式是绝对的最优选择,不同的模式适用于不同的应用场景。 分组加密模式 ECB、CBC、PCBC、CFB、OFB、CTR_fengwang0301的博客-CSDN博客_分组密码ecb模式

补码padding

SM4中的padding是指在加密前对明文进行填充,使其长度为16字节的整数倍,因为SM4是一种分组密码,每个分组的长度为16字节。12

不同的padding方式有不同的填充规则,常见的有PADDING_PKCS5、PADDING_PKCS7、PADDING_ISO10126、PADDING_ANSI_X923等。1

填充的目的是为了保证密文的完整性和一致性,同时也可以防止一些攻击,例如重放攻击和填充攻击。2

SM4用什么padding比较好,取决于你的加密模式和数据格式。12

一般来说,如果你使用的是ECB模式或者CBC模式,那么你需要对明文进行padding,因为这两种模式要求明文的长度是16字节的倍数。12

如果你使用的是CFB模式、OFB模式或者CTR模式,那么你不需要对明文进行padding,因为这三种模式可以将分组密码转化为流密码,可以对任意长度的明文进行加密。12

常见的padding方式有PADDING_PKCS5、PADDING_PKCS7、PADDING_ISO10126、PADDING_ANSI_X923等,它们的区别主要在于填充的字节的值和验证的方式。12

PADDING_PKCS5和PADDING_PKCS7的填充方式是在明文的末尾添加N个字节,每个字节的值都是N,其中N是从1到16的整数,表示需要填充的字节数。12

PADDING_ISO10126的填充方式是在明文的末尾添加N个字节,其中最后一个字节的值是N,其余的字节的值是随机的。12

PADDING_ANSI_X923的填充方式是在明文的末尾添加N个字节,其中最后一个字节的值是N,其余的字节的值都是0。12

在解密的时候,需要根据填充的字节的值来判断填充的字节数,然后去掉填充的字节,恢复原始的明文。12

如果你的数据格式是二进制的,那么你可以选择任意一种padding方式,只要加密和解密的时候保持一致即可。12

如果你的数据格式是文本的,那么你最好选择PADDING_PKCS5或者PADDING_PKCS7,因为这两种方式可以避免填充的字节和原始的明文的字符冲突,导致解密的时候出错。12

综上所述,SM4用什么padding比较好,没有一个确定的答案,你需要根据你的具体的需求和场景来选择合适的padding方式。12

加密

方式1:对每个json字段进行加密?

方式2:对整个请求报文进行加密(网金)请求报文本身是一个JSON,然后加密了之后以参数形式传递。

前端-请求加密

js SM4对称加密

后端-请求解密

java SM4对称解密

后端-响应加密

java SM4对称加密

前端-响应解密

js SM4对称解密

数据防重放

方案1:预请求

预请求,获取redis中的某个随机数;然后,根据随机数去访问接口,就会清掉redis的key-value. 通过这种方式进行防止数据重放。

没有redis,只能放在数据库中,并且需要设计两个请求,每个请求涉及DB的写操作,建议用方案2.

方案2:timestamp + nonce 方案

10 种保证接口数据安全的方案! 时间戳超时机制也是有漏洞的,如果是在时间差内,黑客进行的重放攻击,那就不好使了。可以使用timestamp + nonce方案。

nonce指唯一的随机字符串,用来标识每个被签名的请求。我们可以将每次请求的nonce参数存储到一个“set 集合”中,或者可以 json 格式存储到数据库或缓存中。每次处理 HTTP 请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。

然而对服务器来说,永久保存nonce的代价是非常大的。可以结合timestamp来优化。因为timstamp参数对于超过3min的请求,都认为是非法请求,所以我们只需要存储3minnonce参数的“集合”即可。

方案 优点 缺点
数据库 完全防重放 每次需要查询DB,速率慢;需要写定时任务清理nonce;改造量大
本地缓存 速率快;缓存失效机制自动清理;改造少 假设有n台机器,重放攻击可以到达n次。

不同缓存框架比较

(很全面)SpringBoot 使用 Caffeine 本地缓存 SpringBoot: Implement caching with Caffeine. Spring Cache的使用教程:注解形式和api接口形式,以及调用内部方法注解失效的原因_不如敲代码的博客-CSDN博客

数据防篡改

数据报文加签验签,是保证数据传输安全的常用手段,它可以保证数据在传输过程中不被篡改。以前我做的企业转账系统,就用了加签验签。 签名/验签时,将参数名ASCII码从小到大排序_vamViolet的博客-CSDN博客_参数名ascii码从小到大排序

2.1 什么是加签验签呢?

  • 数据加签:用 Hash算法(SM3)把原始请求参数生成报文摘要,然后用私钥对这个摘要进行加密,就得到这个报文对应的数字签名sign(这个过程就是加签)。通常来说,请求方会把数字签名和报文原文一并发送给接收方。

  • 验签:接收方拿到原始报文和数字签名(sign)后,用**同一个 Hash 算法(**比如都用 SM3)从报文中生成摘要 A。另外,用对方提供的公钥对数字签名进行解密,得到摘要 B,对比 A 和 B 是否相同,就可以得知报文有没有被篡改过。

其实加签,按我的理解,就是把请求参数,按照一定规则,利用hash算法+加密算法生成一个唯一标签sign。验签的话,就是把请求参数按照相同的规则处理,再用相同的hash算法,和对应的密钥解密处理,以对比这个签名是否一致。

请求报文参考

{
  "timestamp":"xxxx",
  "nonce":"xxxx",
  "sign":"xxxxxxxxxxxxxxx",
}

数据安全处理-最终方案

报文密文处理-SM4

  1. 前端对请求报文加密,后端对请求报文解密;
  2. 后端对响应报文加密,前端对响应报文解密

将整个报文体加密为一个密文字符串

分组加密模式 ECB、CBC、PCBC、CFB、OFB、CTR_fengwang0301的博客-CSDN博客_分组密码ecb模式

ECB

ECB模式加密过程

ECB模式解密过程

CBC

CBC模式加密过程

CBC模式解密过程

数据防重放

timestamp + nonce 方案

nonce指唯一的随机字符串,用来标识每个被签名的请求。我们可以将每次请求的nonce参数存储到一个“set 集合”中,或者可以 json 格式存储到数据库或缓存中。每次处理 HTTP 请求时,首先判断该请求的nonce参数是否在该“集合”中,如果存在则认为是非法请求。 然而对服务器来说,永久保存nonce的代价是非常大的。可以结合timestamp来优化。因为timstamp参数对于超过3min的请求,都认为是非法请求,所以我们只需要存储3minnonce参数的“集合”即可。

方案 优点 缺点
nonce放到数据库 完全防重放 每次需要查询DB,速率慢;需要写定时任务清理nonce;改造量大
nonce放到本地缓存 速率快;缓存失效机制自动清理;改造少 假设有n台机器,重放攻击可以到达n次。

数据签名和验签

  • 数据加签:原始报文按Key值排序(ASCALL码),处理成类似a=1&b=2的字符串,再用 Hash算法(SM3)把原始请求参数生成报文摘要,就得到这个报文对应的数字签名sign(这个过程就是加签)。通常来说,请求方会把数字签名和报文原文密文一并发送给接收方。

  • 验签:接收方拿到原始报文和数字签名(sign)后,用**同一个 Hash 算法(**比如都用 SM3)从报文中生成摘要 A。对比 A 和 请求的数字签名 是否相同,就可以得知报文有没有被篡改过。