登录密码加密使用国密算法SM2和SM3

3,315 阅读5分钟

登录密码加密使用国密算法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

三、登录流程

  1. 前端从后端获取SM2的公钥publicKey

  2. 前端使用publicKey+SM2加密用户输入密码并传入,transforPwd=SM2.encrypt(pwd,publicKey);

  3. 后端通过SM2解密transforPwd,获取用户输入密码pwd=SM2.decrypt(transforPwd,privateKey);

  4. 用户密码加盐后通过SM3加密:encryptPwd=SM3.digestHex(systemSalt+pwd+randomSalt);

  5. 校验数据库数据: encryptPwd == databasePwd

  6. 密码正确,登录成功后并返回token

四、登录相关代码

4.1 前端引入国密算法包

GitHub - JuneAndGreen/sm-crypto: 国密算法js版

npm安装npm install --save sm-crypto

4.2 后端引入国密算法包

网上算法试了很多,各有缺点。为了方便统一,所以后端直接使用hutool工具包的国密算法,一共三个jar包。hutool-core-5.8.10.jar hutool-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)){
    ......
}