SpringBoot 前后端加密技巧全攻略

在 Web 应用中,确保前后端之间的数据传输安全是非常重要的。这通常涉及到使用 HTTPS 协议、数据加密、令牌验证等安全措施。本文通过将前后端之间的传输数据进行加密,用于在 Spring Boot 应用中实现前后端传输加密设计。

一、数据加密方案

即使使用了 HTTPS,也可能需要在应用层对数据进行额外的加密。这可以通过以下方式实现:

对称加密: 加密解密是同一个密钥,速度快,数据接收方需要公布其私钥给数据传输方进行数据加密,安全性完全依赖于该密钥。适合做大量数据或数据文件的加解密。

  • 使用 AES、DES 等对称加密算法对敏感数据进行加密和解密。

  • 前后端需要共享一个密钥(key)用于加密和解密。

  • 密钥的管理和传输需要特别注意安全性。

非对称加密: 加密用公钥,解密用私钥。公钥和私钥是成对的 (可借助工具生成,如 openssl 等),即用公钥加密的数据,一定能用其对应的私钥解密,能用私钥解密的数据,一定是其对应的公钥加密。对大量数据或数据文件加解密时,效率较低。数据接收方需公布其公钥给数据传输方,私钥自己保留,安全性更高。

  • 使用 RSA、ECC 等非对称加密算法。

  • 私钥用于加密数据,公钥用于解密数据。

  • 公钥可以公开,而私钥需要安全存储。

混合加密

  • 结合使用对称加密和非对称加密。

  • 使用非对称加密算法交换对称加密的密钥(会话密钥),然后使用会话密钥进行实际的数据加密和解密。

这里就赘述介绍每种加密的实现方式和原理。

1.1 数据加密实现方式

  • 如果数据传输较大,密钥不需要进行网络传输,数据不需要很高的安全级别,则采用对称加密,只要能保证密钥没有人为外泄即可;

  • 如果数据传输小,而且对安全级别要求高,或者密钥需要通过 internet 交换,则采用非对称加密;

本文采用了两者结合的方式 (混合加密模式),这样是大多数场景下采用的加密方式。加密时序图如下所示:

图片

通过使用对称加密 (AES) 和 非对称加密 (RSA) 的方式来实现对数据的加密;即通过对称加密进行业务数据体的加密,通过非对称加密进行对称加密密钥的加密; 它结合了对称加密的高效性 和 非对称加密的安全性。

注意事项:

  • 确保 RSA 公钥在传输过程中是安全的,因为任何拥有这个公钥的人都可以用它来加密 AES 密钥,但只有拥有私钥的人才能解密它。

  • 确保在加密和解密过程中使用安全的加密库和最新的加密算法标准。

  • 定期更换密钥对和对称密钥,以降低密钥泄露的风险。

这种混合加密模式提供了安全性和效率之间的平衡。对称加密(如 AES)用于加密大量数据,因为它通常比非对称加密更快。而非对称加密(如 RSA)用于加密密钥,因为它提供了更强的安全性,特别是当密钥需要在不安全的通道上传输时。

1.2 AES 加密工具类创建

封装 AESUtil 工具类时 pom.xml 中运用到的依赖:

<!--   hutool-all工具类依赖   -->
<dependency>
  <groupId>cn.hutool</groupId>
  <artifactId>hutool-all</artifactId>
  <version>5.8.18</version>
</dependency>

AES 加解密工具类 AESUtil 代码:

package com.example.api_security_demo.utils;

import cn.hutool.core.codec.Base64;

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;

/**
 * @ClassName : AESUtil
 * @Description : AES加密工具类
 * @Author : AD
 */
public class AESUtil {

    public static final String CHAR_ENCODING = "UTF-8";

