強大加密!SpringBoot 實現 RSA+AES 自動解密,保障接口安全
在當今的應用開發中,保障接口的安全性變得尤為關鍵。尤其是當敏感數據通過網絡傳輸時,如何避免數據泄露或篡改,成為了開發者必須考慮的問題。本篇文章將詳細探討如何在 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 混合加密方案,從而保障接口數據傳輸的安全性。通過結合這兩種加密算法,能夠在確保安全的同時,不影響系統性能。