非对称加密详解及Java实现
一、非对称加密概述
非对称加密(Asymmetric Cryptography),也称为公钥加密,是一种使用一对密钥(公钥和私钥)进行加密和解密的加密方法。它与对称加密的主要区别在于使用了不同的密钥进行加密和解密操作。
核心特点:
- 密钥成对出现:公钥(Public Key)和私钥(Private Key)
- 公钥可公开,私钥必须严格保密
- 加密解密方向性 :
- 公钥加密 → 私钥解密(用于加密通信)
- 私钥加密 → 公钥解密(用于数字签名)
- 计算复杂度高,速度比对称加密慢很多
常见算法:
- RSA(最常用)
- DSA(主要用于数字签名)
- ECC(椭圆曲线加密)
- ElGamal
- Diffie-Hellman(密钥交换)
二、RSA算法原理
RSA是最经典的非对称加密算法,由Ron Rivest、Adi Shamir和Leonard Adleman于1977年提出。
密钥生成步骤:
- 选择两个大素数p和q
- 计算n = p × q
- 计算欧拉函数φ(n) = (p-1)(q-1)
- 选择整数e,使得1 < e < φ(n)且e与φ(n)互质
- 计算d,使得d × e ≡ 1 mod φ(n)
- 公钥:(e, n),私钥:(d, n)
加密解密过程:
- 加密:c ≡ m^e mod n
- 解密:m ≡ c^d mod n
三、Java实现RSA加密
1. 密钥对生成
java
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
public class RSAKeyGenerator {
public static void main(String[] args) {
try {
// 1. 创建KeyPairGenerator实例,指定算法为RSA
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
// 2. 初始化密钥长度,通常为1024、2048或4096位
// 密钥越长越安全,但计算速度越慢
keyPairGenerator.initialize(2048);
// 3. 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 4. 获取公钥和私钥
System.out.println("Public Key: " + keyPair.getPublic());
System.out.println("Private Key: " + keyPair.getPrivate());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
代码解释:
KeyPairGenerator
是Java中用于生成非对称密钥对的类initialize(2048)
指定密钥长度为2048位(目前推荐的最小安全长度)- 生成的
KeyPair
包含公钥和私钥
2. RSA加密解密实现
java
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;
public class RSAEncryptionExample {
public static void main(String[] args) throws Exception {
// 1. 生成RSA密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 获取公钥和私钥
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 原始消息
String originalMessage = "这是一条需要加密的敏感信息";
System.out.println("原始消息: " + originalMessage);
// 2. 使用公钥加密
Cipher encryptCipher = Cipher.getInstance("RSA");
encryptCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = encryptCipher.doFinal(originalMessage.getBytes());
String encryptedMessage = Base64.getEncoder().encodeToString(encryptedBytes);
System.out.println("加密后的消息: " + encryptedMessage);
// 3. 使用私钥解密
Cipher decryptCipher = Cipher.getInstance("RSA");
decryptCipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedBytes = decryptCipher.doFinal(Base64.getDecoder().decode(encryptedMessage));
String decryptedMessage = new String(decryptedBytes);
System.out.println("解密后的消息: " + decryptedMessage);
}
}
代码解释:
- 使用
Cipher
类进行加密解密操作 ENCRYPT_MODE
表示加密模式,使用公钥初始化DECRYPT_MODE
表示解密模式,使用私钥初始化- 由于加密后的字节数组不易显示,使用Base64编码转换为字符串
- RSA加密有长度限制,加密的数据长度不能超过密钥长度减去一定的padding长度
3. 处理长文本加密
由于RSA加密有长度限制,对于长文本需要分段加密:
java
import javax.crypto.Cipher;
import java.security.*;
import java.util.Base64;
public class RSALongTextEncryption {
private static final int KEY_SIZE = 2048;
private static final int MAX_ENCRYPT_BLOCK = KEY_SIZE / 8 - 11; // 加密块最大长度
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPair keyPair = generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 长文本
StringBuilder longText = new StringBuilder();
for (int i = 0; i < 100; i++) {
longText.append("这是一段非常长的文本,需要进行分段加密处理。");
}
// 加密
byte[] encryptedData = encryptLongText(longText.toString(), publicKey);
String encryptedBase64 = Base64.getEncoder().encodeToString(encryptedData);
System.out.println("加密后长度: " + encryptedBase64.length());
// 解密
String decryptedText = decryptLongText(Base64.getDecoder().decode(encryptedBase64), privateKey);
System.out.println("解密是否成功: " + longText.toString().equals(decryptedText));
}
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE);
return keyPairGenerator.generateKeyPair();
}
public static byte[] encryptLongText(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] dataBytes = data.getBytes();
int inputLen = dataBytes.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(dataBytes, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(dataBytes, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
return out.toByteArray();
}
public static String decryptLongText(byte[] encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
int inputLen = encryptedData.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
int blockSize = KEY_SIZE / 8; // 解密块大小
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > blockSize) {
cache = cipher.doFinal(encryptedData, offSet, blockSize);
} else {
cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * blockSize;
}
return out.toString();
}
}
关键点:
- 2048位RSA密钥最大加密块为245字节(2048/8-11)
- 解密块大小为256字节(2048/8)
- 使用
ByteArrayOutputStream
收集所有加密/解密后的分段数据
四、数字签名
非对称加密还可用于数字签名,验证数据完整性和身份认证。
Java实现数字签名
java
import java.security.*;
import java.util.Base64;
public class DigitalSignatureExample {
public static void main(String[] args) throws Exception {
// 1. 生成密钥对
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 待签名的数据
String data = "这是一份重要合同内容";
byte[] dataBytes = data.getBytes();
// 2. 创建签名实例并初始化
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(keyPair.getPrivate()); // 用私钥签名
// 3. 更新要签名的数据
signature.update(dataBytes);
// 4. 生成签名
byte[] digitalSignature = signature.sign();
String signatureBase64 = Base64.getEncoder().encodeToString(digitalSignature);
System.out.println("数字签名: " + signatureBase64);
// 5. 验证签名
Signature verifySignature = Signature.getInstance("SHA256withRSA");
verifySignature.initVerify(keyPair.getPublic()); // 用公钥验证
verifySignature.update(dataBytes);
boolean isVerified = verifySignature.verify(digitalSignature);
System.out.println("签名验证结果: " + isVerified);
}
}
代码解释:
- 使用
Signature
类进行数字签名操作 SHA256withRSA
表示使用SHA-256哈希算法和RSA签名算法- 私钥用于生成签名,公钥用于验证签名
- 如果数据或签名被篡改,验证将失败
五、非对称加密的应用场景
-
安全通信(如HTTPS、SSL/TLS)
- 客户端使用服务器公钥加密对称密钥
- 服务器用私钥解密获取对称密钥
- 后续通信使用对称加密
-
数字签名
- 验证数据完整性和来源真实性
- 用于软件发布、合同签署等场景
-
密钥交换
- 如Diffie-Hellman密钥交换协议
- 在不安全通道上安全地协商对称密钥
-
数字证书
- 证书颁发机构(CA)使用私钥签名证书
- 用户使用CA公钥验证证书真实性
六、安全注意事项
- 密钥长度:目前推荐至少2048位RSA密钥,3072或4096位更安全
- 密钥管理:私钥必须严格保护,建议使用HSM(硬件安全模块)存储
- 加密模式:使用适当的padding模式(如OAEP),避免使用PKCS1v1.5
- 性能考虑:非对称加密计算量大,通常仅用于加密小数据或密钥交换
- 算法选择:考虑使用ECC(椭圆曲线加密)替代RSA,在相同安全强度下密钥更短
七、与其他加密方式比较
特性 | 对称加密 | 非对称加密 |
---|---|---|
密钥 | 单一密钥 | 公钥/私钥对 |
速度 | 快 | 慢 |
密钥分发 | 困难 | 容易(公钥可公开) |
用途 | 大数据加密 | 密钥交换、数字签名、小数据加密 |
常见算法 | AES, DES, 3DES | RSA, ECC, DSA |
密钥长度 | 128/256位 | 2048/4096位(RSA) |
八、总结
非对称加密是现代密码学的基石,解决了密钥分发和数字签名等关键安全问题。Java通过java.security
和javax.crypto
包提供了完整的非对称加密支持。在实际应用中,通常将对称加密和非对称加密结合使用,发挥各自优势:使用非对称加密安全地交换对称密钥,然后使用对称加密加密实际通信数据。
理解非对称加密的原理和正确使用方式,对于构建安全系统至关重要。开发者应当根据具体场景选择适当的算法、密钥长度和加密模式,并遵循最佳实践来管理密钥。