    /**
     * [常见算法]AES、DES、RSA、Blowfish、RC4 等等
     * [常见的模式] ECB (电子密码本模式)、CBC (密码分组链接模式)、CTR (计数模式) 等等
     * [常见的填充] NoPadding、PKCS5Padding、PKCS7Padding 等等
     *
     * [AES算法]可以有以下几种常见的值:
     * AES:标准的AES算法。
     * AES/CBC/PKCS5Padding:使用CBC模式和PKCS5填充的AES算法。
     * AES/ECB/PKCS5Padding:使用ECB模式和PKCS5填充的AES算法。
     * AES/GCM/NoPadding:使用GCM模式的AES算法,不需要填充。
     * AES/CCM/NoPadding:使用CCM模式的AES算法,不需要填充。
     * AES/CFB/NoPadding:使用CFB模式的AES算法,不需要填充。
     * */
    public static final String AES_ALGORITHM = "AES";

    public static char[] HEXCHAR = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    /**
     * Description: 随机生成 AESKey密钥
     *
     * @param length 随机生成密钥长度
     * @return java.lang.String
    */
    public static String getAESKey(int length) throws Exception
    {
        /*
        * Random类用于生成伪随机数。
        * */
        Random random = new Random();
        StringBuilder ret = new StringBuilder();
        for(int i = 0; i < length; i++)
        {
            // 选择生成数字还是字符
            boolean isChar = (random.nextInt(2) % 2 == 0);
            /* 0随机生成一个字符*/
            if (isChar)
            {
                // 选择生成大写字母 / 小写字母
                int choice = (random.nextInt(2) % 2 == 0) ? 65 : 97;
                ret.append((char) (choice+random.nextInt(26)));
                /* 1随机生成一个数字 */
            }else
            {
                ret.append( random.nextInt(10));
            }
        }
        return ret.toString();
    }

    /**
     * Description: 加密
     *
     * @param data 待加密数据内容
     * @param aesKey 加密密钥
     * @return byte[]
    */
    public static byte[] encrypt(byte[] data,byte[] aesKey)
    {
        if (aesKey.length != 16)
        {
            throw new RuntimeException("Invalid AES key length (must be 16 bytes) !");
        }
        try{
            /*
            * 创建一个SecretKeySpec对象来包装AES密钥。
            * 它使用了aesKey字节数组作为密钥,并指定算法为"AES"。
            * 这个对象用来提供对称加密算法的密钥。
            * */
            SecretKeySpec secretKey = new SecretKeySpec(aesKey, "AES");
            /*
            * 获取SecretKeySpec对象中的编码形式,将其存储在encodedFormat字节数组中。
            * 这个编码形式可以被用来重新构造密钥。
            * */
            byte[] encodedFormat = secretKey.getEncoded();
            /*
            * 使用encodedFormat字节数组创建了另一个SecretKeySpec对象secKey。
            * 这个对象也用来提供对称加密算法的密钥。
            * */
            SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
            /*
            * 使用Cipher类的getInstance()方法获取了一个Cipher对象(创建密码器)。
            * 这个对象用来完成加密或解密的工作。
            * */
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            /*
            * 码调用init()方法来初始化Cipher对象(初始化)。
            * 它要求传入操作模式和提供密钥的对象,这里使用Cipher.ENCRYPT_MODE代表加密模式,以及之前创建的secKey对象作为密钥。
            * */
            cipher.init(Cipher.ENCRYPT_MODE,secKey);
            /*
            * 用Cipher对象对data进行加密操作,得到加密后的结果存储在result字节数组中。
            * */
            byte[] result = cipher.doFinal(data);
            return result;
        }catch (Exception e){
            throw new RuntimeException(" encrypt fail! ",e);
        }
    }

