[Spring Boot]登录密码三种加密方式

简述

介绍其三种密码加密方法
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);
  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柒杉杉

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值