加密与签名技术之数字签名算法

概述

数字签名用于验证数据的完整性和来源真实性。使用私钥签名,公钥验证。

目录

  1. RSA签名
  2. DSA
  3. ECDSA
  4. EdDSA
  5. SM2签名
  6. 性能对比

RSA签名

原理

RSA签名使用私钥对数据哈希值进行加密,验证者使用公钥解密并与数据哈希比较。

签名流程:

  1. 计算数据哈希值 H = Hash(Data)
  2. 使用私钥签名 S = H^d mod n
  3. 使用公钥验证:H' = S^e mod n
  4. 比较 H == H'

技术规格

属性
密钥长度 2048/3072/4096位
签名长度 等于密钥长度
哈希算法 SHA-256, SHA-384, SHA-512
安全级别 高(2048位以上)

应用场景

  1. 数字证书:SSL/TLS证书签名
  2. 代码签名:软件发布
  3. 文档签名:PDF、Office文档
  4. API认证:JWT签名
  5. 邮件签名: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流行)

应用场景

  1. 遗留系统:兼容旧系统
  2. 特定标准要求

性能影响

  • 签名速度:中等
  • 验证速度:中等
  • 签名长度:相对较小

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位

应用场景

  1. 区块链:比特币、以太坊
  2. TLS/SSL:现代浏览器支持
  3. 移动设备:资源受限场景
  4. 高性能应用:需要快速签名验证

性能影响

  • 签名速度:快(比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位 非常高

应用场景

  1. 现代应用:需要高性能签名
  2. 新项目:推荐使用
  3. 加密货币:某些币种使用
  4. 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
安全级别 高(国密标准)

应用场景

  1. 政府系统:符合国密要求
  2. 金融行业:国内银行、支付
  3. 信创项目:国产化替代

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

高安全需求:

  • Ed448ECDSA P-384

总结

推荐选择

通用场景:

  • 首选:Ed25519 或 ECDSA P-256
  • 备选:RSA-2048

特殊需求:

  • 国密合规:SM2
  • 最高性能:Ed25519
  • 最高兼容性:RSA-2048

选择决策树

css 复制代码
需要数字签名?
├─ 需要国密合规?→ SM2
├─ 性能优先?→ Ed25519
├─ 兼容性优先?→ RSA-2048
└─ 通用场景?→ ECDSA P-256
相关推荐
解读玫瑰1 小时前
WSL+openEuler嵌入式开发实战:交叉编译与QEMU仿真全流程
后端
程序员爱钓鱼1 小时前
Node.js 编程实战:理解 Buffer 与 Stream
后端·node.js·trae
用户8356290780511 小时前
Word 图表自动化:基于 C# 的高效数据可视化方案
后端·c#
火车叼位2 小时前
让 ast-grep 听你的:指定语言解析 Vue/TSX/JSX 全流程
前端·javascript·后端
哈哈老师啊2 小时前
Springboot学生接送服务平台8rzvo(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
VX:Fegn08952 小时前
计算机毕业设计|基于springboot + vue图书商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序员爱钓鱼2 小时前
Node.js 编程实战:npm和yarn基础使用
后端·node.js·trae
程序员爱钓鱼3 小时前
Node.js 编程实战:CommonJS 与ES6 模块
后端·node.js·trae
开心猴爷3 小时前
构建可落地的 iOS 性能测试体系,从场景拆解到多工具协同的工程化实践
后端