    /**
     * Description: 解密
     *
     * @param data 解密数据
     * @param aesKey 解密密钥
     * @return byte[]
     */
    public static byte[] decrypt(byte[] data,byte[] aesKey)
    {
        if (aesKey.length != 16)
        {
            throw new RuntimeException(" Invalid AES Key length ( must be 16 bytes)");
        }
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(aesKey, "AES");
            byte[] encodedFormat = secretKeySpec.getEncoded();
            SecretKeySpec secKey = new SecretKeySpec(encodedFormat, "AES");
            /* 创建密码器 */
            Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
            /* 初始化密码器 */
            cipher.init(Cipher.DECRYPT_MODE,secKey);
            byte[] result = cipher.doFinal(data);
            return result;
        }catch (Exception e){
            throw new RuntimeException(" Decrypt Fail !",e);
        }
    }

    /**
     * Description:加密数据,并转换为Base64编码格式!
     *
     * @param data 待加密数据
     * @param aeskey 加密密钥
     * @return java.lang.String
    */
    public static String encryptToBase64(String data,String aeskey)
    {
        try {
            byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), aeskey.getBytes(CHAR_ENCODING));
            /* 加密数据转 Byte[]--> 换为Base64 --> String */
            return Base64.encode(valueByte);
        }catch (UnsupportedEncodingException e){
            throw new RuntimeException(" Encrypt Fail !",e);
        }
    }

    /**
     * Description: 解密数据,将Basse64格式的加密数据进行解密操作
     *
     * @param data
     * @param aeskey
     * @return java.lang.String
    */
    public static String  decryptFromBase64(String data,String aeskey)
    {
        try {
            byte[] originalData = Base64.decode(data.getBytes());
            byte[] valueByte = decrypt(originalData,aeskey.getBytes(CHAR_ENCODING));
            return new String(valueByte,CHAR_ENCODING);
        }catch (UnsupportedEncodingException e){
            throw new RuntimeException("Decrypt Fail !",e);
        }
    }

    /**
     * Description:加密数据,aesKey为Base64格式时,并将加密后的数据转换为Base64编码格式
     *
     * @param data
     * @param aesKey
     * @return java.lang.String
    */
    public static String encryptWithKeyBase64(String data,String aesKey)
    {
        try{
            byte[] valueByte = encrypt(data.getBytes(CHAR_ENCODING), Base64.decode(aesKey.getBytes()));
            return Base64.encode(valueByte);
        }catch (UnsupportedEncodingException e){
            throw new RuntimeException("Encrypt Fail!",e);
        }
    }

    /**
     * Description: 解密数据,数据源为Base64格式,且 aesKey为Base64编码格式
     *
     * @param data
     * @param aesKey
     * @return java.lang.String
    */
    public static String decryptWithKeyBase64(String data,String aesKey)
    {
        try {
            byte[] originalDate = Base64.decode(data.getBytes());
            byte[] valueByte = decrypt(originalDate,Base64.decode(aesKey.getBytes()));
            return new String(valueByte,CHAR_ENCODING);
        }catch (UnsupportedEncodingException e){
            throw new RuntimeException("Decrypt Fail !",e);
        }

    }

    /**
     * Description:通过密钥生成器生成一个随机的 AES 密钥,并将其以字节数组的形式返回。
     * 主要功能是生成并返回一组随机的密钥字节数组,这些字节数组可用于加密和解密数据。
     *
     * @param
     * @return byte[]
    */
    public static byte[] generateRandomAesKey()
    {
        KeyGenerator keyGenerator = null;
        try{
            /*
            * KeyGenerator是Java Cryptography Architecture(JCA)提供的主要密钥生成器类之一,用于生成对称加密算法的密钥。
            * 获取一个用于生成AES算法密钥的KeyGenerator实例,以便在加密和解密操作中使用该密钥。
            * */
            keyGenerator = KeyGenerator.getInstance(AES_ALGORITHM);
        }catch (NoSuchAlgorithmException e){
            throw new RuntimeException("GenerateRandomKey Fail !",e);
        }
        /*
        * SecureRandom 类提供了一种用于生成加密强随机数的实现。
        * */
        SecureRandom secureRandom = new SecureRandom();
        /*
        * 初始化密钥生成器 keyGenerator。
        * 初始化密钥生成器时使用了 SecureRandom 实例,以确保生成的密钥具有足够的随机性。
        * */
        keyGenerator.init(secureRandom);
        /*
        * 调用 generateKey() 方法,使用初始化后的 keyGenerator 生成密钥对象 key。
        * */
        Key key = keyGenerator.generateKey();
        //返回生成的密钥的字节数组表示。
        return key.getEncoded();
    }

    /**
     * Description: 通过密钥生成器生成一个随机的 AES 密钥,并转化为Base64格式
     *
     * @param
     * @return java.lang.String
    */
    public static String generateRandomAesKeyWithBase64()
    {
        return Base64.encode(generateRandomAesKey());
    }

