实现密码等明文的加密传输以及加密存储

需求:用户在登录时密码经常使用明文传输,在不安全的网络环境下,很容易造成密码泄露,而密码直接存储在数据库中,如果数据泄露,也会造成安全问题。

解决方法:前端给后端传输密码关键信息时,进行加密后再传输,后端解密验证,然后将密码加密后再存储到数据库中。

实现思路:

采用RSA非对称加密加密和解密密码传输,采用哈希加盐算法加密密码并存储

1.前端需要传输密码时,先向服务器获取一个加密公钥(加密密钥对由后端生成,以公钥为key,私钥为value存储在redis中)。

2.获取到公钥后,将密码进行加密,并将加密后的密码以及公钥一起传给后端。

3.后端根据公钥从redis中得到配对的密钥,然后对密码解密。

4.将密码加盐后进行哈希加密,然后把加密的盐和加密后的密码一起存入数据库。

5.下次比较时,只需要比较密码+盐进行hash计算后的值是不是和数据库的加密密码相同即可。

代码:

RSA加密与解密:

package com.zong.zongmail.config;

import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.codec.binary.Base64;
import org.springframework.stereotype.Repository;

import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @return
 * @Author: Zongmao on 2021/1/8 15:44
 * @description:非对称加密方法
 */
@Slf4j
@Repository
public class RSAConfig {

    /**
     * @Author ZongMao
     * @Description //使用Java标准库提供的算法生成密钥对
     * @Date 2021/1/8 15:49
     **/
    public Map createKeyPair() throws GeneralSecurityException {
        // 生成公钥/私钥对:
        KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA");
        //它的密钥有256/512/1024/2048/4096等不同的长度。长度越长,密码强度越大,当然计算速度也越慢。
        //此处我设置256会报错:RSA keys must be at least 512 bits long
        kpGen.initialize(512);
        KeyPair kp = kpGen.generateKeyPair();
        PublicKey publicKey = kp.getPublic();
        PrivateKey privateKey = kp.getPrivate();
        //将公钥和私钥转换为base64编码便于存储和返回至前端
        byte[] publicKeyByte = publicKey.getEncoded();
        byte[] privateKeyByte = privateKey.getEncoded();
        String publicKeyStr = Base64.encodeBase64String(publicKeyByte);
        String privateKeyStr = Base64.encodeBase64String(privateKeyByte);
        Map map = new HashMap();
        map.put("publicKeyStr", publicKeyStr);
        map.put("privateKeyStr", privateKeyStr);
        return map;
    }

    /**
     * @Author ZongMao
     * @Description
     * 使用私钥解密(前端加密后传入的base64编码的信息,base64编码的私钥),
     * return 解密后的密码
     * @Date 2021/1/8 16:29
     **/
    public String decrypt(String passwordBase64, String privateKeyStr) throws GeneralSecurityException{

        Cipher cipher = Cipher.getInstance("RSA");

        //将Base64编码的私钥转为Private类型
        byte[] privateKeyByte = Base64.decodeBase64(privateKeyStr);
        KeyFactory kf = KeyFactory.getInstance("RSA"); // or "EC" or whatever
        PrivateKey privateKey = kf.generatePrivate(new PKCS8EncodedKeySpec(privateKeyByte));

        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        //将base64编码的加密信息转为byte[]数据
        byte[] passwordByte = Base64.decodeBase64(passwordBase64);
        //获取解密后的byte[]数据
        byte[] resByte = cipher.doFinal(passwordByte);
        //将byte[]转为字符串,即为解密后的信息
        String resStr = new String(resByte, StandardCharsets.UTF_8);
        return resStr;
    }

}

 哈希加盐加密以及验证方法:

package com.zongmao.schoolsystem.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @return
 * @Author: Zongmao on 2021/1/8 16:56
 * @description:使用Md5对明文口令进行加密(加盐)
 */
@Slf4j
@Repository
public class HashConfigWithSalt {

    /**
     * @Author ZongMao
     * @Description //加盐进行hash加密
     * @Date 2021/1/8 16:56
     **/
    public String HashWithSalt(String message, String salt) throws NoSuchAlgorithmException, UnsupportedEncodingException {
        // 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("MD5");
        // 反复调用update输入数据:
        String endMessage = message + salt;
        md.update(endMessage.getBytes("UTF-8"));
        byte[] result = md.digest();
       return new BigInteger(1, result).toString(16);
    }

    /**
     * @Author ZongMao
     * @Description //比较是否相等(加密后的字符串,盐,需要验证的信息)
     * @Date 2021/1/8 16:59
     **/
    public boolean isCommon(String mdStr, String salt, String message) throws UnsupportedEncodingException, NoSuchAlgorithmException {
        String nowStr = HashWithSalt(message, salt);
        if (nowStr.equals(mdStr)){
            return true;
        }
        return false;
    }
}

 请求获取公钥:

@PostMapping("/common/getPublicKey")
    public FormatResultData getPublicKey(){
        log.info("请求获取加密公钥");
        String publicKey = "";
        try {
            Map map = rsaConfig.createKeyPair();
            publicKey = (String) map.get("publicKeyStr");
            String privateKey = (String) map.get("privateKeyStr");
            //将publicKey作为key,privateKey作为value存入redis,并且设置过期时间
            stringRedisTemplate.opsForValue().set(publicKey, privateKey, 5 * 60 * 1000, TimeUnit.MILLISECONDS);
        }catch (GeneralSecurityException e){
            log.error(e.toString());
            return FormatResultData.error("获取公钥失败", 0);
        }catch (Exception e){
            log.info(e.toString());
            return FormatResultData.systemError();
        }
        return FormatResultData.success("获取公钥成功", publicKey);
    }

