由于“加密”是一个宽泛的概念,我将从最常用和最重要的 对称加密 和 非对称加密 开始,并介绍如何在实际应用中结合使用它们(通过 数字信封 技术)。
核心概念
-
对称加密:
- 特点:使用同一个密钥进行加密和解密。
- 优点:速度快,计算开销小,适合加密大量数据。
- 缺点:密钥的分发和管理是个大问题,如何安全地将密钥发送给接收方是最大的挑战。
- 常用算法:AES (Advanced Encryption Standard),DES (已不推荐使用)。
-
非对称加密:
- 特点:使用一对密钥:公钥 和 私钥。
- 用公钥加密的数据,只能用对应的私钥解密。
- 用私钥加密的数据(即签名),只能用对应的公钥验证。
- 优点:安全性高,解决了密钥分发问题,公钥可以公开分发。
- 缺点:速度慢,计算复杂,不适合加密大量数据。
- 常用算法:RSA (Rivest–Shamir–Adleman)。
- 特点:使用一对密钥:公钥 和 私钥。
-
数字信封:
- 概念:这是结合对称和非对称加密优点的一种混合加密方案。
- 流程:
- 使用对称算法(如 AES)加密原始数据。
- 使用接收方的公钥来加密上一步生成的对称密钥。
- 将加密后的数据和加密后的对称密钥一起发送给接收方。
- 解密流程:
- 接收方用自己的私钥解密出对称密钥。
- 用解密出的对称密钥解密出原始数据。
- 优点:既利用了对称加密的高效,又利用了非对称加密的安全密钥交换。
代码示例
下面我将提供完整的、可运行的 Java 代码示例,涵盖 AES 对称加密、RSA 非对称加密以及它们的组合(数字信封)。
准备工作:依赖
对于较新的 Java 版本(如 Java 8+),标准库已经包含了所有需要的类,无需额外依赖。
对称加密:AES 示例
AES 是目前对称加密的黄金标准,我们将使用 AES-128-CBC 模式,并使用 PKCS5Padding 填充方式,CBC 模式需要一个初始化向量来增加安全性。
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.SecureRandom;
import java.util.Base64;
public class AESEncryptionExample {
// AES 加密
public static String encrypt(String data, String secretKey) throws Exception {
// 1. 生成密钥
// KeyGenerator 用于生成密钥,AES 要求密钥长度为 128, 192, 或 256 bits
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128); // 使用 AES-128
SecretKey key = keyGenerator.generateKey();
byte[] keyBytes = key.getEncoded();
// 2. 准备初始化向量
// CBC 模式需要一个 IV,长度与块大小相同 (AES 是 16 bytes)
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
// 3. 创建 Cipher 实例
// "AES/CBC/PKCS5Padding" 表示使用 AES 算法,CBC 模式,PKCS5 填充
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), ivParameterSpec);
// 4. 执行加密
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
// 5. 将 IV 和加密后的数据合并,以便解密时使用
// IV 不需要保密,但需要和密文一起传输
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
return Base64.getEncoder().encodeToString(combined);
}
// AES 解密
public static String decrypt(String encryptedData, String secretKey) throws Exception {
// 1. 解码 Base64
byte[] combined = Base64.getDecoder().decode(encryptedData);
// 2. 分离 IV 和实际密文
byte[] iv = new byte[16];
byte[] actualEncryptedData = new byte[combined.length - 16];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, actualEncryptedData, 0, actualEncryptedData.length);
// 3. 重新创建密钥 (在实际应用中,这个密钥应该是预先共享或安全传输的)
// 注意:这个示例为了演示,在加密和解密时重新生成了密钥,这是不正确的。
// 正确做法是密钥需要被安全地存储和共享,这里简化处理。
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
SecretKey key = keyGenerator.generateKey();
byte[] keyBytes = key.getEncoded();
// 4. 创建 Cipher 实例
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), new IvParameterSpec(iv));
// 5. 执行解密
byte[] decryptedData = cipher.doFinal(actualEncryptedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
public static void main(String[] args) {
try {
String originalData = "这是一段需要加密的敏感信息,Hello, AES!";
// 加密
String encryptedString = encrypt(originalData, "my-secret-key-for-demo");
System.out.println("原始数据: " + originalData);
System.out.println("加密后 (Base64): " + encryptedString);
// 解密
String decryptedString = decrypt(encryptedString, "my-secret-key-for-demo");
System.out.println("解密后: " + decryptedString);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注意:上面的 decrypt 方法为了演示方便,在解密时重新生成了密钥。在实际应用中,加密和解密必须使用完全相同的密钥,如何安全地分发这个 AES 密钥?这正是 RSA 的用武之地。
非对称加密:RSA 示例
RSA 用于加密少量数据,AES 的密钥。
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSAEncryptionExample {
// 生成 RSA 密钥对
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048); // RSA 密钥长度,2048 是推荐值
return keyPairGenerator.generateKeyPair();
}
// 使用公钥加密
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(encryptedData);
}
// 使用私钥解密
public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decodedData = Base64.getDecoder().decode(encryptedData);
byte[] decryptedData = cipher.doFinal(decodedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
// 将密钥转换为字符串 (Base64)
public static String publicKeyToString(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
public static String privateKeyToString(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
// 从字符串转换回密钥
public static PublicKey publicKeyFromString(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
public static PrivateKey privateKeyFromString(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
public static void main(String[] args) throws Exception {
// 1. 生成密钥对
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
System.out.println("公钥 (Base64): " + publicKeyToString(publicKey));
System.out.println("私钥 (Base64): " + privateKeyToString(privateKey));
// 2. 使用公钥加密
String dataToEncrypt = "这是 AES 的密钥";
String encryptedKey = encrypt(dataToEncrypt, publicKey);
System.out.println("\n原始密钥: " + dataToEncrypt);
System.out.println("公钥加密后的密钥 (Base64): " + encryptedKey);
// 3. 使用私钥解密
String decryptedKey = decrypt(encryptedKey, privateKey);
System.out.println("私钥解密后的密钥: " + decryptedKey);
}
}
实战应用:数字信封 (结合 AES 和 RSA)
这个示例展示了如何安全地发送一段数据,发送方用 AES 加密数据,然后用接收方的 RSA 公钥加密 AES 密钥。
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class DigitalEnvelopeExample {
// 假设这是接收方的 RSA 公钥 (在实际应用中,这会从某个地方获取)
private static final String RECEIVER_PUBLIC_KEY_STR = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA..."; // 这里放一个真实的公钥字符串
// 假设这是发送方的 RSA 私钥 (用于签名,本示例省略签名部分)
private static final String SENDER_PRIVATE_KEY_STR = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQD..."; // 这里放一个真实的私钥字符串
public static void main(String[] args) throws Exception {
// 1. 准备密钥
PublicKey receiverPublicKey = publicKeyFromString(RECEIVER_PUBLIC_KEY_STR);
PrivateKey senderPrivateKey = privateKeyFromString(SENDER_PRIVATE_KEY_STR);
// 2. 发送方操作
String originalData = "这是绝密的商业计划书,必须安全送达!";
// 2a. 使用 AES 加密原始数据
SecretKey aesKey = generateAESKey();
String encryptedData = encryptWithAES(originalData, aesKey);
// 2b. 使用接收方的公钥加密 AES 密钥
String encryptedAesKey = encryptWithRSA(aesKey.getEncoded(), receiverPublicKey);
// 2c. 将加密后的数据和加密后的密钥打包
String digitalEnvelope = encryptedAesKey + "|" + encryptedData;
System.out.println("--- 发送方 ---");
System.out.println("原始数据: " + originalData);
System.out.println("加密后的 AES 密钥 (Base64): " + encryptedAesKey);
System.out.println("加密后的数据 (Base64): " + encryptedData);
System.out.println("数字信封内容: " + digitalEnvelope);
// 3. 接收方操作
System.out.println("\n--- 接收方 ---");
String[] parts = digitalEnvelope.split("\\|");
String receivedEncryptedAesKey = parts[0];
String receivedEncryptedData = parts[1];
// 3a. 使用自己的私钥解密出 AES 密钥
byte[] decryptedAesKeyBytes = decryptWithRSA(receivedEncryptedAesKey, senderPrivateKey); // 注意:这里应该是 receiverPrivateKey
SecretKey decryptedAesKey = new SecretKeySpec(decryptedAesKeyBytes, "AES");
System.out.println("解密出的 AES 密钥: " + new String(decryptedAesKey.getEncoded()));
// 3b. 使用解密出的 AES 密钥解密数据
String finalDecryptedData = decryptWithAES(receivedEncryptedData, decryptedAesKey);
System.out.println("最终解密数据: " + finalDecryptedData);
}
// ========== 辅助方法 ==========
private static SecretKey generateAESKey() throws Exception {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
return keyGenerator.generateKey();
}
private static String encryptWithAES(String data, SecretKey key) throws Exception {
byte[] iv = new byte[16];
new SecureRandom().nextBytes(iv);
IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);
byte[] encryptedData = cipher.doFinal(data.getBytes(StandardCharsets.UTF_8));
byte[] combined = new byte[iv.length + encryptedData.length];
System.arraycopy(iv, 0, combined, 0, iv.length);
System.arraycopy(encryptedData, 0, combined, iv.length, encryptedData.length);
return Base64.getEncoder().encodeToString(combined);
}
private static String decryptWithAES(String encryptedData, SecretKey key) throws Exception {
byte[] combined = Base64.getDecoder().decode(encryptedData);
byte[] iv = new byte[16];
byte[] actualEncryptedData = new byte[combined.length - 16];
System.arraycopy(combined, 0, iv, 0, iv.length);
System.arraycopy(combined, iv.length, actualEncryptedData, 0, actualEncryptedData.length);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
byte[] decryptedData = cipher.doFinal(actualEncryptedData);
return new String(decryptedData, StandardCharsets.UTF_8);
}
private static String encryptWithRSA(byte[] data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return Base64.getEncoder().encodeToString(cipher.doFinal(data));
}
private static byte[] decryptWithRSA(String encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(Base64.getDecoder().decode(encryptedData));
}
private static PublicKey publicKeyFromString(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
private static PrivateKey privateKeyFromString(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
}
重要修正:在 DigitalEnvelopeExample 的 main 方法中,接收方解密 AES 密钥时,应该使用自己的私钥,而不是发送方的私钥,请将 decryptWithRSA(receivedEncryptedAesKey, senderPrivateKey) 修改为 decryptWithRSA(receivedEncryptedAesKey, receiverPrivateKey)。
总结与最佳实践
- 不要自己实现加密算法:始终使用标准库(如
java.crypto)提供的、经过广泛审查的算法和实现。 - 选择合适的算法:
- 加密大量数据:首选 AES。
- 加密少量数据(如密钥、密码):使用 RSA。
- 混合使用(数字信封)是行业标准。
- 密钥管理是核心:
- 对称密钥:必须安全地存储和共享,不要硬编码在代码里,可以考虑使用密钥库、或者由用户输入。
- 非对称密钥对:
- 私钥:必须极其保密,存储在安全的地方(如硬件安全模块 HSM、操作系统的密钥库)。
- 公钥:可以公开,但需要确保其真实性(防止中间人攻击),通常通过数字证书来验证。
- 考虑使用
KeyStore:Java 提供了KeyStoreAPI,可以用来安全地存储密钥和证书,这比将密钥放在普通文件中要安全得多。 - 初始化向量:在使用 AES-CBC 等模式时,IV 是必须的,IV 不需要保密,但必须是唯一的(对于每个加密操作),并且需要和密文一起传输。
- 填充模式:确保加密和解密使用相同的填充模式(如
PKCS5Padding)。
希望这个详细的指南能帮助你理解和使用 Java 进行加密!