/* !!当GET请求进行加密时,地址上的加密参数就以16进制字符串的方式进行传输,否则特殊符号路径无法解析[ +、/、=]等Base64编码格式 */
    /**
     * Description: 从Byte[] 数组转 16进制字符串
     *
     * @param b
     * @return java.lang.String
     */
    public static String toHexString(byte[] b)
    {
        /*
         * 每个字节都可以用两个十六进制字符来表示,因此初始化的容量是字节数组长度的两倍。
         * */
        StringBuilder sb = new StringBuilder(b.length * 2);
        for (int i = 0; i<b.length ;i++)
        {
            /*
             * 首先取字节的高四位,然后查找对应的十六进制字符,并将其追加到StringBuilder中
             * */
            sb.append(HEXCHAR[(b[i] & 0xf0) >>> 4]);
            /*
             * 取字节的低四位,找到对应的十六进制字符,并追加到StringBuilder中。
             * */
            sb.append(HEXCHAR[b[i] & 0x0f]);
        }
        return sb.toString();
    }

    /**
     * Description: 从16进制字符串转 byte[] 数组
     *
     * @param s
     * @return byte[]
     */
    public static final byte[] toBytes(String s)
    {
        byte[] bytes;
        bytes = new byte[s.length() / 2];
        for (int i = 0; i < bytes.length ; i++)
        {
            bytes[i] = (byte) Integer.parseInt(s.substring(2*i,2*i+2),16);
        }
        return bytes;
    }
}

AES 加解密工具类方法代码解析(为了方便自己理解和使用,有必要简单分类记录一下工具类中的方法接口):

AESUtil 工具类中的方法封装的比较杂乱,通过梳理之后更加能理清每个方法的具体用法和功能!

生成 AES 密钥的方法:

该工具类中总共封装了两种 生成 AES 密钥的方法 String getAESKey(int length) 和 String generateRandomAesKeyWithBase64()

  • getAESKey 方法生成的密钥是 由数字 (0-9)、小写字母、大写字母随机组成的普通字符串;

  • generateRandomAesKeyWithBase64() 方法生成的密钥是 通过javax.crypto.KeyGenerator 密钥生成器生成Byte[] 类型的数据 在将该 byte[] 转换为 Base64 编码格式。

图片

AES 加密数据方法:

1.AES 工具类封装的加密数据方法有以下几种byte[] encrypt(byte[] data,byte[] aesKey)、 String encryptToBase64(String data,String aeskey) 和  String encryptWithKeyBase64(String data,String aesKey) 共三种加密方式:

注:其实其余加密方法都是基于该方法进行封装的。也可以根据自己需求来调整,注意区别在于传入的数据格式有所区别!!

2.String encryptToBase64(String data,String aeskey) 该 AES 加密方法是通过传入加密数据的字符串,同时传入字符格式的 AES 密钥 Key(通过 getAESKey 生成的密钥);方法内部会将传入进来的 待加密数据 data 和 aesKey 密钥转换为 byte[] 格式,然后在调用第一种加密方法;最后生成的加密数据byte[] 也会在内部自动转换为 Base64 编码格式 然后返回。

3.String encryptWithKeyBase64(String data,String aesKey) 该 AES 加密方法,需要传入 字符串形式的加密数据,以及 Base64 编码格式的 AES 密钥 (该密钥主要是通过generateRandomAesKeyWithBase64() 方法生成的密钥数据 为 Base64 编码格式)。加密方法内部在接收到 待加密数据后会自动转换为byte[]格式;

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值