当前位置:   article > 正文

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

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);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

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);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94

4.5 : 登出

关键代码:

public Result loginOut(String token) throws Exception {
    SessionUtil.removeLoginFromSession(token);
    return Result.ok();
}
  • 1
  • 2
  • 3
  • 4

5 : 资源下载

Springboot项目报文加密(AES、RSA、Filter动态加密):https://blog.csdn.net/qq_38254635/article/details/135273863
CSDN下载:https://download.csdn.net/download/qq_38254635/87620796
百度网盘下载:https://pan.baidu.com/s/1v7Wvev9w0xS-8-VAMEGOmw?pwd=wu8n
提取码:wu8n

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/581770
推荐阅读
相关标签
  

闽ICP备14008679号