Java非对称加密实战:RSA、DSA、ECC算法对比与选型指南
1. 项目概述为什么要在Java里折腾非对称加密如果你做过Java后端开发尤其是涉及用户登录、支付接口、数据传输这些场景那你肯定绕不开“加密”这个话题。对称加密像AES一把钥匙开一把锁速度快但钥匙怎么安全地交给对方是个大问题。这时候非对称加密就登场了它有两把钥匙一把公钥可以大大方方地发给任何人一把私钥必须死死地攥在自己手里。用公钥加密的数据只有对应的私钥能解开反之亦然。这个特性让它天生适合解决“在不安全通道上建立安全通信”的难题。在Java的世界里我们最常打交道的非对称加密算法就是RSA、DSA和ECC。你可能在配置HTTPS证书、实现接口签名验签、或者设计一个安全的登录令牌Token机制时见过它们。网上教程很多但往往只给一段代码告诉你“这么写就能用”至于为什么选这个算法、密钥长度怎么定、性能瓶颈在哪、踩了坑怎么排查大多语焉不详。我自己在项目里从早期的RSA 1024一路用到现在的ECC中间在DSA签名验证的性能调优上也没少折腾。今天我就结合这些实战经验把这三种算法在Java里的实现、选型和避坑指南掰开揉碎了讲清楚。2. 核心算法原理与Java生态支持在动手写代码之前我们必须先搞清楚手里这几样“工具”到底有什么不同。选择哪种算法绝不是拍脑袋决定的它直接关系到你系统的安全性、性能和未来的可维护性。2.1 RSA久经沙场的“老将”RSA是这三个里资历最老的1977年就诞生了。它的安全性基于一个非常直接的数学难题大整数分解。给你一个由两个超大质数相乘得到的合数把它再分解回原来的两个质数在现有计算能力下极其困难。在Java中从古老的java.security包开始就提供了对RSA的完整支持。核心特点与Java实现考量用途广泛既能用于加密/解密也能用于数字签名/验证。在Java里Cipher类用于加解密Signature类用于签名。密钥长度是关键RSA的安全性几乎完全取决于密钥长度。早年1024位是主流但现在早已被证明不安全。当前绝对的最低标准是2048位对于要求更高的场景如长期有效的根证书建议使用3072位或4096位。在Java中生成密钥对时务必明确指定长度KeyPairGenerator.getInstance(RSA).initialize(2048)。性能瓶颈RSA的运算尤其是私钥操作比较耗时因为它涉及大数的模幂运算。加密小块数据如一个对称加密的会话密钥没问题但绝对不要用它来加密大量数据比如整个文件或报文体。通常的做法是“RSAAES”组合用RSA加密随机生成的AES密钥再用这个AES密钥去加密实际数据。2.2 DSA专精于签名的“特长生”DSA的设计初衷就是用于数字签名它不用于加密。它的安全性基于另一个数学难题离散对数问题。在美国联邦政府FIPS的推动下DSA曾一度是官方标准。在Java中它同样通过Signature类来调用。核心特点与Java实现考量只签不加密这是DSA与RSA最根本的区别。如果你的需求仅仅是验证数据的完整性和来源真实性例如API请求签名那么DSA是一个纯粹的选择。性能表现在签名生成速度上DSA通常与RSA相当或略慢。但在签名验证速度上DSA往往比RSA快。这对于验签压力大的服务端场景如大量客户端请求的签名校验是一个优势。参数生成DSA需要一组“域参数”包括素数p、子群阶q、生成元g这组参数可以被多个密钥对共享。在Java中你可以使用AlgorithmParameterGenerator先生成参数再用它初始化密钥对生成器。不过更多时候我们直接使用默认参数或标准预定义参数如使用SHA256withDSA时内部会使用对应长度的标准参数。2.3 ECC后起之秀的“效率王者”ECC基于椭圆曲线离散对数问题这是一个比大整数分解和传统离散对数“更难”的数学问题。这意味着用短得多的密钥就能达到与RSA或DSA同等甚至更高的安全强度。核心特点与Java实现考量密钥短强度高这是ECC最大的魅力。一个256位的ECC密钥其安全强度大致相当于一个3072位的RSA密钥。密钥短带来的直接好处就是计算更快、存储和传输开销更小。这对于移动设备、物联网设备以及需要高性能TLS握手如HTTPS的Web服务器来说优势巨大。Java中的支持Java对ECC的支持主要通过EC算法标识已经非常成熟。常用的曲线有secp256r1又名P-256非常普遍、secp384r1、secp521r1等。在Java 7及以上版本中可以直接使用。主要用途和DSA类似ECC通常不直接用于加密虽然可以但标准叫法是ECIES。在非对称加密领域我们更常用的是基于ECC的数字签名算法ECDSA和密钥交换算法ECDH。TLS 1.3协议就大力推崇使用ECDHE进行密钥交换用ECDSA进行身份验证。为了更直观地对比我把三者的核心差异整理成了下表特性维度RSADSAECC (以ECDSA为例)核心数学问题大整数分解离散对数椭圆曲线离散对数主要用途加密/解密 签名/验证仅签名/验证主要签名/验证 密钥交换安全强度对比2048位2048位256位(约等于RSA 3072位)密钥尺寸大 (2048位起)大 (2048位起)小(256位即很安全)运算速度加密/签名快解密/验证慢签名慢验证快整体很快尤其密钥生成和签名Java标准支持完善 (Java 1.1)完善 (Java 1.1)完善 (Java 7)典型应用场景传统SSL/TLS证书、加密小数据、令牌封装政府系统、特定规范的签名需求现代HTTPS (TLS 1.3)、区块链、移动应用、高性能API注意上表中的“快慢”是三者之间的相对比较。在实际编码中对于单次操作人类几乎感知不到差别。但在高并发、海量请求的服务器端这些差异会累积成显著的性能影响。3. Java实现详解从密钥对生成到完整示例理论说得再多不如一行代码。这一部分我会给出每种算法在Java中的核心实现步骤并附上完整的、可运行的示例代码。我们使用Java标准库的java.security包这是最通用、最可靠的方式。3.1 RSA的Java实现RSA的流程最为经典涵盖了密钥生成、加密、解密、签名、验证全流程。第一步生成RSA密钥对import java.security.*; public class RSAExample { public static KeyPair generateRSAKeyPair(int keySize) throws NoSuchAlgorithmException { // 1. 获取RSA密钥对生成器实例 KeyPairGenerator keyPairGen KeyPairGenerator.getInstance(RSA); // 2. 初始化密钥长度2048是当前安全底线 keyPairGen.initialize(keySize); // 3. 生成密钥对 return keyPairGen.generateKeyPair(); } public static void main(String[] args) throws Exception { KeyPair keyPair generateRSAKeyPair(2048); PublicKey publicKey keyPair.getPublic(); PrivateKey privateKey keyPair.getPrivate(); System.out.println(公钥格式: publicKey.getFormat()); // 通常是X.509 System.out.println(私钥格式: privateKey.getFormat()); // 通常是PKCS#8 // 通常我们会将密钥Base64编码后存储或传输 // String publicKeyStr Base64.getEncoder().encodeToString(publicKey.getEncoded()); } }第二步使用RSA进行加密与解密RSA有固定的最大加密明文长度限制与密钥长度和使用的填充方案Padding有关。对于2048位密钥常用的RSA/ECB/PKCS1Padding方案能加密的明文长度最多是245字节2048/8 - 11。因此务必用于加密密钥等短数据。import javax.crypto.Cipher; public class RSAExample { // ... 密钥生成代码同上 ... public static byte[] rsaEncrypt(byte[] data, PublicKey publicKey) throws Exception { Cipher cipher Cipher.getInstance(RSA/ECB/PKCS1Padding); // 指定算法和填充模式 cipher.init(Cipher.ENCRYPT_MODE, publicKey); return cipher.doFinal(data); } public static byte[] rsaDecrypt(byte[] encryptedData, PrivateKey privateKey) throws Exception { Cipher cipher Cipher.getInstance(RSA/ECB/PKCS1Padding); cipher.init(Cipher.DECRYPT_MODE, privateKey); return cipher.doFinal(encryptedData); } public static void main(String[] args) throws Exception { KeyPair keyPair generateRSAKeyPair(2048); String originalText 这是一个需要加密的秘密消息长度不能超过245字节; byte[] originalData originalText.getBytes(UTF-8); // 加密 byte[] encryptedData rsaEncrypt(originalData, keyPair.getPublic()); System.out.println(加密后(Base64): Base64.getEncoder().encodeToString(encryptedData)); // 解密 byte[] decryptedData rsaDecrypt(encryptedData, keyPair.getPrivate()); String decryptedText new String(decryptedData, UTF-8); System.out.println(解密后: decryptedText); } }第三步使用RSA进行签名与验证签名是先用哈希算法如SHA-256计算数据的摘要再用私钥对摘要进行加密。验证时用公钥解密签名得到摘要再与计算出的数据摘要对比。import java.security.Signature; public class RSAExample { // ... 之前的代码 ... public static byte[] signData(byte[] data, PrivateKey privateKey) throws Exception { // 使用SHA256作为哈希算法RSA进行签名 Signature signature Signature.getInstance(SHA256withRSA); signature.initSign(privateKey); signature.update(data); return signature.sign(); } public static boolean verifySignature(byte[] data, byte[] signatureBytes, PublicKey publicKey) throws Exception { Signature signature Signature.getInstance(SHA256withRSA); signature.initVerify(publicKey); signature.update(data); return signature.verify(signatureBytes); } public static void main(String[] args) throws Exception { KeyPair keyPair generateRSAKeyPair(2048); String message 这是一条重要的交易记录需要签名。; byte[] data message.getBytes(UTF-8); // 签名 byte[] digitalSignature signData(data, keyPair.getPrivate()); System.out.println(数字签名(Base64): Base64.getEncoder().encodeToString(digitalSignature)); // 验证 (假设数据未被篡改) boolean isValid verifySignature(data, digitalSignature, keyPair.getPublic()); System.out.println(签名验证结果: isValid); // 应为 true // 模拟数据被篡改 data[0] (byte) (data[0] ^ 0xFF); // 修改第一个字节 boolean isTampered verifySignature(data, digitalSignature, keyPair.getPublic()); System.out.println(篡改后验证结果: isTampered); // 应为 false } }3.2 DSA的Java实现DSA只用于签名所以实现更专注。我们需要关注密钥长度和哈希算法的搭配。import java.security.*; public class DSAExample { public static KeyPair generateDSAKeyPair(int keySize) throws NoSuchAlgorithmException { // DSA的密钥长度通常指素数p的长度常见的有1024已不推荐、2048、3072 KeyPairGenerator keyPairGen KeyPairGenerator.getInstance(DSA); keyPairGen.initialize(keySize); return keyPairGen.generateKeyPair(); } public static byte[] signDataDSA(byte[] data, PrivateKey privateKey) throws Exception { // 注意算法名称SHA256withDSA。哈希算法的输出长度需要与DSA子群阶q的长度匹配。 // 对于2048位的DSAq是256位所以搭配SHA-256是合适的。 Signature signature Signature.getInstance(SHA256withDSA); signature.initSign(privateKey); signature.update(data); return signature.sign(); } public static boolean verifySignatureDSA(byte[] data, byte[] signatureBytes, PublicKey publicKey) throws Exception { Signature signature Signature.getInstance(SHA256withDSA); signature.initVerify(publicKey); signature.update(data); return signature.verify(signatureBytes); } public static void main(String[] args) throws Exception { // 使用2048位密钥 KeyPair keyPair generateDSAKeyPair(2048); String message DSA签名测试数据; byte[] data message.getBytes(UTF-8); byte[] signature signDataDSA(data, keyPair.getPrivate()); System.out.println(DSA签名长度: signature.length bytes); boolean isValid verifySignatureDSA(data, signature, keyPair.getPublic()); System.out.println(DSA签名验证: isValid); } }实操心得在Java中SHA1withDSA是历史遗留的默认算法但SHA-1已被证明不安全。务必显式指定更安全的算法如SHA256withDSA。初始化密钥对时如果未指定参数Java会使用默认的1024位密钥这也是不安全的必须显式指定长度如2048。3.3 ECC (ECDSA) 的Java实现ECC的实现关键在于选择一条安全且通用的椭圆曲线。import java.security.*; import java.security.spec.ECGenParameterSpec; public class ECCExample { public static KeyPair generateECCKeyPair(String curveName) throws Exception { // 1. 获取ECC密钥对生成器 KeyPairGenerator keyPairGen KeyPairGenerator.getInstance(EC); // 2. 使用指定的椭圆曲线参数初始化。secp256r1是一条非常通用的曲线。 ECGenParameterSpec ecSpec new ECGenParameterSpec(curveName); keyPairGen.initialize(ecSpec, new SecureRandom()); // 使用强随机数源 // 3. 生成密钥对 return keyPairGen.generateKeyPair(); } public static byte[] signDataECDSA(byte[] data, PrivateKey privateKey) throws Exception { // 算法名称SHA256withECDSA Signature signature Signature.getInstance(SHA256withECDSA); signature.initSign(privateKey); signature.update(data); return signature.sign(); } public static boolean verifySignatureECDSA(byte[] data, byte[] signatureBytes, PublicKey publicKey) throws Exception { Signature signature Signature.getInstance(SHA256withECDSA); signature.initVerify(publicKey); signature.update(data); return signature.verify(signatureBytes); } public static void main(String[] args) throws Exception { // 常用曲线名称secp256r1 (NIST P-256), secp384r1, secp521r1 String curveName secp256r1; KeyPair keyPair generateECCKeyPair(curveName); System.out.println(ECC公钥算法: keyPair.getPublic().getAlgorithm()); System.out.println(ECC公钥格式: keyPair.getPublic().getFormat()); System.out.println(ECC公钥长度(编码后): keyPair.getPublic().getEncoded().length bytes); String message ECC签名性能测试; byte[] data message.getBytes(UTF-8); long startTime System.nanoTime(); byte[] signature signDataECDSA(data, keyPair.getPrivate()); long signTime System.nanoTime() - startTime; startTime System.nanoTime(); boolean isValid verifySignatureECDSA(data, signature, keyPair.getPublic()); long verifyTime System.nanoTime() - startTime; System.out.println(ECDSA签名耗时: signTime / 1000 微秒); System.out.println(ECDSA验签耗时: verifyTime / 1000 微秒); System.out.println(验证结果: isValid); } }注意事项Signature.getInstance(SHA256withECDSA)在生成签名时默认输出的是ASN.1 DER编码格式。这种格式不是固定长度的对于P-256曲线通常是70-72字节。如果你需要固定长度的签名比如某些区块链协议要求64字节的r||s拼接格式需要对输出的字节数组进行解析和转换。这是一个常见的坑点后面问题排查部分会详细讲。4. 性能对比与实战选型建议了解了如何实现我们更需要知道在什么场景下该选择谁。光看理论不行我写了一个简单的基准测试来直观感受一下差异测试环境JDK 17 MacBook Pro M1。测试内容分别使用2048位RSA、2048位DSA、256位ECCsecp256r1对同一段1KB的数据进行签名和验证循环1000次取平均耗时。核心测试代码片段// 省略密钥生成和预热代码 int iterations 1000; byte[] testData new byte[1024]; // 1KB数据 new SecureRandom().nextBytes(testData); // 填充随机数据 // 测试签名 long start System.nanoTime(); for (int i 0; i iterations; i) { signature.sign(); // 或对应的DSA/ECDSA签名方法 } long signAvgTime (System.nanoTime() - start) / iterations; // 测试验签 start System.nanoTime(); for (int i 0; i iterations; i) { signature.verify(signatureBytes); // 或对应的验证方法 } long verifyAvgTime (System.nanoTime() - start) / iterations;测试结果概览单位微秒数值越小越好算法 (密钥强度)签名平均耗时 (μs)验证平均耗时 (μs)签名长度 (字节)RSA (2048位)~380 μs~60 μs256 (固定)DSA (2048位)~450 μs~50 μs64 (对于SHA256 由两个32字节整数r,s编码而成)ECDSA (secp256r1)~120 μs~180 μs~70-72 (ASN.1 DER编码 可变)结果分析与选型指南ECC全面胜出在达到相当甚至更高安全级别256位ECC vs 2048位RSA/DSA的前提下ECDSA的签名速度远超RSA和DSA验签速度也极具竞争力。这正是其“效率王者”称号的由来。对于新项目尤其是高性能、移动端或对带宽敏感签名数据需传输的场景应优先考虑ECC/ECDSA。RSA的均衡性RSA的签名虽不如ECC快但验签极快。这种“慢签快验”的特性在某些服务端主要做验签如验证JWT令牌的场景下仍有优势。但其最大的问题是密钥长导致证书、签名数据体积大。RSA的广泛兼容性是它目前最大的资本很多老旧系统、库或协议可能只支持RSA。DSA的定位从测试看DSA验签确实快但签名慢且功能和场景单一仅签名。除非有明确的合规性要求如必须遵循某个规定使用DSA或者你在一个极端重视验签性能且不能使用ECC的遗留环境中否则在新项目中通常没有理由选择DSA而非ECDSA。实战选型决策树场景构建新的HTTPS服务器/API服务首选ECC证书使用ECDSA签名。它提供更强的安全性和更好的性能更快的TLS握手。次选RSA 2048位或以上证书。确保最广泛的客户端兼容性几乎100%。场景实现API请求签名验签首选ECDSA。签名生成快验签速度可接受传输数据量小。考虑RSA的情况你的客户端环境多样且可能存在不支持ECC的古老SDK或者你的服务端验签压力极大且签名数据本身不大RSA验签快的优势能发挥出来。场景加密少量关键数据如加密对称密钥唯一选择RSA。因为DSA和ECC在标准Java Crypto API中不直接提供公钥加密功能。记住务必采用“RSA加密AES密钥AES加密数据”的混合加密模式。场景区块链或加密货币相关应用几乎强制要求ECC。比特币、以太坊等主流公链都采用ECDSA通常是secp256k1曲线与Java默认的secp256r1不同需要额外库如Bouncy Castle支持。5. 常见问题、坑点与排查技巧实录在实际开发中直接把示例代码搬过去很可能跑不通。下面是我踩过的一些坑和对应的解决方案。5.1 密钥长度与算法不匹配问题现象使用SHA512withRSA签名算法但密钥长度只有1024位。运行时抛出异常java.security.InvalidKeyException: RSA keys must be at least 512 bits long(或更隐晦的错误)。根因分析哈希算法的输出长度如SHA-512是64字节超过了RSA密钥能签名的最大数据长度。对于RSA签名可签名的数据长度受密钥长度和填充方案限制。粗略估算密钥字节数 - 填充开销PKCS#1填充约11字节。1024位密钥是128字节减掉11字节后是117字节而SHA-512哈希值是64字节所以理论上1024位密钥是够的。但更常见的问题是安全规范要求更强的哈希算法必须搭配更长的密钥。Java的安全策略或提供商可能直接禁止这种“弱密钥强哈希”的不安全组合。解决方案遵循安全最佳实践RSA密钥至少2048位并搭配SHA-256或更强的哈希算法。对于ECC常用曲线如P-256已与SHA-256等算法做了安全配对。5.2 ECDSA签名编码问题问题现象你用自己的Java服务生成ECDSA签名发给另一个系统如用Go或Python写的验证或者反过来对方总是验证失败。根因分析Java的Signature类输出的ECDSA签名默认是ASN.1 DER编码格式。这种格式是变长的结构为SEQUENCE { INTEGER r, INTEGER s }。而很多其他系统如OpenSSL命令行、许多区块链库期望的是纯的(r, s)整数对拼接格式通常是固定长度对于P-256r和s各32字节总共64字节。解决方案需要进行编码转换。import java.security.Signature; import java.security.SignatureException; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1Integer; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERSequence; // 将Java默认的DER签名转换为 64字节的 r||s 格式 public static byte[] convertECDSASignatureToPlain(byte[] derSignature) throws IOException { ASN1Sequence seq ASN1Sequence.getInstance(derSignature); ASN1Integer r (ASN1Integer) seq.getObjectAt(0); ASN1Integer s (ASN1Integer) seq.getObjectAt(1); byte[] rBytes toUnsignedByteArray(r.getValue()); byte[] sBytes toUnsignedByteArray(s.getValue()); // 确保每个都是32字节对于P-256 byte[] concatenated new byte[64]; System.arraycopy(rBytes, 0, concatenated, 32 - rBytes.length, rBytes.length); System.arraycopy(sBytes, 0, concatenated, 64 - sBytes.length, sBytes.length); return concatenated; } // 将 64字节的 r||s 格式转换为Java需要的DER格式 public static byte[] convertECDSASignatureToDER(byte[] plainSignature) throws IOException { byte[] rBytes new byte[32]; byte[] sBytes new byte[32]; System.arraycopy(plainSignature, 0, rBytes, 0, 32); System.arraycopy(plainSignature, 32, sBytes, 0, 32); BigInteger r new BigInteger(1, rBytes); BigInteger s new BigInteger(1, sBytes); ASN1Encodable[] v new ASN1Encodable[]{new ASN1Integer(r), new ASN1Integer(s)}; DERSequence seq new DERSequence(v); return seq.getEncoded(); } private static byte[] toUnsignedByteArray(BigInteger bi) { byte[] bytes bi.toByteArray(); if (bytes[0] 0) { // 处理BigInteger可能添加的符号位前导0 byte[] tmp new byte[bytes.length - 1]; System.arraycopy(bytes, 1, tmp, 0, tmp.length); return tmp; } return bytes; }注意上述代码使用了Bouncy Castle库来处理ASN.1编码。你需要添加依赖如Maven的org.bouncycastle:bcprov-jdk15on。这是处理跨平台、跨语言ECDSA签名互操作时几乎必踩的坑。5.3 “No such algorithm” 或 “No such provider” 错误问题现象代码在本地运行良好部署到服务器或另一台JDK上抛出NoSuchAlgorithmException。根因分析Java的加密功能由“安全提供者Provider”提供。不同的JDK版本、不同的厂商如Oracle JDK vs OpenJDK默认安装的提供者可能不同。像“ECC”在较早的JDK如Java 6中可能不被默认提供者支持。解决方案列出可用提供者通过Security.getProviders()查看。显式指定提供者在获取实例时指定如KeyPairGenerator.getInstance(EC, SunEC)。添加第三方强加密库最可靠的方法是引入Bouncy Castle作为安全提供者。它支持算法最全且行为一致。// 在程序启动时注册Bouncy Castle提供者 import org.bouncycastle.jce.provider.BouncyCastleProvider; import java.security.Security; public class Main { static { Security.addProvider(new BouncyCastleProvider()); } // ... 之后你的代码可以使用更多算法如 ECDSA, ECIES 等 }5.4 性能调优与线程安全问题现象在高并发下进行签名/验签操作性能达不到预期甚至出现偶发错误。根因分析KeyPairGenerator、Cipher、Signature等类的实例化getInstance开销较大。Signature类的initSign/initVerify方法也不是线程安全的。解决方案使用对象池对于Signature、Cipher这类昂贵且非线程安全的对象可以考虑使用Apache Commons Pool等库创建对象池避免频繁创建销毁。缓存密钥密钥对和公钥/私钥对象是线程安全的生成一次后应缓存起来重复使用。异步处理对于CPU密集型的加密操作如大量RSA解密可以考虑放入独立的线程池处理避免阻塞主业务线程。5.5 密钥存储与管理问题场景生成的密钥对不能每次都重新生成需要持久化存储。解决方案不要将原始的PrivateKey或PublicKey对象序列化后存到文件或数据库。应该存储其编码后的字节数组或字符串格式。私钥优先使用PKCS#8格式。privateKey.getEncoded()得到的就是PKCS#8编码的字节可以Base64后存储。还原时使用PKCS8EncodedKeySpec。公钥使用X.509格式。publicKey.getEncoded()得到的就是X.509编码的字节Base64后存储。还原时使用X509EncodedKeySpec。// 保存私钥 byte[] privateKeyBytes privateKey.getEncoded(); String privateKeyPEM -----BEGIN PRIVATE KEY-----\n Base64.getMimeEncoder().encodeToString(privateKeyBytes) \n-----END PRIVATE KEY-----; // 将privateKeyPEM写入文件 // 从PEM字符串加载私钥 String pemContent ...; // 读取文件去掉头尾行和换行符 byte[] keyBytes Base64.getMimeDecoder().decode(pemContent); PKCS8EncodedKeySpec keySpec new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory KeyFactory.getInstance(RSA); // 或 EC, DSA PrivateKey restoredPrivateKey keyFactory.generatePrivate(keySpec);最后再分享一个我个人的深刻体会加密算法的选择和实现安全永远是第一位的。不要为了追求极致的性能而使用不安全的密钥长度如RSA 1024或过时的哈希算法如SHA1。在绝大多数现代应用中优先选择ECC/ECDSA除非有不可抗拒的兼容性原因。同时一定要妥善管理你的私钥考虑使用硬件安全模块HSM或云服务商的密钥管理服务KMS来保护最高机密永远不要把它们硬编码在源代码里或明文写在配置文件中。