数据加密复合混用
AES加密算法和SM加密算法
AES
高级加密标准(AES,Advanced Encryption Standard)为最常见的对称加密算法(微信小程序加密传输就是用这个加密算法的)。对称加密算法也就是加密和解密用相同的密钥。
发送方:明文通过密钥配合加密算法加密成密文;
接收方:收到密文通过相同的密钥配合解密算法解密成明文
SM
国产国密算法有很多种,其中SM1、SM4、SM7、祖冲之密码(ZUC)是对称算法;SM2、SM9是非对称算法;SM3是哈希算法。
这里简单解释一下sm2 和sm4两种,因为在后续的混合加解密过程中有使用到
1、 SM2椭圆曲线公钥密码算法
SM2算法就是ECC椭圆曲线密码机制,但在签名、密钥交换方面不同于ECDSA、ECDH等国际标准,而是采取了更为安全的机制。另外,SM2推荐了一条256位的曲线作为标准曲线。
SM2标准包括总则,数字签名算法,密钥交换协议,公钥加密算法四个部分,并在每个部分的附录详细说明了实现的相关细节及示例。
SM2算法主要考虑素域Fp和F2m上的椭圆曲线,分别介绍了这两类域的表示,运算,以及域上的椭圆曲线的点的表示,运算和多倍点计算算法。然后介绍了编程语言中的数据转换,包括整数和字节串,字节串和比特串,域元素和比特串,域元素和整数,点和字节串之间的数据转换规则。
//重点:sm2加密算法是非对称算法,加密与解密使用的密钥并不是同一个
2 SM4对称算法
该算法的分组长度为128比特,密钥长度为128比特。加密算法与密钥扩展算法都采用32轮非线性迭代结构。解密算法与加密算法的结构相同,只是轮密钥的使用顺序相反,解密轮密钥是加密轮密钥的逆序。
此算法采用非线性迭代结构,每次迭代由一个轮函数给出,其中轮函数由一个非线性变换和线性变换复合而成,非线性变换由S盒所给出。其中rki为轮密钥,合成置换T组成轮函数。轮密钥的产生与上图流程类似,由加密密钥作为输入生成,轮函数中的线性变换不同,还有些参数的区别。SM4算法的具体描述和示例见SM4标准。
3 SM3杂凑算法
SM3密码杂凑(哈希、散列)算法给出了杂凑函数算法的计算方法和计算步骤,并给出了运算示例。**此算法适用于商用密码应用中的数字签名和验证,消息认证码的生成与验证以及随机数的生成,可满足多种密码应用的安全需求。**在SM2,SM9标准中使用。
此算法对输入长度小于2的64次方的比特消息,经过填充和迭代压缩,生成长度为256比特的杂凑值,其中使用了异或,模,模加,移位,与,或,非运算,由填充,迭代过程,消息扩展和压缩函数所构成。具体算法及运算示例见SM3标准。
对称加密算法
加密和解密用到的密钥是相同的,这种加密方式加密速度非常快,适合经常发送数据的场合。缺点是密钥的传输比较麻烦。
非对称加密算法
加密和解密用的密钥是不同的,这种加密方式是用数学上的难解问题构造的,通常加密解密的速度比较慢,适合偶尔发送数据的场合。优点是密钥传输方便。常见的非对称加密算法为RSA、ECC和EIGamal。
对SM国密加密的复合加密包装
从上面的文章钟我们了解到sm4对称加密是比较复合我们的业务场景的,政务领域也只能使用国密算法,但是考虑到安全性,密钥在传输过程中容易被拦截,导致密钥被窃取,从而影响数据安全,所以我们考虑使用更为安全复杂的sm2非对称加密算法,不过非对称算法解析过于缓慢也不符合我们的业务要求,否则容易造成接口缓慢请求超时,所以我们想到一个折中的方式,我们的数据加密使用sm4,但是我们传输sm4密钥的时候使用sm2对密钥进行加密,然后后台接收到我们的数据后先解密密钥,再用密钥解析数据,这样就算别人在传输过程中获取到了我们的密钥也无法使用,接下来就让我们实现吧
1、后台生成sm2的加密密钥
通过接口把生成的密钥发送到前台(sm2再生成密钥的时候会同时生成配套的一个加密密钥(公钥)一个解密密钥(私钥)):
if (this.$configApiUrl.VUE_APP_IS_ENCRYPT) {
// 获取公钥用于秘钥加密
this.$api.getpub.getpub().then(resp => {
console.log(resp)
const msg = rstr('this.form.userName')
localStorage.setItem('sec', msg)
const sm2 = require('sm-crypto').sm2
const cipherMode = 1 // 1 - C1C3C2,0 - C1C2C3,默认为1
const publicKey = resp // 公钥
const encryptData = '04' + sm2.doEncrypt(msg, publicKey, cipherMode) // 加密结果
setsm4(encryptData)
this.unlogin()
})
} else {
this.unlogin()
}
2、前台随机获取sm4加密秘钥
前台在获取到sm2公钥的时候通过rstr()函数在前台生成sm4的密钥并加密存放到cookie里,然后前端自己保存一份sm4密钥到localStorage
// 随机获取sm4加密秘钥
export function rstr (name) {
const sm3 = require('sm-crypto').sm3
const $chars = sm3(Date.parse(new Date()) + name) // 杂凑
const maxPos = $chars.length
let str = ''
for (let i = 0; i < 16; i++) {
str += $chars.charAt(Math.floor(Math.random() * maxPos))
}
return str
}
rstr()里面使用sm3生成一串随机数再对传入参数进行随机编码生成sm4的密钥
接下来在我们发送请求的时候会把存放到cookie里的被加密过的sm4密钥存放到请求heade里面,如下:
const sm4 = getsm4()
if (sm4) {
config.headers.sec = getsm4() // 让每个请求携带自定义sm4
}
后台在获取请求数据的时候会先获取heade里面的sec参数,然后使用之前生成的sm2私钥进行解密获取到sm4密钥,然后再使用这个密钥对数据进行解密,得到明文参数
注意:sm4加密的时候模式要和后端协商一致:mode
const SM4 = require('gm-crypt').sm4
const sm4Config = {
// 配置sm4参数
key: localStorage.getItem('sec'), //
mode: 'ecb', // 加密的方式有两种,ecb和cbc两种,也是看后端如何定义的,不过要是cbc的话下面还要加一个iv的参数,ecb不用
cipherType: 'base64' //
}
sm4的加密解密方法:
const sm4 = new SM4(sm4Config) //
const datas = sm4.decrypt(response.data.data) // 解密
const datas = sm4.encrypt(response.data.data) // 加密
在我们的工程里前端还有一个控制字段,判断是否加密
#是否加密的开关 //true => 加密 false => 不加密
VUE_APP_IS_ENCRYPT = false
解密判断写在axios文件夹index里面:
这里除了要判断是否打开加密开关还要把获取sm2公钥的接口放开,因为在调用这个接口的时候我们并没有做加密工作
// 加密开关
const is_encrypt = vm.$configApiUrl?.VUE_APP_IS_ENCRYPT
console.log('is_encrypt', is_encrypt)
// 二进制下载处理
if (response.config.responseType === 'blob' && response.status === 200) {
return Promise.resolve(response)
}
const star = response.config.url.lastIndexOf('/')
const end = response.config.url.length
const urls = response.config.url.substring(star, end)
if (urls === 'serverconfig.json') {
return Promise.resolve(response.data)
}
if (urls !== '/getpub' && is_encrypt) { //
}else{
}
加密判断写在axios文件夹function里面:只针对post接口进行加密,这里单独封装了方法存放在common公共方法里面
// sm4加密
export function sm4_encryption (params) {
if (!new Vue().$configApiUrl.VUE_APP_IS_ENCRYPT) {
return params
}
const SM4 = require('gm-crypt').sm4
const sm4Config = {
// 配置sm4参数
key: localStorage.getItem('sec'), //
mode: 'ecb', // 加密的方式有两种,ecb和cbc两种,也是看后端如何定义的,不过要是cbc的话下面还要加一个iv的参数,ecb不用
cipherType: 'base64' //
}
const sm4 = new SM4(sm4Config)// 这里new一个函数,将上面的sm4Config作为参数传递进去。然后就可以开心的加密了
const Account = sm4.encrypt(JSON.stringify(params)) // 账号加密
const data = {
data: Account
}
return data
}
另外在url拼接参数里面有单独参数encrypt可以告知后台我们哪些接口需要单独不加密
encrypt=0表示前端不加密后端也不加密
encrypt=0表示前端不加密后端加密