Springboot项目报文加密(采用AES、RSA动态加密策略)

1 : 痛点

在项目安全扫描中,发现仅采用秘钥加密的形式存在安全隐患。
如果秘钥泄露,那每一次的会话及请求的报文,则无异于暴露在外。
这种现象虽不太会发生,但秘钥一旦泄露,或被恶意攻破,会导致客户信息泄露,后果是很严重的。

2 : 思路

AES是对称加密,而RSA是非对称加密。
如果单采用AES对报文加密,虽然可行,但是存在风险,于是就想到了采用组合加密的形式。
具体思路:前后端互相交换RSA公钥、私钥。然后再进行AES秘钥交换。
目的:为每一次的会话,生成只有会话的前后端知道的AES秘钥,会话与会话之前秘钥不相同。
大致如下图:
在这里插入图片描述

3 : 前期准备

3.1 : AES加密

参考链接:https://blog.csdn.net/qq_38254635/article/details/129622075

3.2 : RSA加密

参考链接:https://blog.csdn.net/qq_38254635/article/details/129623413

3.3 : 登录验证码

参考链接:https://blog.csdn.net/qq_38254635/article/details/129735679

3.4 : BCrypt非对称加密

参考链接:https://blog.csdn.net/qq_38254635/article/details/129746320

4 : 代码设计

4.1 : 获取验证码

关键代码:

public Result getVerifyCode() throws IOException {
    
    
    String verifyCode = VerifyCodeUtils.generateVerifyCode(4);
    log.info("The current verification code is:" + verifyCode + "\n");
    String verifyKey = SessionUtil.getUUId();
    SessionUtil.getSession().setAttribute(VerifyCodeUtils.VERIFY_CODE,verifyCode + "_" + System.currentTimeMillis());
    redisUtil.delete(RedisBean.VERIFY_CODE + verifyKey);
    redisUtil.setEx(RedisBean.VERIFY_CODE + verifyKey, verifyCode, 300);
    ByteArrayOutputStream output = new ByteArrayOutputStream();
    VerifyCodeUtils.outputImage(VerifyCodeUtils.VERIFY_CODE_WIDE, VerifyCodeUtils.VERIFY_CODE_HIGH, output, verifyKey);
    String imageBase = Base64.getEncoder().encodeToString(output.toByteArray());
    Result<Map<String,Object>> result = new Result<>();
    result.setData(new HashMap<String,Object>(){
    
    {
    
    
        put("verifyKey", verifyKey);
        put("verifyIO", imageBase);
    }});
    return result;
}

4.2 : 获取RSA秘钥

关键代码:

public Result getRSAKey(){
    
    
    try {
    
    
        //first:generate RSA key pair
        String sessionKey = SessionUtil.getUUId();
        Map<String, Object> keyMap = RSAUtils.generateRSAKeyPair();
        String publicKey = RSAUtils.getRSAPublicKey(keyMap);
        String privateKey = RSAUtils.getRSAPrivateKey(keyMap);
        log.info("sessionKey:" + sessionKey + "\n" + "publicKey:" + publicKey + "\n" + "privateKey:" + privateKey);
        //second:save public key and private key to redis
        redisUtil.setEx(RedisBean.CLIENT_PUBLIC + sessionKey, publicKey,30, TimeUnit.MINUTES);
        redisUtil.setEx(RedisBean.CLIENT_PRIVATE + sessionKey, privateKey,30, TimeUnit.MINUTES);
        //third:return session key and public key to web
        Result<Map<String,Object>> result = new Result<>();
        result.setData(new HashMap<String,Object>(){
    
    {
    
    
            put("sessionKey", sessionKey);
            put("publicKey", publicKey);
        }});
        return result;
    } catch (Exception e){
    
    
        throw new RuntimeException("get RSA public key error", e);
    }
}

4.3 : 获取AES秘钥

关键代码:

public Result getAESKey(String param) throws Exception{
    
    
    Encrypt vo = JSON.parseObject(param, Encrypt.class);
    //first:decrypt param got web RSA public key.
    String sessionKey = vo.getSessionKey();
    String clientPrivateKey = redisUtil.get(RedisBean.CLIENT_PRIVATE + sessionKey);
    String webPublicKey = new String(RSAUtils.decryptByPrivateKey(Base64Utils.decode(vo.getEncryptedString()), clientPrivateKey));
    //second:generate AES key
    String aesKey = AesUtil.generateAESKey();
    redisUtil.setEx(RedisBean.AES_KEY + sessionKey, aesKey,30, TimeUnit.MINUTES);
    //third:use web RSA public key to encrypt AES key, return encrypted string
    String encryptedString = Base64Utils.encode(RSAUtils.encryptByPublicKey(aesKey.getBytes(), webPublicKey));
    return Result.success(encryptedString);
}

4.4 : 登录

关键代码:

