概述
数字签名用于验证数据的完整性和来源真实性。使用私钥签名,公钥验证。
目录
RSA签名
原理
RSA签名使用私钥对数据哈希值进行加密,验证者使用公钥解密并与数据哈希比较。
签名流程:
- 计算数据哈希值 H = Hash(Data)
- 使用私钥签名 S = H^d mod n
- 使用公钥验证:H' = S^e mod n
- 比较 H == H'
技术规格
| 属性 | 值 |
|---|---|
| 密钥长度 | 2048/3072/4096位 |
| 签名长度 | 等于密钥长度 |
| 哈希算法 | SHA-256, SHA-384, SHA-512 |
| 安全级别 | 高(2048位以上) |
应用场景
- 数字证书:SSL/TLS证书签名
- 代码签名:软件发布
- 文档签名:PDF、Office文档
- API认证:JWT签名
- 邮件签名:PGP/GPG
性能影响
- 签名速度:慢(与密钥长度相关)
- 验证速度:快(公钥指数小)
- 签名长度:大(等于密钥长度)
- CPU使用率:高(签名时)
Java实现示例
ini
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RSASignatureExample {
/**
* 生成RSA密钥对
*/
public static KeyPair generateKeyPair(int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.generateKeyPair();
}
/**
* RSA签名(SHA256withRSA)
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/**
* RSA验证签名
*/
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
/**
* 使用不同哈希算法的签名
*/
public static String signWithHash(String data, PrivateKey privateKey, String hashAlgorithm)
throws Exception {
String algorithm = hashAlgorithm + "withRSA";
Signature signature = Signature.getInstance(algorithm);
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/**
* 签名大文件(使用流式处理)
*/
public static String signFile(java.io.InputStream inputStream, PrivateKey privateKey)
throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initSign(privateKey);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
signature.update(buffer, 0, bytesRead);
}
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/**
* 验证大文件签名
*/
public static boolean verifyFile(java.io.InputStream inputStream, String sign,
PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(publicKey);
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
signature.update(buffer, 0, bytesRead);
}
byte[] signBytes = Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
/**
* 密钥序列化
*/
public static String serializePublicKey(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
public static String serializePrivateKey(PrivateKey privateKey) {
return Base64.getEncoder().encodeToString(privateKey.getEncoded());
}
public static PublicKey deserializePublicKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
public static PrivateKey deserializePrivateKey(String keyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(keyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
/**
* 完整示例
*/
public static void main(String[] args) throws Exception {
System.out.println("=== RSA数字签名 ===");
// 生成密钥对
KeyPair keyPair = generateKeyPair(2048);
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
String data = "这是要签名的数据";
System.out.println("\n=== 基本签名验证 ===");
String signature = sign(data, privateKey);
System.out.println("数据: " + data);
System.out.println("签名: " + signature.substring(0, 50) + "...");
System.out.println("签名长度: " + signature.length() + " 字符");
boolean isValid = verify(data, signature, publicKey);
System.out.println("验证结果: " + isValid);
// 测试数据被篡改
String tamperedData = "这是被篡改的数据";
boolean isTamperedValid = verify(tamperedData, signature, publicKey);
System.out.println("篡改后验证: " + isTamperedValid);
System.out.println("\n=== 不同哈希算法 ===");
String[] hashAlgorithms = {"SHA256", "SHA384", "SHA512"};
for (String hashAlg : hashAlgorithms) {
String sig = signWithHash(data, privateKey, hashAlg);
boolean valid = verify(data, sig, publicKey);
System.out.println(hashAlg + "withRSA: " + valid);
}
System.out.println("\n=== 性能测试 ===");
long start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
sign(data, privateKey);
}
long signTime = System.currentTimeMillis() - start;
System.out.println("签名100次: " + signTime + "ms");
start = System.currentTimeMillis();
for (int i = 0; i < 100; i++) {
verify(data, signature, publicKey);
}
long verifyTime = System.currentTimeMillis() - start;
System.out.println("验证100次: " + verifyTime + "ms");
}
}
安全建议
✅ 推荐:
- 使用SHA-256或更高哈希算法
- 密钥长度至少2048位
- 使用PKCS#1 v1.5或PSS填充方案
⚠️ 避免:
- MD5withRSA或SHA1withRSA(已不安全)
- 1024位密钥
DSA
原理
DSA (Digital Signature Algorithm) 是基于离散对数问题的数字签名算法。
技术规格
| 属性 | 值 |
|---|---|
| 密钥长度 | 1024/2048/3072位 |
| 签名长度 | 320位(1024位密钥)或更长 |
| 安全级别 | 中等(不如RSA/ECDSA流行) |
应用场景
- 遗留系统:兼容旧系统
- 特定标准要求
性能影响
- 签名速度:中等
- 验证速度:中等
- 签名长度:相对较小
Java实现示例
java
import java.security.*;
import java.security.spec.DSAParameterSpec;
public class DSAExample {
public static KeyPair generateKeyPair(int keySize) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(keySize);
return keyPairGenerator.generateKeyPair();
}
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withDSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return java.util.Base64.getEncoder().encodeToString(signBytes);
}
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withDSA");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = java.util.Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
public static void main(String[] args) throws Exception {
System.out.println("⚠️ 注意: DSA使用较少,建议使用RSA或ECDSA");
KeyPair keyPair = generateKeyPair(2048);
String data = "测试数据";
String signature = sign(data, keyPair.getPrivate());
System.out.println("签名: " + signature.substring(0, 50) + "...");
boolean isValid = verify(data, signature, keyPair.getPublic());
System.out.println("验证结果: " + isValid);
}
}
安全建议
⚠️ 建议迁移到RSA或ECDSA
ECDSA
原理
ECDSA (Elliptic Curve Digital Signature Algorithm) 是椭圆曲线版本的DSA,性能优于RSA和DSA。
技术规格
| 曲线 | 密钥长度 | 签名长度 | 等价RSA |
|---|---|---|---|
| P-256 | 256位 | 512位 | 3072位 |
| P-384 | 384位 | 768位 | 7680位 |
| P-521 | 521位 | 1042位 | 15360位 |
| secp256k1 | 256位 | 512位 | 3072位 |
应用场景
- 区块链:比特币、以太坊
- TLS/SSL:现代浏览器支持
- 移动设备:资源受限场景
- 高性能应用:需要快速签名验证
性能影响
- 签名速度:快(比RSA快)
- 验证速度:快
- 签名长度:小(比RSA小)
- CPU使用率:低
Java实现示例
java
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
public class ECDSAExample {
/**
* 生成ECDSA密钥对
*/
public static KeyPair generateKeyPair(String curveName) throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
ECGenParameterSpec ecSpec = new ECGenParameterSpec(curveName);
keyPairGenerator.initialize(ecSpec);
return keyPairGenerator.generateKeyPair();
}
/**
* ECDSA签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/**
* ECDSA验证
*/
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SHA256withECDSA");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
/**
* 不同曲线的签名
*/
public static void testDifferentCurves() throws Exception {
String[] curves = {"secp256r1", "secp384r1", "secp521r1"};
String data = "测试数据";
for (String curve : curves) {
KeyPair keyPair = generateKeyPair(curve);
long start = System.currentTimeMillis();
String signature = sign(data, keyPair.getPrivate());
long signTime = System.currentTimeMillis() - start;
start = System.currentTimeMillis();
verify(data, signature, keyPair.getPublic());
long verifyTime = System.currentTimeMillis() - start;
System.out.println(curve + " - 签名: " + signTime + "ms, 验证: " + verifyTime + "ms");
}
}
public static void main(String[] args) throws Exception {
System.out.println("=== ECDSA数字签名 ===");
KeyPair keyPair = generateKeyPair("secp256r1");
String data = "这是要签名的数据";
String signature = sign(data, keyPair.getPrivate());
System.out.println("数据: " + data);
System.out.println("签名: " + signature.substring(0, 50) + "...");
System.out.println("签名长度: " + signature.length() + " 字符");
boolean isValid = verify(data, signature, keyPair.getPublic());
System.out.println("验证结果: " + isValid);
System.out.println("\n=== 不同曲线性能 ===");
testDifferentCurves();
System.out.println("\n=== 与RSA性能对比 ===");
// RSA签名
KeyPairGenerator rsaGen = KeyPairGenerator.getInstance("RSA");
rsaGen.initialize(2048);
KeyPair rsaPair = rsaGen.generateKeyPair();
Signature rsaSig = Signature.getInstance("SHA256withRSA");
rsaSig.initSign(rsaPair.getPrivate());
rsaSig.update(data.getBytes());
long start = System.currentTimeMillis();
rsaSig.sign();
long rsaTime = System.currentTimeMillis() - start;
// ECDSA签名
Signature ecdsaSig = Signature.getInstance("SHA256withECDSA");
ecdsaSig.initSign(keyPair.getPrivate());
ecdsaSig.update(data.getBytes());
start = System.currentTimeMillis();
ecdsaSig.sign();
long ecdsaTime = System.currentTimeMillis() - start;
System.out.println("RSA-2048签名: " + rsaTime + "ms");
System.out.println("ECDSA P-256签名: " + ecdsaTime + "ms");
System.out.println("ECDSA比RSA快: " + (rsaTime / (double) ecdsaTime) + "倍");
}
}
安全建议
✅ 推荐:
- 使用P-256(secp256r1)或更高
- 用于区块链时使用secp256k1
- SHA-256或更高哈希算法
⚠️ 注意:
- 选择安全的曲线
- 确保随机数生成器的安全性
EdDSA
原理
EdDSA (Edwards-curve Digital Signature Algorithm) 是基于Edwards曲线的数字签名算法,是Ed25519和Ed448的统称。
技术规格
| 算法 | 曲线 | 密钥长度 | 签名长度 | 安全级别 |
|---|---|---|---|---|
| Ed25519 | Curve25519 | 256位 | 512位 | 高 |
| Ed448 | Curve448 | 448位 | 896位 | 非常高 |
应用场景
- 现代应用:需要高性能签名
- 新项目:推荐使用
- 加密货币:某些币种使用
- TLS 1.3:支持EdDSA
性能影响
- 签名速度:非常快
- 验证速度:非常快
- 签名长度:固定且较小
- CPU使用率:低
Java实现示例
java
// 注意:Java标准库在Java 15+支持EdDSA
// 或使用BouncyCastle库
import java.security.*;
import java.security.spec.NamedParameterSpec;
import java.util.Base64;
public class EdDSAExample {
/**
* 生成Ed25519密钥对
*/
public static KeyPair generateEd25519KeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("Ed25519");
keyPairGenerator.initialize(255);
return keyPairGenerator.generateKeyPair();
}
/**
* Ed25519签名
*/
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("Ed25519");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
/**
* Ed25519验证
*/
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("Ed25519");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
public static void main(String[] args) throws Exception {
System.out.println("=== EdDSA (Ed25519) 数字签名 ===");
KeyPair keyPair = generateEd25519KeyPair();
String data = "这是要签名的数据";
String signature = sign(data, keyPair.getPrivate());
System.out.println("数据: " + data);
System.out.println("签名: " + signature);
System.out.println("签名长度: " + signature.length() + " 字符");
boolean isValid = verify(data, signature, keyPair.getPublic());
System.out.println("验证结果: " + isValid);
// 性能测试
System.out.println("\n=== 性能测试 ===");
long start = System.currentTimeMillis();
for (int i = 0; i < 1000; i++) {
sign(data, keyPair.getPrivate());
}
long end = System.currentTimeMillis();
System.out.println("Ed25519签名1000次: " + (end - start) + "ms");
}
}
安全建议
✅ 推荐:
- Ed25519:性能和安全性最佳平衡
- Ed448:更高安全需求
⚠️ 注意:
- 需要Java 15+或BouncyCastle库
- 某些系统可能不支持
SM2签名
原理
SM2是基于椭圆曲线的数字签名算法,中国国家密码标准。
技术规格
| 属性 | 值 |
|---|---|
| 曲线 | sm2p256v1 |
| 密钥长度 | 256位 |
| 哈希算法 | SM3 |
| 安全级别 | 高(国密标准) |
应用场景
- 政府系统:符合国密要求
- 金融行业:国内银行、支付
- 信创项目:国产化替代
Java实现示例
java
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.util.Base64;
public class SM2SignatureExample {
static {
Security.addProvider(new BouncyCastleProvider());
}
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
keyPairGenerator.initialize(sm2Spec);
return keyPairGenerator.generateKeyPair();
}
public static String sign(String data, PrivateKey privateKey) throws Exception {
Signature signature = Signature.getInstance("SM3withSM2", "BC");
signature.initSign(privateKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = signature.sign();
return Base64.getEncoder().encodeToString(signBytes);
}
public static boolean verify(String data, String sign, PublicKey publicKey) throws Exception {
Signature signature = Signature.getInstance("SM3withSM2", "BC");
signature.initVerify(publicKey);
signature.update(data.getBytes("UTF-8"));
byte[] signBytes = Base64.getDecoder().decode(sign);
return signature.verify(signBytes);
}
public static void main(String[] args) throws Exception {
KeyPair keyPair = generateKeyPair();
String data = "测试数据";
String signature = sign(data, keyPair.getPrivate());
System.out.println("SM2签名: " + signature.substring(0, 50) + "...");
boolean isValid = verify(data, signature, keyPair.getPublic());
System.out.println("验证结果: " + isValid);
}
}
性能对比
数字签名算法性能对比
| 算法 | 密钥长度 | 签名速度 | 验证速度 | 签名长度 | 推荐度 |
|---|---|---|---|---|---|
| Ed25519 | 256位 | 非常快 | 非常快 | 512位 | ⭐⭐⭐⭐⭐ |
| ECDSA P-256 | 256位 | 快 | 快 | 512位 | ⭐⭐⭐⭐⭐ |
| RSA-2048 | 2048位 | 慢 | 快 | 2048位 | ⭐⭐⭐⭐ |
| ECDSA P-384 | 384位 | 快 | 快 | 768位 | ⭐⭐⭐⭐ |
| RSA-3072 | 3072位 | 很慢 | 慢 | 3072位 | ⭐⭐⭐ |
| SM2 | 256位 | 快 | 快 | 512位 | ⭐⭐⭐⭐ |
选择建议
现代应用:
- 首选:Ed25519 或 ECDSA P-256
- 备选:RSA-2048(兼容性要求)
国密合规:
- SM2
高安全需求:
- Ed448 或 ECDSA P-384
总结
推荐选择
通用场景:
- 首选:Ed25519 或 ECDSA P-256
- 备选:RSA-2048
特殊需求:
- 国密合规:SM2
- 最高性能:Ed25519
- 最高兼容性:RSA-2048
选择决策树
css
需要数字签名?
├─ 需要国密合规?→ SM2
├─ 性能优先?→ Ed25519
├─ 兼容性优先?→ RSA-2048
└─ 通用场景?→ ECDSA P-256