登录密码加密使用国密算法SM2和SM3
一、SM3和SM2算法
-
SM2是非对称算法,SM3是摘要算法,且都是国密算法,适应项目国产化趋势;
-
SM2在前端加密用户输入密码,保障密码在传输中的安全性;SM3是后端加密密码后存储到数据库,保障密码在数据库的安全性;
-
SM2私钥和SM3系统固定盐要做好保密工作;
二、算法基本使用
2.1 SM3算法使用
import cn.hutool.crypto.digest.SM3;
import cn.hutool.crypto.SmUtil;
String pwd = "123456";
SM3 sm3 = SmUtil.sm3();
System.out.println(sm3.digestHex(pwd));// 207cf410532f92a47dee245ce9b11ff71f578ebd763eb3bbea44ebd043d018fb
2.2 SM2算法使用
SM2算法可以产生不两种不同的公私钥,这里使用的这种是为了和前端国密加密包
sm-crypto相对应。另外需要注意的是解密
sm-crypto包SM2加密的字符串,必须在加密字符串补上04。
import cn.hutool.crypto.SmUtil;
import cn.hutool.crypto.asymmetric.SM2;
SM2 sm2 = SmUtil.sm2();
// 生成私钥
String privateKeyD = HexUtil.encodeHexStr(BCUtil.encodeECPrivateKey(sm2.getPrivateKey()));
// 生成公钥
String publicKeyQ = HexUtil.encodeHexStr(((BCECPublicKey) sm2.getPublicKey()).getQ().getEncoded(false));
System.out.println(privateKeyD);
System.out.println(publicKeyQ);
String plaintext = "123456";
// 公钥加密
String ciphertext = sm2.encryptHex(plaintext, KeyType.PublicKey);
System.out.println("ciphertext: " + ciphertext);
// 私钥解密
plaintext = sm2.decryptStr(ciphertext, KeyType.PrivateKey);
System.out.println(plaintext);
008b865f272b0614a5c0cc479dd3d4c471e938428d4f16f111ee727c26769c8478
047e1eaf5c02d569e6d6462e6bf1e747caa66c2b95ccbf5985a4177791ef316639ce689570a33503453f1d5555cc6a26379a45066a810f166d7b2dbef2d2ee1b78
ciphertext: 04f3f96c723f254d07a71cd19974ee6a80d57a3b75a1abd7848560d1731e6110c158cffe755609654b390ab88218b850e0b1c601c9a09c7e065a80b450bd839928adf769970ca20807ed734c5e431a24b38a805771efea11ff262ebacb56956541824021f53be3
123456
三、登录流程
-
前端从后端获取SM2的公钥
publicKey -
前端使用publicKey+SM2加密用户输入密码并传入,
transforPwd=SM2.encrypt(pwd,publicKey); -
后端通过SM2解密transforPwd,获取用户输入密码
pwd=SM2.decrypt(transforPwd,privateKey); -
用户密码加盐后通过SM3加密:
encryptPwd=SM3.digestHex(systemSalt+pwd+randomSalt); -
校验数据库数据:
encryptPwd == databasePwd -
密码正确,登录成功后并返回token
四、登录相关代码
4.1 前端引入国密算法包
npm安装:npm install --save sm-crypto
4.2 后端引入国密算法包
网上算法试了很多,各有缺点。为了方便统一,所以后端直接使用hutool工具包的国密算法,一共三个jar包。
hutool-core-5.8.10.jarhutool-crypto-5.8.10.jar``bcprov-jdk15on-1.70.jar`
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.10</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-crypto</artifactId>
<version>5.8.10</version>
</dependency>
4.3 前端SM2加密用户密码
// 引入SM2算法
const sm2 = require('sm-crypto').sm2
// 用户输入的密码
const pwd = '123456'
'// 公钥从获取
const publicKey = '04eb0805545199f5386b2fc84f0664e2227f9da57d2b8ce7ae421f2f65d66367e391efa9abfd00247bd34160b65abe9852885c991e81594772429b28289b00f3ad'
console.log(sm2.doEncrypt(pwd, publicKey))
// 0a0f22802de5345cec8760a02cd3bea24a0c9abed136e3932159e14c88a5ecd3be6562feb123cf0f7ad48d3bbacf839c7ea10b0998768224a81e36e594e9eab47a7927f73d182d9dade380d8a03b6fdc894139ac7057c42b89c1c2e60724c17d0d1ea7fa3fad
4.4 后端SM2解密传入密码
注意: 不能直接解密前端传入的加密字符串,必须在加密字符串前面补上
04后(04+‘xxx’)再解密,否则会报异常Exception in thread "main" java.lang.IllegalArgumentException: Invalid point encoding 0xa
String privateKeyD="008b865f272b0614a5c0cc479dd3d4c471e938428d4f16f111ee727c26769c8478";
// 这里用前端传入的加密密码,并在前面补上了04字符串
String transforPwd="040a0f22802de5345cec8760a02cd3bea24a0c9abed136e3932159e14c88a5ecd3be6562feb123cf0f7ad48d3bbacf839c7ea10b0998768224a81e36e594e9eab47a7927f73d182d9dade380d8a03b6fdc894139ac7057c42b89c1c2e60724c17d0d1ea7fa3fad";
// 生成公私钥的SM2对象如果是静态的则可以直接使用无需创建,
// 如果对象是非静态且不能引用,则使用私钥重新创建一个
SM2 sm2 = SmUtil.sm2(privateKeyD, null);
String pwd= sm2.decryptStr(transforPwd,KeyType.PrivateKey);
System.out.println(pwd);// 123456
4.5 使用SM3加密密码并校验
systemSalt系统固定盐,随机字符但在整个系统中唯一;用户独有的随机盐
randomSalt可以在数据库创建一个字段存一个随机字符,简单的也可以直接使用userId作为随机盐;
String systemSalt = "ssxx12@!@3assa";
String randomSalt = userId;
SM3 sm3 = SmUtil.sm3();
// 加密密码
String encryptPwd = sm3 .digestHex((systemSalt+ originPwd + randomSalt));
// 校验数据库密码
if(encryptPwd.equals(databasePwd)){
......
}