记难受的老项目进行安全扫描的坑:敏感数据加密传输和保存
首先我们先创建这么一个JAVA工具类,用里面的main方法来得到SM2加密时需要用到的公钥和私钥,需要注意的是,执行一次就拿到这两个值来放到另一个类中或者数据库中来永久使用,需要调用时从里面调用即可,记得保密!
package com.cn.zj.sm;
import java.math.BigInteger;
import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;
public class SMutil
{
public static class SM2Key{
public String publicKey; //公钥
public String privateKey; //私钥
}
private static boolean isEmpty(String str)
{
if(str==null || str.trim().equals("")){
return true;
}
return false;
}
/**
* 生成SM2秘钥
*/
@SuppressWarnings("deprecation")
public static SM2Key generateSM2Key(){
SM2 sm2 = SM2.Instance();
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger privateKey = ecpriv.getD();
ECPoint publicKey = ecpub.getQ();
SM2Key sm2Key = new SM2Key();
sm2Key.publicKey = Util.byteToHex(publicKey.getEncoded());
sm2Key.privateKey = Util.byteToHex(privateKey.toByteArray());
return sm2Key;
}
/**
* SM2加密
* @param publickey 公钥
* @param src 待加密的明文
* @return
*/
public static String sm2encrypt(String publickey, String str){
if(isEmpty(publickey) || isEmpty(str)){
return null;
}
try {
return SM2Utils.encrypt(Util.hexToByte(publickey), str.getBytes());
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* SM2解密
* @param privatekey 私钥
* @param src 待解密的密文
* @return
*/
public static String sm2decrypt(String privatekey, String str){
if(isEmpty(privatekey) || isEmpty(str)){
return null;
}
try {
return new String(SM2Utils.decrypt(Util.hexToByte(privatekey), Util.hexToByte(str)));
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* SM3加密
* @param str 待加密的明文
* @return
*/
public static String sm3encrypt(String str){
if(isEmpty(str)){
return null;
}
byte[] md = new byte[32];
byte[] msg1 = str.getBytes();
SM3Digest sm3 = new SM3Digest();
sm3.update(msg1, 0, msg1.length);
sm3.doFinal(md, 0);
return new String(Hex.encode(md));
}
/**
* sm3密码校验
* @param str 待校验的明文
* @param sm3Str sm3加密后的密文
* @return
*/
public static boolean sm3decrypt(String str, String sm3Str){
if(isEmpty(str) || isEmpty(sm3Str)){
return false;
}
return sm3Str.equalsIgnoreCase(sm3encrypt(str));
}
/**
* SM4加密
* @param str 待加密的明文
* @return
*/
public static String sm4encrypt(String str){
if(isEmpty(str)){
return null;
}
String ret = new SM4Utils().encryptData_ECB(str);
return ret;
}
/**
* SM4加密
* @param str 待解密的密文
* @return
*/
public static String sm4decrypt(String str){
if(isEmpty(str)){
return null;
}
String ret = new SM4Utils().decryptData_ECB(str);
return ret;
}
public static void main(String[] args){
SM2Key sm2Key = generateSM2Key();
System.out.println("私钥privateKey:"+sm2Key.privateKey);
System.out.println("公钥publicKey: "+sm2Key.publicKey);
}
}
这是一个在上面的工具类基础上写出的另一个更加功能完善的工具类,可以直接调用,公钥和私钥都已经放在上面
package com.cn.zj.sm;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.xquant.xqa.component.util.utils.StringUtil;
/**
* @program: SMHandler
* @description: 国密加密解密
* @author: zhangjun
* @create: 2021/03/30
**/
@Service
public class SMHandler {
//SM2私钥
private static final String privatekey = "24F2401757F59665DED44ECEC08C7314E14F74E95B0720D0FA03E7992440CF1A";
//SM2公钥
private static final String pubkeyHex = "04509AEB08F6645C6AC92B965A5EAA6EE7761908A4AE1454DA537A855F1ADF0D611D45486362D856D7DFB45688EC969B96FD32E5E008FFE9E74E4A424A422DF3AC";
private Logger logger = LoggerFactory.getLogger(SMHandler.class);
/**
* @Title: passWordCryptBySM2_3
* @Description: 解析前端传入密文,转译成数据库存储密文,
* 前端加密逻辑要求:密文 = SM2 (明文)+SM3 (明文)
* 存入数据库密文要求:密文 = SM3 (明文)
* @param str 前端传入密文
* @return: java.lang.String 密码明文
* @throws
*/
public String passWordCryptBySM2_3(String str){
//【1】分割传输过来的密文,得到 0.SM2加密过的明文 1.SM3算法加密后的密文
String[] strArr = StringUtil.split(str,"|");
if(ArrayUtils.isEmpty(strArr) || strArr.length != 2){
logger.info("完整性校验失败:"+ str);
return null;
}
//【2】用SM2私钥解密传输过来的密文,获取明文
String privateKey = privatekey;
String passWord = SMutil.sm2decrypt(privateKey,strArr[0]);
String passWd = SMutil.sm2decrypt(privateKey,strArr[1]);
//【3】完整性校验——判断明文与加密后的密文是否相同
// if(!SMutil.sm3decrypt(passWord,strArr[1])){
if(!SMutil.sm3decrypt(passWord,passWd)){
logger.info("完整性校验失败:"+ str);
return null;
}
// System.out.println("传入密文:"+str);
// System.out.println("SM2密钥加密密文:"+strArr[0]);
// System.out.println("SM3加密密文:"+strArr[1]);
// System.out.println("密码:"+passWord);
//【4】返回SM3加密后明文
// return strArr[1];
//【4】返回密码明文
return passWord;
}
/**
* @Title: passWordCryptBySM2_3_4
* @Description: 解析前端传入密文,转译成数据库存储密文,
* 前端加密逻辑要求:密文 = SM2 (明文+SM3 (明文))
* 存入数据库密文要求:密文 = SM4 (明文)
* @param str 前端传入密文
* @return: java.lang.String 数据库存储密文
* @throws
*/
public String passWordCryptBySM2_3_4(String str){
//【1】用SM2私钥解密传输过来的密文,得到SM3算法加密后的密文及明文
String privateKey = privatekey;
String mm = SMutil.sm2decrypt(privateKey,str);
//根据标识符切割【0】:SM3算法加密后的密文,【1】明文
String[] mmArr = StringUtil.split(mm,"|");
if(ArrayUtils.isEmpty(mmArr) || mmArr.length != 2){
logger.info("传入密文解密失败:"+ str);
return null;
}
String mw_sm3 = mmArr[0];
String passWord = mmArr[1];
//【2】完整性校验——判断明文与加密后的密文是否相同
if(!SMutil.sm3decrypt(passWord,mw_sm3)){
logger.info("完整性校验失败:"+ mw_sm3);
return null;
}
//【3】SM4加密明文,存入数据库
String mw_sm4 = SMutil.sm4encrypt(passWord);
// System.out.println("传入密文:"+str);
// System.out.println("SM2密钥解密后密文:"+mm);
// System.out.println("SM3加密后密文:"+mw_sm3);
// System.out.println("SM4加密后密文:"+mw_sm4);
// System.out.println("密码:"+passWord);
return mw_sm4;
}
/**
* @Title: accountDecryptBySM2
* @Description: 解析前端传入密文,转译成明文
* 前端加密逻辑要求:密文 = SM2 (明文)
* @param str
* @return: java.lang.String
* @throws
*/
public String accountDecryptBySM2(String str){
String privateKey = privatekey;
String mm = SMutil.sm2decrypt(privateKey,str);
if(mm == null){
logger.info("传入密文解密失败:"+ str);
return null;
}
return mm;
}
/**
* @Title: passWordEnryptBySM3
* @Description: 加密密码存入数据库
* 加密要求:密文 = SM3(明文)
* @param str
* @return: java.lang.String
* @throws
*/
public String passWordEnryptBySM3(String str){
String mm = SMutil.sm3encrypt(str);
if(mm == null){
logger.info("加密失败:"+ str);
return null;
}
return mm;
}
/**
* @Title: passWordEnryptBySM2
* @Description: 加密密码存入数据库
* 加密要求:密文 = SM2(公钥,明文)
* @param str
* @return
* @throws
*/
public String passWordEnryptBySM2(String str) {
String publicKey = ConfigSupport.getProperty("SM2.pubkeyHex", pubkeyHex);
String mm = SMutil.sm2encrypt(publicKey, str);
if(mm == null){
logger.info("加密失败:"+ str);
return null;
}
return mm;
}
/**
* @Title: passWordDecryptBySM2
* @Description: 返回解密后的明文
* 解密要求:明文 = SM2(私钥, 密文)
* @param str
* @return
* @throws
*/
public String passWordDecryptBySM2(String str) {
String privateKey = privatekey;
String passWord = SMutil.sm2decrypt(privateKey, str);
if(passWord == null){
logger.info("解密失败:"+ str);
return null;
}
return passWord;
}
public static void main(String[] args) {
String publicKey = ConfigSupport.getProperty("SM2.pubkeyHex", pubkeyHex);
String mm = SMutil.sm2encrypt(publicKey, "111111");
String str2 = "16ebf7bd3f730247ff2df62ddfe30201487c0c0ebbfb0b0326d2212735490287";
System.out.println(SMutil.sm2decrypt("00C1C7D8BF35BA59F4F0B28959EBBB1AAE6606140AAEAC4C379A8D57CCA508D16D", str2));
}
}
所以在java代码层面的加密和解密的代码只需要调用SMHandler这个工具类即可,代码如下:
//加密---begin
if(StringUtil.isNotEmpty(role.getRoleName())){
String name = sMHandler.passWordEnryptBySM2(role.getRoleName());
if(StringUtil.isEmpty(name)){
return new CommResponse(false,"操作失败");
}else{
role.setRoleName(name);
}
}
//加密---end
//解密---begin
if(StringUtil.isNotEmpty(role.getRoleName())){
String name = sMHandler.passWordDecryptBySM2(role.getRoleName());
if(StringUtil.isEmpty(name)){
return new CommResponse(false,"操作失败");
}else{
role.setRoleName(name);
}
}
//解密---end
接下来就是JS层面的加密解密,首先我们先找到sm.js这个文件,在里面写三个function方法,获取公钥私钥和加密解密,当然你也可以自己创建一个新的js文件,代码如下:
function getSm2pubkeyHex() {
var pubkey = '04509AEB08F6645C6AC92B965A5EAA6EE7761908A4AE1454DA537A855F1ADF0D611D45486362D856D7DFB45688EC969B96FD32E5E008FFE9E74E4A424A422DF3AC'
return pubkey;
}
function getSm2prikeyHex() {
var prikey = '24F2401757F59665DED44ECEC08C7314E14F74E95B0720D0FA03E7992440CF1A'
return prikey ;
}
function SMutil()
{
this.sm2encrypt = function(data, publickey){
var msgData = CryptoJS.enc.Utf8.parse(data);
var pubkeyHex = publickey;
if (pubkeyHex.length > 64 * 2) {
pubkeyHex = pubkeyHex.substr(pubkeyHex.length - 64 * 2);
}
var xHex = pubkeyHex.substr(0, 64);
var yHex = pubkeyHex.substr(64);
var cipher = new SM2Cipher("0");
var userKey = cipher.CreatePoint(xHex, yHex);
msgData = cipher.GetWords(msgData.toString());
var encryptData = cipher.Encrypt(userKey, msgData);
return '04' + encryptData;
}
this.sm3encrypt = function(str){
var sm3 = new window.SM3Digest();
str = new SM2Cipher().GetWords(CryptoJS.enc.Utf8.parse(str).toString());
sm3.BlockUpdate(str,0,str.length);
var md = new Array(32);
sm3.DoFinal(md, 0);
var words = [ ];
var j = 0;
for (var i = 0; i < md.length * 2; i += 2) {
words[i >>> 3] |= parseInt(md[j]) << (24 - (i % 8) * 4);
j++;
}
var encryptData = new CryptoJS.lib.WordArray.init(words, md.length).toString();
return encryptData;
}
this.sm2Decrypt = function(privateKey,encrypted) {
encrypted = encrypted.substr(2);
var privKey = new BigInteger(privateKey, 16);
var cipher = new SM2Cipher("0");
var decryptData = cipher.Decrypt(privKey, encrypted);
return decryptData;
}
那我们在js中加密解密方法就只需要调用sm文件里面这个方法即可,代码如下:
//加密---begin
var smutil = new SMutil();
var pubkey = smutil.getSm2pubkeyHex();
smutil.sm2encrypt(str, pubkey);
//加密---end
//解密---begin
var smutil = new SMutil();
var prikey = smutil.getSm2prikeyHex();
smutil.sm2Decrypt(prikey, str);
//解密---end
因为公钥和私钥在前端和后端的一致性,所以前端加密的数据可以在后端解密,后端加密的数据也可以在前端解密。这就是最简单快捷的国密SM2加密解密,当然我的方法中还有sm3和sm2混合加密解密,sm4加密解密,我就不一一介绍了