系统数据加密传输的实现

文章目录

1、背景

用户在浏览器发送请求数据到后台系统,期间数据在网络传输,如果这些数据是敏感数据,被恶意拦截时,就有安全问题,造成用户密码泄漏等等。

如此,可考虑使用非对称加密或者对称加密,给前端一个公钥,让前端把数据用公钥加密后传到后端,后端负责解密得到原始数据后再处理请求,如此,即使被恶意拦截,也无法得到真实密码。

对称加密即:文件的加密和解密都是使用相同的密钥。加密方和解密方使用同一把钥匙。

对称加密的优点是加密速度快,缺点是相对不安全(如果别人知道你用的哪种加密算法且密钥泄漏,则一切形同虚设)。

非对称加密即:两个密钥,公钥用来加密,私钥用来解密

和对称加密相比,安全性更高,但加解密更慢,数据量小时可采用。对称加密和非对称加密这两种思想,市面上有多种不同的具体落地的算法。对称加密如AES,非对称加密如RSA。 具体选择:

  • 文件很大建议使用对称加密
  • 文件较小,要求安全性高,建议采用非对称加密

2、需求

对系统中敏感数据进行加密,保证数据安全。敏感数据包括:用户密码、用户手机号、用户邮箱、Nacos配置中各个中间件的密码。加密方向包括:

  • 数据加密传输:前端调用后端接口时,先用公钥对密码进行加密,再使用base64编码,然后传输
  • 数据加密存储:后端落库时,base64解码,再用私钥解密,对明文密码要经过Bcrypt等不可逆加密算法加密后保存,防止被拖库

3、实现思路

3.1 密码加密

这里使用非对称加密实现更合理。针对以上要加密的数据,用户密码处理的流程图如下:修改注册后端接口,用户注册时,提交密码,前端用公钥对密码进行加密后,传到后端服务器。后端接口中用私钥对密码进行解密,实现加密传输。解密后,再对解密后的明文密码进行加密,存入数据库,实现加密存储。项目中用到了Spring Security框架,所以这里用Bcrypt算法进行加密存储,Bcrypt也可防止彩虹表破解。

对邮箱名、手机号等信息,可非对称加密,也可使用AES对称加密,实现加密传输,加密落库则可有可无,如果选择了加密落库,可能会影响到之前的userList接口等等,总之明文、密文别转换叉了。

3.2 密码解密

修改后端登录接口,登录时,前端传来的密码,解密后传到SpringSecurity框架,如果账户是加密传输的,也需解密,因为框架里要loadUserByUsername,用户名得转换过来。

流程图:

3.3 nacos密码加密

项目中,用Nacos做配置管理,很多中间件,如MySQL、Redis的密码都明文存储在配置文件中,考虑改为密文存储。SpringBoot服务启动时,去Nacos拉取配置、注册服务信息。改为密文后,需要先解密,才能连接中间件成功,实现这个可以考虑加一个Filter过滤器或者AOP,在读配置文件时,判断如果是密文,则解密后重新赋值。这里直接用已有的开源实现:Jasypt

java 复制代码
//官方文档:
https://github.com/ulisesbocchio/jasypt-spring-boot
//源码解析:
https://blog.csdn.net/u013905744/article/details/86508236

大致看了下,实现思路是借助SpringBoot Bean加载的扩展点,做一个过滤器,如果读到的内容是以Jasypt指定的前后缀ENC(),则解密后重新赋值:

4、相关工具类

4.1 非对称加密RSA

加密和解密的方法:

java 复制代码
import org.apache.tomcat.util.codec.binary.Base64;

import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 * 使用Cipher类实现RSA加密解密
 **/
public class RSAUtil {

    /**
     * 私钥
     */
    private static final String privateKey = "";
    /**
     * 公钥
     */
    private static final String publicKey = "";
    /**
     * 编码字符集
     */
    public static final String CHARSET = "UTF-8";
    /**
     * 算法定义
     */
    public static final String RSA_ALGORITHM = "RSA";

    /**
     * RSA公钥加密
     *
     * @param str 加密字符串
     * @return 返回加密字符串的base64值
     */
    public static String encrypt(String str) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密并base64编码
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, pubKey);
        return Base64.encodeBase64String(cipher.doFinal(str.getBytes(CHARSET)));
    }

    /**
     * RSA私钥解密
     *
     * @param str 加密字符串
     * @return 返回解密后的明文
     * @throws Exception 解密过程中的异常信息
     */
    public static String decrypt(String str) throws Exception {
        //64位解码加密后的字符串
        byte[] inputByte = Base64.decodeBase64(str.getBytes(CHARSET));
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(RSA_ALGORITHM).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, priKey);
        return new String(cipher.doFinal(inputByte));
    }

}

前端RSA加密:

javascript 复制代码
// 安装jsencrypt
// npm i jsencrypt -S
 
import JSEncrypt from 'jsencrypt/bin/jsencrypt.min'
 
//公钥
const publicKey = ''
//私钥
const privateKey = ''
  
// 加密
export function encrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPublicKey(publicKey) // 设置公钥
  return encryptor.encrypt(txt) // 对数据
}
  
// 解密(暂无使用)
export function decrypt(txt) {
  const encryptor = new JSEncrypt()
  encryptor.setPrivateKey(privateKey) // 设置私钥
  return encryptor.decrypt(txt) // 对数据进行解密
}

4.2 对称加密AES

加密和解密的方法:

java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.Base64Utils;

