在当今的应用开发中,保障接口的安全性变得尤为关键。尤其是当敏感数据通过网络传输时,如何避免数据泄露或篡改,成为了开发者必须考虑的问题。本篇文章将详细探讨如何在 SpringBoot 3.4 框架下,利用 RSA 和 AES 混合加密方案,确保接口通信的安全性。
为什么需要接口加密?
在没有加密的情况下,通过网络传输的数据很容易被中间人或抓包工具截获。尤其是当数据中包含用户隐私信息或支付数据时,缺乏加密的传输方式会带来巨大的安全隐患。通过对接口数据进行加密,即使数据在传输过程中被截获,黑客也无法解密和理解数据内容,从而有效避免了数据泄漏的风险。
RSA+AES 混合加密方案的优势
选择 RSA 和 AES 混合加密方案,主要是因为这两种加密算法的结合,能够平衡加密的安全性和性能:
- RSA 是一种非对称加密算法,虽然加密安全性高,但加密速度较慢,适合用来加密较小的数据,比如加密 AES 密钥。
- AES 是一种对称加密算法,速度较快,适用于大量数据的加密,但密钥的分发和管理是一个挑战。
通过结合这两种算法,我们利用 RSA 加密 AES 的密钥,再使用 AES 加密实际的数据,从而实现了高安全性和高性能的平衡。
实现原理
- 客户端和服务端预先约定好 RSA 公钥和私钥。
- 客户端生成一个随机的 AES 密钥,并使用 RSA 公钥加密这个 AES 密钥。
- 客户端使用 AES 密钥加密实际的数据。
- 客户端将加密后的 AES 密钥和加密的数据一并发送给服务端。
- 服务端使用 RSA 私钥解密得到 AES 密钥,然后用 AES 密钥解密数据。
项目依赖
在 pom.xml
中,我们需要添加以下依赖,来支持加密解密功能:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.78</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
</dependencies>
加密工具类
接下来,我们将实现一个加密工具类,用于处理 RSA 和 AES 加密解密逻辑。以下是该类的实现代码:
package com.icoderoad.secureapi.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class EncryptionUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String AES_ALGORITHM = "AES/CBC/PKCS7Padding";
private static final int AES_KEY_SIZE = 256;
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
private static final int RSA_KEY_SIZE = 2048;
public static KeyPair generateRSAKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(RSA_KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static String keyToString(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
public static PublicKey stringToRSAPublicKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
public static PrivateKey stringToRSAPrivateKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(AES_KEY_SIZE);
return keyGen.generateKey();
}
public static SecretKey stringToAESKey(String keyStr) {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
return new SecretKeySpec(keyBytes, "AES");
}
public static String encryptWithRSA(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decryptWithRSA(String encryptedData, PrivateKey privateKey) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static String encryptWithAES(String data, SecretKey secretKey, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decryptWithAES(String encryptedData, SecretKey secretKey, byte[] iv) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static byte[] generateIV() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
return iv;
}
}
请求包装类与解密拦截器
为了便于加密请求的自动解密,我们将创建一个 EncryptedRequest
请求包装类:
package com.icoderoad.secureapi.model;
import lombok.Data;
@Data
public class EncryptedRequest {
private String encryptedKey;
private String iv;
private String encryptedData;
private Long timestamp;
private String signature;
}
接着,创建一个解密拦截器,自动在控制器处理请求前进行解密:
package com.icoderoad.secureapi.utils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class EncryptionUtils {
static {
Security.addProvider(new BouncyCastleProvider());
}
private static final String AES_ALGORITHM = "AES/CBC/PKCS7Padding";
private static final int AES_KEY_SIZE = 256;
private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding";
private static final int RSA_KEY_SIZE = 2048;
public static KeyPair generateRSAKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(RSA_KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static String keyToString(Key key) {
return Base64.getEncoder().encodeToString(key.getEncoded());
}
public static PublicKey stringToRSAPublicKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
public static PrivateKey stringToRSAPrivateKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(keySpec);
}
public static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(AES_KEY_SIZE);
return keyGen.generateKey();
}
public static SecretKey stringToAESKey(String keyStr) {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
return new SecretKeySpec(keyBytes, "AES");
}
public static String encryptWithRSA(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decryptWithRSA(String encryptedData, PrivateKey privateKey) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static String encryptWithAES(String data, SecretKey secretKey, byte[] iv) throws Exception {
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
byte[] encryptedBytes = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
public static String decryptWithAES(String encryptedData, SecretKey secretKey, byte[] iv) throws Exception {
byte[] encryptedBytes = Base64.getDecoder().decode(encryptedData);
Cipher cipher = Cipher.getInstance(AES_ALGORITHM, "BC");
IvParameterSpec ivSpec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
byte[] decryptedBytes = cipher.doFinal(encryptedBytes);
return new String(decryptedBytes, StandardCharsets.UTF_8);
}
public static byte[] generateIV() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
random.nextBytes(iv);
return iv;
}
}
完整的实现
- RSA 密钥生成在应用启动时,生成并存储公钥和私钥。
- AES 密钥生成与加密客户端生成一个随机 AES 密钥,并通过 RSA 公钥加密。
- 数据加密与解密使用 AES 加密数据,服务端使用 RSA 解密 AES 密钥后,再用 AES 解密数据。
总结
本文展示了如何在 Spring Boot 应用中实现 RSA + AES 混合加密方案,从而保障接口数据传输的安全性。通过结合这两种加密算法,能够在确保安全的同时,不影响系统性能。