简述
介绍其三种密码加密方法
1.SM2加密与验签
2.随机密码盐加密
3.MD5加密
推荐使用方法1,其次使用方法2,最不推荐的是方法3。方法3极其容易被密码字典破解,如果项目进行安全测试,通常是不允许的加密方式。
SM2加密与验签
引入bcprov,以使用SM2加密。
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15to18</artifactId>
<version>1.69</version>
</dependency>
工具类与测试方法
加密的主要工具类如下,其中带有测试的main方法。
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Map;
/**
* sm2算法与签名的使用
*
* @author fir
* @date 2024/7/23 14:22
*/
@Slf4j
public class Sm2SignatureUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static final String PUBLIC_KEY = "publicKey";
public static final String PRIVATE_KEY = "privateKey";
/**
* 生成国密公私钥对
*/
public static Map<String, String> generateSmKey() throws Exception {
KeyPairGenerator keyPairGenerator;
SecureRandom secureRandom = new SecureRandom();
ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
keyPairGenerator = KeyPairGenerator.getInstance("EC", new BouncyCastleProvider());
keyPairGenerator.initialize(sm2Spec);
keyPairGenerator.initialize(sm2Spec, secureRandom);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
PublicKey publicKey = keyPair.getPublic();
String publicKeyStr = new String(Base64.getEncoder().encode(publicKey.getEncoded()));
String privateKeyStr = new String(Base64.getEncoder().encode(privateKey.getEncoded()));
return Map.of(PUBLIC_KEY, publicKeyStr, PRIVATE_KEY, privateKeyStr);
}
/**
* 将Base64转码的公钥串,转化为公钥对象
*/
public static PublicKey createPublicKey(String publicKey) {
PublicKey publickey = null;
try {
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(Base64.getDecoder().decode(publicKey));
KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
publickey = keyFactory.generatePublic(publicKeySpec);
} catch (Exception e) {
log.error("将Base64转码的公钥串,转化为公钥对象异常:{}", e.getMessage(), e);
}
return publickey;
}
/**
* 将Base64转码的私钥串,转化为私钥对象
*/
public static PrivateKey createPrivateKey(String privateKey) {
PrivateKey publickey = null;
try {
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
KeyFactory keyFactory = KeyFactory.getInstance("EC", new BouncyCastleProvider());
publickey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (Exception e) {
log.error("将Base64转码的私钥串,转化为私钥对象异常:{}", e.getMessage(), e);
}
return publickey;
}
/**
* 根据publicKey对原始数据data,使用SM2加密
*/
public static String encrypt(byte[] data, String publicKeyBase64) {
PublicKey publicKey = createPublicKey(publicKeyBase64);
ECPublicKeyParameters localEcPublicKeyParameters = getEcPublicKeyParameters(publicKey);
SM2Engine localSm2Engine = new SM2Engine();
localSm2Engine.init(true, new ParametersWithRandom(localEcPublicKeyParameters, new SecureRandom()));
byte[] arrayOfByte2;
try {
arrayOfByte2 = localSm2Engine.processBlock(data, 0, data.length);
return Base64.getEncoder().encodeToString(arrayOfByte2);
} catch (InvalidCipherTextException e) {
log.error("SM2加密失败:{}", e.getMessage(), e);
return null;
}
}
private static ECPublicKeyParameters getEcPublicKeyParameters(PublicKey publicKey) {
ECPublicKeyParameters localEcPublicKeyParameters = null;
if (publicKey instanceof BCECPublicKey localEcPublicKey) {
ECParameterSpec localEcParameterSpec = localEcPublicKey.getParameters();
ECDomainParameters localEcDomainParameters = new ECDomainParameters(localEcParameterSpec.getCurve(),
localEcParameterSpec.getG(), localEcParameterSpec.getN());
localEcPublicKeyParameters = new ECPublicKeyParameters(localEcPublicKey.getQ(), localEcDomainParameters);
}
return localEcPublicKeyParameters;
}
/**
* 根据privateKey对加密数据encode data,使用SM2解密
*/
public static String decrypt(String encodeBase64, String privateKeyBase64) {
SM2Engine localSm2Engine = new SM2Engine();
PrivateKey privateKey = createPrivateKey(privateKeyBase64);
BCECPrivateKey sm2PriK = (BCECPrivateKey) privateKey;
byte[] encodeData = Base64.getDecoder().decode(encodeBase64);
ECParameterSpec localEcParameterSpec = sm2PriK.getParameters();
ECDomainParameters localEcDomainParameters = new ECDomainParameters(localEcParameterSpec.getCurve(),
localEcParameterSpec.getG(), localEcParameterSpec.getN());
ECPrivateKeyParameters localEcPrivateKeyParameters = new ECPrivateKeyParameters(sm2PriK.getD(),
localEcDomainParameters);
localSm2Engine.init(false, localEcPrivateKeyParameters);
try {
byte[] result = localSm2Engine.processBlock(encodeData, 0, encodeData.length);
return new String(result);
} catch (InvalidCipherTextException e) {
log.error("SM2解密失败:{}", e.getMessage(), e);
return null;
}
}
/**
* 私钥,数据,生成签名
*/
public static String signByPrivateKey(String dataStr, String privateKeyBase64) throws Exception {
PrivateKey privateKey = createPrivateKey(privateKeyBase64);
byte[] data = Base64.getDecoder().decode(dataStr);
Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
sig.initSign(privateKey);
sig.update(data);
byte[] sign = sig.sign();
return Base64.getEncoder().encodeToString(sign);
}
/**
* 公钥与签名验证数据合法性
*/
public static boolean verifyByPublicKey(String dataStr, String publicKeyBase64, String signatureBase64) throws Exception {
PublicKey publicKey = createPublicKey(publicKeyBase64);
byte[] signature = Base64.getDecoder().decode(signatureBase64);
byte[] data = Base64.getDecoder().decode(dataStr);
Signature sig = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString(), BouncyCastleProvider.PROVIDER_NAME);
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(signature);
}
public static void test() throws Exception {
// 生成公私钥对
Map<String, String> keys = generateSmKey();
String publicKey = keys.get(PUBLIC_KEY);
String privateKey = keys.get(PRIVATE_KEY);
String testStr = "123456";
System.out.println("原始字符串:" + testStr);
System.out.println("公钥:" + keys.get(PUBLIC_KEY));
System.out.println("私钥:" + keys.get(PRIVATE_KEY));
System.out.println();
// 公钥加密
String encrypt = encrypt(testStr.getBytes(), publicKey);
System.out.println("加密数据:" + encrypt);
// 私钥签名,后续根据数据与公钥验签
String sign = signByPrivateKey(encrypt, privateKey);
System.out.println("数据签名:" + sign);
// 公钥验签,验证通过后再进行数据解密
boolean b = verifyByPublicKey(encrypt, publicKey, sign);
System.out.println("数据验签:" + b);
//私钥解密
String decrypt = decrypt(encrypt, privateKey);
System.out.println("解密数据:" + decrypt);
}
public static void uesCase() throws Exception {
//生成公私钥对
String publicKey = "MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEsrdE0XrAO2S7Ize0tm0r3diH9cPH23t0J9yVDtiVux6g71msH5YGTWW6/ogQSCVJ4iaofgCS/ly5+wkXa+/IGg==";
String privateKey = "MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgNxb+Jcu1vhGt9UEbeFUYeCC+RWL7+sfUL1vnhBp2KtKgCgYIKoEcz1UBgi2hRANCAASyt0TResA7ZLsjN7S2bSvd2If1w8fbe3Qn3JUO2JW7HqDvWawflgZNZbr+iBBIJUniJqh+AJL+XLn7CRdr78ga";
String testStr = "123456";
System.out.println("原始字符串:" + testStr);
System.out.println("公钥:" + publicKey);
System.out.println("私钥:" + privateKey);
System.out.println();
//公钥加密
String encrypt = encrypt(testStr.getBytes(), publicKey);
System.out.println("加密数据:" + encrypt);
// 私钥签名,后续根据数据与公钥验签
String sign = signByPrivateKey(encrypt, privateKey);
System.out.println("数据签名:" + sign);
//公钥验签,验证通过后再进行数据解密
boolean b = verifyByPublicKey(encrypt, publicKey, sign);
System.out.println("数据验签:" + b);
//私钥解密
String decrypt = decrypt(encrypt, privateKey);
System.out.println("解密数据:" + decrypt);
}
public static void main(String[] args) {
try {
test();
// uesCase();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
使用案例
验证密码
接受到用户输入的用户与密码之后,在数据库中查询出旧的密码,并进行旧密码进行验签、解密。解密后判断用户输入的密码与数据库存储的密码是否相同。
// 验证密码是否正确
String password = "123456";
String passwordOld = user.getPassword();
String signature = user.getSignature();
String decryptPasswordOld = null;
try {
// 公钥验签,查看数据与签名是否有效
boolean b = Sm2SignatureUtils.verifyByPublicKey(passwordOld, public, signature);
if(!b){
throw new CommonException("数据损坏");
}
decryptPasswordOld = Sm2SignatureUtils.decrypt(passwordOld, private);
}catch (Exception e){
throw new CommonException("数据损坏");
}
if (decryptPasswordOld == null || !decryptPasswordOld.equals(password)) {
throw new CommonException("用户密码错误");
}
修改密码
接收到用户的密码后,根据公私要生成加密数据串与私钥签名。并存储到数据库,用于之后的密码验证。
// 公钥加密
String password = "123456";
String encrypt = Sm2SignatureUtils.encrypt(password.getBytes(), public);
// 根据数据与私钥,生成私钥签名
String sign = Sm2SignatureUtils.signByPrivateKey(encrypt, private);
user.setPassword(encrypt);
user.setSignature(sign);
随机密码盐加密
工具类与测试方法
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* @author fir
* @date 2024/7/25 12:22
*/
public class SaltUtils {
/**
* 生成随机安全盐
*
* @return 盐
*/
public static String generateSalt() {
// 使用SecureRandom生成安全的随机盐
SecureRandom random = new SecureRandom();
byte[] salt = new byte[16];
random.nextBytes(salt);
return Base64.getEncoder().encodeToString(salt);
}
/**
* 生成加盐密码
*
* @return 盐
*/
public static String hashPassword(String password, String salt){
// 将密码和盐结合
String saltedPassword = password + salt;
// 使用SHA-256进行哈希
MessageDigest md;
try {
md = MessageDigest.getInstance("SHA-256");
}catch (NoSuchAlgorithmException e){
throw new RuntimeException("加密盐处理失败");
}
byte[] hashedBytes = md.digest(saltedPassword.getBytes());
// 将哈希值转换为字符串
return Base64.getEncoder().encodeToString(hashedBytes);
}
public static void main(String[] args){
String salt = generateSalt();
String password = "123456";
String hashPassword = hashPassword(password, salt);
System.out.println("盐:" + salt);
System.out.println("密码:" + password);
System.out.println("加密密码:" + hashPassword);
}
}
使用案例
验证密码
查询出用户的密码,将用户输入的密码盐加密,并判断与数据库的加密密码是否一致。
String password = "123456";
String salt = user.getSalt();
String passwordOld = user.getPassword();
String mPassword = SaltUtils.hashPassword(password, salt);
if (!mPassword.equals(passwordOld)) {
throw new CommonException("密码错误");
}
修改密码
将用户输入的密码md5加密之后,存在数据库中,用于之后的密码验证
String password = "123456";
String salt = SaltUtils.generateSalt();
String hashPassword = SaltUtils.hashPassword(password, salt);
user.setPassword(hashPassword);
MD5加密
引入hutool包,以使用md5加密。
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.19</version>
</dependency>
测试方法
import cn.hutool.crypto.digest.MD5;
/**
* @author fir
* @date 2024/7/22 10:07
*/
public class Md5Utils {
public static void main(String[] args){
// MD5取值
String mPassword = MD5.create().digestHex("123456");
System.out.println(mPassword);
}
}
使用案例
验证密码
查询出用户的密码,将用户输入的密码MD5加密,并判断与数据库的加密密码是否一致。
String password = "123456";
String mPassword = MD5.create().digestHex(password);
String passwordOld = user.getPassword();
if (!mPassword.equals(passwordOld)) {
throw new CommonException("密码错误");
}
修改密码
将用户输入的密码md5加密之后,存在数据库中,用于之后的密码验证
String password = "123456";
String passwordMd5 = MD5.create().digestHex(password);
user.setPassword(passwordMd5);