import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;

/**
 * AES加密工具类
 */
@Slf4j
public class AESUtil {

    /**
     * 编码
     */
    private static final String ENCODING = "UTF-8";
    /**
     * 算法定义
     */
    private static final String AES_ALGORITHM = "AES";
    /**
     * 指定填充方式
     */
    private static final String CIPHER_PADDING = "AES/ECB/PKCS5Padding";

    /**
     * 密码
     */
    private static final String AES_KEY = "your-private-key-xx";


    /**
     * AES加密
     *
     * @param content 待加密内容
     * @return 加密后内容的base64值
     */
    public static String encrypt(String content) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        try {
            //对密码进行编码
            byte[] bytes = AES_KEY.getBytes(ENCODING);
            //设置加密算法,生成秘钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
            // 算法/模式/补码方式
            Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
            //选择加密
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            //处理待加密内容生成字节数组
            byte[] encrypted = cipher.doFinal(content.getBytes(ENCODING));
            //返回base64字符串
            return Base64Utils.encodeToString(encrypted);
        } catch (Exception e) {
            log.error("AESUtil.encrypt content:{}, 加密异常", content, e);
            return content;
        }
    }

    /**
     * 解密
     *
     * @param content 待解密内容
     * @return 解密后的明文
     */
    public static String decrypt(String content) {
        if (StringUtils.isBlank(content)) {
            return content;
        }
        try {
            //对密码进行编码
            byte[] bytes = AES_KEY.getBytes(ENCODING);
            //设置解密算法,生成秘钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(bytes, AES_ALGORITHM);
            // 算法/模式/补码方式
            Cipher cipher = Cipher.getInstance(CIPHER_PADDING);
            //选择解密
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

            //先进行Base64解码
            byte[] decodeBase64 = Base64Utils.decodeFromString(content);

            //根据待解密内容进行解密
            byte[] decrypted = cipher.doFinal(decodeBase64);

            //将字节数组转成字符串
            return new String(decrypted, ENCODING);
        } catch (Exception e) {
            log.error("AESUtil.decrypt content:{}, 解密异常", content, e);
            return content;
        }
    }

}

注意,不管是RSA的公钥私钥,还是AES的密钥,都重新生成了一次,以防止用户选择相对简单的密码,而被攻击者破解或推断密钥

4.3 Nacos加解密的实现:Jasypt

Jasypt 其实是一个专门用于加解密的库,用的是对称加密AES。jasypt-spring-boot-starter用在SpringBoot项目中的步骤:

  • 引入依赖
xml 复制代码
<dependency>
  <groupId>com.github.ulisesbocchio</groupId>
  <artifactId>jasypt-spring-boot-starter</artifactId>
  <version>3.0.5</version>
</dependency>
  • 增加密钥配置
yaml 复制代码
jasypt:
  encryptor:
    password: hello!!!
    # 默认的加密算法是 PBEWITHHMACSHA512ANDAES_256,JDK9才支持,JDK1.8用不了,改为下面这个
    algorithm: PBEWithMD5AndDES

这个password就别放nacos了,否则password泄漏,其余密文照样不安全,可放在项目jar包里的配置文件,或者直接不写在配置文件,只是让运维在java -jar是指定一下这个password值

  • 引入Jasypt的加密类,改造Nacos中的明文
java 复制代码
@Autowired
private StringEncryptor encryptor;

//明文变带有jasypt能识别前缀的密文
public String encrypt(String str) {
  return "ENC(" + encryptor.encrypt(str) + ")";
}

// 生成结果如:ENC(GT2vTn1+SdeFu90xH/vgw3uYTNyV5PGp),替换Nacos中对应的明文
  • 前面提到,Jasypt自己会识别是否为自己的密文,然后解密后重新赋值,所以改造后取值,依旧像之前一样直接取即可
java 复制代码
@Value("${spring.redis.password}")
private String password;

5、历史数据兼容处理

对旧的明文存储的数据,需要处理为密文,系统有定时任务管理页面的话,可考虑加个定时任务,给运维人员去执行一次。如果没有,可考虑提供一个内部接口,调用一次,处理旧数据。

相关推荐
Mysticbinary20 天前
椭圆曲线公钥密码算法原理入门
rsa·ecc·公钥密码学
01空间21 天前
Aes加解密
java·aes
GGBondlctrl1 个月前
丹摩征文活动 |【网络原理】关于HTTP的进化之HTTPS的加密原理的那些事
网络·https·非对称加密·对称加密·中间人攻击
Yaml42 个月前
Spring Boot 安全 API 构建:加密解密功能的卓越实践
服务器·网络·spring boot·安全·aes·rsa
许野平2 个月前
OpenSSL:生成 DER 格式的 RSA 密钥对
服务器·网络·openssl·rsa·pem·der
阿华的代码王国2 个月前
【网络原理】——图解HTTPS如何加密(通俗简单易懂)
网络协议·http·https·非对称加密·对称加密·htttps加密传输·证书加密
软件算法开发2 个月前
基于AES的遥感图像加密算法matlab仿真
matlab·加密·aes·遥感图像
A_ugust__2 个月前
vue3.2实现AES加密解密,秘钥通过API获取,并混淆秘钥,后端thinkphp
vue·aes·thinkphp
许野平3 个月前
Rust: AES 加密算法库
开发语言·后端·rust·aes
常敲代码手不生3 个月前
开放API接口时要注意的安全处理总结
java·安全·非对称加密·md5·令牌