前端加密密码:

(使用jsencrypt)

 import {JSEncrypt} from 'jsencrypt'
//加密
export function encryptedData(publicKey, data) {
    let encryptor = new JSEncrypt();
    /*此处可以指定公钥的编码,也可以不指定*/
    encryptor.setPublicKey(publicKey, "base64");
    return encryptor.encrypt(data);
}

//解密
export function decryptData(privateKey, data) {
    let decrypt = new JSEncrypt();
    decrypt.setPrivateKey(privateKey);
    return decrypt.decrypt(data);
}


//获取publicKey
        getPublicKey(){
            this.$http("/common/getPublicKey").then(res =>{
                if (res.code === 1){
                    this.loginForm.publicKey = res.data;
                    this.password = this.loginForm.password;
                    this.loginForm.password = encryptedData(this.loginForm.publicKey, this.password);
                    this.$http("/commonLogin/allUserLogin",this.loginForm).then(res =>{
                        if (res.code === 1){
                            this.Cookie.set("token", res.data, { expires: 1});
                            this.$store.commit("changeUserType", this.loginForm.userType);
                            localStorage.setItem("userName", res.extend);
                            localStorage.setItem("account", this.loginForm.account);
                            localStorage.setItem("userType", this.loginForm.userType);
                            this.$router.push(this.fromPath);
                        }else {
                            this.loginForm.password = this.password;
                            Message({
                                message: res.msg,
                                showClose: true,
                                type:"error"
                            });
                            this.getCode();
                        }
                    }).catch(err =>{
                        this.getCode();
                    })
                }else {
                    this.loginForm.publicKey = "";
                    Message({
                        message: "获取公钥失败,请稍后再试!",
                        showClose: true,
                        type:"error"
                    });
                }
            }).catch(err =>{

            })
        },

注册新增账号时加密存储:

 /**
     * @Author ZongMao
     * @Description //新增管理员账号
     * @Date 2021/1/12 16:05
     **/
    @Override
    public String addAccount(AdminUser adminUser) throws Exception{
        String account = adminUser.getAccount();
        if (!account.equals("admin")){
            log.info("非法用户名!");
            throw new IllegalArgumentException("非法用户名");
        }
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("account", account);
        AdminUser hasUser = adminUserMapper.selectOne(wrapper);
        if (null != hasUser) {
            throw new IllegalArgumentException("管理员账号已存在");
        }
        String password = adminUser.getPassword(); //前端传的加密后的代码
        String publicKey = adminUser.getPublicKey(); //加密使用的公钥
        String privateKey = stringRedisTemplate.opsForValue().get(publicKey); //从redis中取出对应的密钥
        if (null == privateKey){
            throw new IllegalArgumentException("安全校验失败!");
        }
        String resPassword = rsaConfig.decrypt(password, privateKey); //使用密钥解密得到真实的密码
        String salt = randomConfig.randomData("", 6, false); //随机生成一个盐
        String hashPassword = hashConfigWithSalt.HashWithSalt(resPassword, salt); //把密码加盐后hash加密
        adminUser.setPassword(hashPassword); //把加密后的密码以及盐存入数据库
        adminUser.setSalt(salt);
        String id = UUID.randomUUID().toString();
        adminUser.setId(id);
        int addUser = adminUserMapper.insert(adminUser);
        if (addUser == 0){
            log.info("新增失败!");
            throw new IllegalArgumentException("新增失败!");
        }
        log.info("新增成功!");
        stringRedisTemplate.delete(publicKey);//新增成功后删除密钥对
        return "新增成功";
    }

 在登录情况下进行验证:

/**
     * @Author ZongMao
     * @Description //登录(adminUser)
     * @Date 2021/1/12 11:57
     **/
    @Override
    public String login(String account, String password) throws Exception{
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("account", account);
        AdminUser adminUser = adminUserMapper.selectOne(wrapper); //从数据库中查出对应的用户
        if (null == adminUser){
            throw new IllegalArgumentException("账号不存在!");
        }
        Date lockTime = adminUser.getLockTime();
        if (null != lockTime){
            lockAccount.checkLock(lockTime);
        }
        String salt = adminUser.getSalt(); //从数据库中得到盐
        String okPassword = adminUser.getPassword(); //数据库中存储的加密的密码
        boolean okLogin = hashConfigWithSalt.isCommon(okPassword, salt, password); //比较是否相等,password已经是解密了的
        if (!okLogin){
            boolean needLock = lockAccount.lockTime(account);
            if (needLock){
                Date date = new Date();
                date.setTime(date.getTime() + needLockTime * 60 * 1000);
                adminUser.setLockTime(date);
                UpdateWrapper<AdminUser> updateWrapper = new UpdateWrapper<>();
                updateWrapper.eq("account", account)
                        .set("lock_time", date);
                int i = adminUserMapper.update(adminUser, wrapper);
                if (i == 1){
                    throw new IllegalArgumentException("你的账号已被锁定,请在" + needLockTime + "分钟后重试");
                }
                throw new IllegalArgumentException("你已多次尝试登录失败,账号可能会被锁定");
            }
            throw new IllegalArgumentException("账号或密码错误!");
        }
        String userId = adminUser.getId();
        String token = tokenConfig.createToken(userId);
        return token;
    }

 

  • 0
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值