public Result login(Encrypt encrypt){
    
    
    //1、使用sessionKey,从redis获取AES秘钥
    String aesKey = redisUtil.get(RedisBean.AES_KEY + encrypt.getSessionKey());
    //2、解密加密报文
    String json = AesUtil.decryptAes(encrypt.getEncryptedString(), aesKey);
    LoginVO vo = JSON.parseObject(json, LoginVO.class);
    if(null == vo) return Result.error("数据有误,请刷新当前页面");
    //3、校验验证码
    Result codeResult = this.checkVerifyCode(vo);
    if(codeResult.getCode() != 0) return codeResult;
    //4、校验账号密码
    SysUser user = sysUserService.queryOne(vo.getUserCode());
    if(StringUtils.isEmpty(user)) return Result.error("账号或密码有误,请核实!");
    if(!"1".equals(user.getStatus())) return Result.error("当前用户已失效,请核实!");
    //5、账号锁定功能
    Result lockResult = this.checkAccountLock(vo.getUserCode(), user.getPassword(), vo.getPassWord());
    if(lockResult.getCode() != 0) return codeResult;
    //6、初始化login信息
    LoginSession login = new LoginSession(user.getId(), user.getUserCode(), user.getUserName(),
            user.getOrgCode(), user.getStatus(), new Date(), aesKey);
    //7、生成token
    String token = SessionUtil.putLoginToSession(login, null);
    //8、清理redis中RSA、AES秘钥
    redisUtil.delete(RedisBean.CLIENT_PUBLIC + encrypt.getSessionKey());
    redisUtil.delete(RedisBean.CLIENT_PRIVATE + encrypt.getSessionKey());
    redisUtil.delete(RedisBean.AES_KEY + encrypt.getSessionKey());
    //9、返回前端参数
    Result<Map<String,Object>> result = new Result<>();
    result.setData(new HashMap<String,Object>(){
    
    {
    
    
        put("token", token);
        put("login", login);
    }});
    return result;
}

private Result checkVerifyCode(LoginVO vo){
    
    
    String code = redisUtil.get(vo.getVerifyCodeId());
    if(StringUtils.isEmpty(code)) code = (String) SessionUtil.getSession().getAttribute(VerifyCodeUtils.VERIFY_CODE);
    redisUtil.delete(vo.getVerifyCodeId());
    SessionUtil.getSession().removeAttribute(VerifyCodeUtils.VERIFY_CODE);
    if(!Optional.ofNullable(code).isPresent()) return Result.error(500, "验证码已失效");
    if(code.contains("_")){
    
    
        String[] splitCode = code.split("_");
        boolean isTimeOut = isTimeOut(Long.valueOf(splitCode[1]), 300L);
        if(isTimeOut) return Result.error(500, "验证码已失效");
        if(!vo.getVerifyCode().equalsIgnoreCase(splitCode[0])) return Result.error(500, "验证码输入有误");
    } else {
    
    
        if(!vo.getVerifyCode().equalsIgnoreCase(code)) return Result.error(500, "验证码输入有误");
    }
    return Result.ok();
}

private static boolean isTimeOut(Long var, Long intervalTime) {
    
    
    long nm = 1000;
    Date d1 = new Date(System.currentTimeMillis());
    Date d2 = new Date(var);
    long diff = d1.getTime() - d2.getTime();
    long minute = diff / nm;
    return minute > intervalTime;
}

private Result checkAccountLock(String userCode, String userPassword, String password){
    
    
    if(!"on".equals(LOCK_ACCOUNT_SWITCH)) {
    
    
        if(!PassWordUtil.check(password, userPassword))  return Result.error("账号或密码有误,请核实!");
        return Result.ok();
    }
    //校验已冻结帐号
    if (!StringUtils.isEmpty(redisUtil.get(RedisBean.FREEZE_ACCOUNT + userCode))) {
    
    
        Long time = redisUtil.getExpire(RedisBean.FREEZE_ACCOUNT + userCode);
        Long m = time/60;
        if(m == 0){
    
    
            return Result.error(500, "账号已锁定,请在" + time + "秒后重新登陆!");
        } else {
    
    
            return Result.error(500, "账号已锁定,请在" + m + "分钟后重新登陆!");
        }
    }
    if(!PassWordUtil.check(password, userPassword)){
    
    
        Long count = redisUtil.incrBy(RedisBean.FREEZE_COUNT + userCode, 1);
        //密码错误五次冻结
        if (count == 1) {
    
    
            redisUtil.expire(RedisBean.FREEZE_COUNT + userCode,1,TimeUnit.HOURS);
        }
        if (count >= WRONG_TIMES) {
    
    
            //冻结一小时
            redisUtil.setEx(RedisBean.FREEZE_ACCOUNT + userCode, "1", 3600);
            //重置次数
            redisUtil.delete(RedisBean.FREEZE_COUNT + userCode);
        }
        return Result.error("账号或密码有误,请核实!");
    }
    //重置错误验证次数
    if("on".equals(LOCK_ACCOUNT_SWITCH)) redisUtil.delete(RedisBean.FREEZE_COUNT + userCode);
    return Result.ok();
}

4.5 : 登出

关键代码:

public Result loginOut(String token) throws Exception {
    
    
    SessionUtil.removeLoginFromSession(token);
    return Result.ok();
}

5 : 资源下载

CSDN下载:https://download.csdn.net/download/qq_38254635/87620796
百度网盘下载:https://pan.baidu.com/s/1v7Wvev9w0xS-8-VAMEGOmw?pwd=wu8n
提取码:wu8n

猜你喜欢

转载自blog.csdn.net/qq_38254635/article/details/129275971