ECDSA与RSASSA-PSS:数字签名的艺术与科学
引言:数字世界中的"亲笔签名"
想象一下,您正在数字世界中签署一份重要文件。如何确保这个签名是您亲手所签,且不被他人伪造?这就是数字签名算法诞生的初衷。数字签名如同现实世界的印章和签名,但在数学的严密性下,达到了前所未有的安全水平。
数字签名的演变之路
1980年代,随着公钥密码学的兴起,数字签名开始崭露头角。从最初的RSA签名到如今的多种算法并存,数字签名技术经历了从简单到复杂、从易受到安全的演进过程。在这个过程中,ECDSA和RSASSA-PSS成为了两个重要的里程碑。
第一部分:ECDSA - 椭圆曲线的优雅之舞
1.1 椭圆曲线:从数学到密码学的奇妙旅程
椭圆曲线密码学源于一个看似简单的问题:如何找到满足方程 y² = x³ + ax + b 的点?这些点形成的图形虽然名为"椭圆曲线",但看起来一点也不像椭圆!
关键特性:
- 加法运算的封闭性:曲线上任意两点相加,结果仍在曲线上
- 离散对数问题的困难性:已知点P和kP,求k极其困难(这个"困难"是安全的基石)
- 密钥尺寸小:同等安全强度下,椭圆曲线密钥远小于RSA密钥
# 椭圆曲线方程示例(secp256k1曲线,比特币使用)
y² = x³ + 7 (mod p)
其中 p = 2²⁵⁶ - 2³² - 2⁹ - 2⁸ - 2⁷ - 2⁶ - 2⁴ - 1
1.2 ECDSA算法详解:四步曲的精密协奏
让我们将ECDSA算法拆解为四个清晰步骤:
步骤1:密钥生成 - 铸造您的数字印章
椭圆曲线参数 = (曲线类型, 基点G, 阶n)
私钥d = 随机选择 [1, n-1] 之间的整数
公钥Q = d × G (椭圆曲线标量乘法)
可视化示例:
私钥生成:在巨大的数字海洋中随机抛锚
公钥计算:通过椭圆曲线乘法将私钥"映射"到曲线上
步骤2:签名生成 - 为消息烙上独特印记
当您要签署消息m时:
// 以下是签名生成的核心步骤(概念性代码)
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/sha.h>
/**
* @brief 生成ECDSA签名
* @param[in] private_key 私钥
* @param[in] message 待签名的消息
* @param[in] message_len 消息长度
* @param[out] signature 生成的签名(输出参数)
* @param[out] sig_len 签名长度(输出参数)
* @return 成功返回1,失败返回0
*/
int ecdsa_sign(const EC_KEY *private_key,
const unsigned char *message,
size_t message_len,
unsigned char *signature,
unsigned int *sig_len) {
// 1. 计算消息的哈希值
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, hash);
// 2. 使用ECDSA签名
return ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH,
signature, sig_len, private_key);
}
签名生成流程详解:
- 计算哈希:将任意长度的消息压缩为固定长度的"数字指纹"
- 生成随机数k:这是签名的"盐",确保每次签名都不同
- 计算临时点:R = k × G,取其x坐标r = R.x mod n
- 计算签名值:s = k⁻¹(hash + d·r) mod n
- 输出签名:(r, s) 对
流程图解:
消息 → SHA-256哈希 → 随机数k生成 → 计算R = k×G → 提取r → 计算s → 签名(r,s)
步骤3:签名验证 - 真伪鉴定的艺术
验证者需要确认签名是否有效:
/**
* @brief 验证ECDSA签名
* @param[in] public_key 公钥
* @param[in] message 原始消息
* @param[in] message_len 消息长度
* @param[in] signature 待验证的签名
* @param[in] sig_len 签名长度
* @return 签名有效返回1,无效返回0,错误返回-1
*/
int ecdsa_verify(const EC_KEY *public_key,
const unsigned char *message,
size_t message_len,
const unsigned char *signature,
unsigned int sig_len) {
// 1. 计算消息哈希
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, hash);
// 2. 验证ECDSA签名
return ECDSA_verify(0, hash, SHA256_DIGEST_LENGTH,
signature, sig_len, public_key);
}
验证流程详解:
- 计算哈希:与签名时相同的方式处理消息
- 计算中间值:w = s⁻¹ mod n
- 计算点:u₁ = hash·w mod n, u₂ = r·w mod n
- 计算验证点:V = u₁×G + u₂×Q
- 验证:检查V.x mod n 是否等于 r
数学原理证明:
验证方程推导:
已知:s = k⁻¹(hash + d·r) mod n
推导:k = s⁻¹(hash + d·r) mod n
= hash·s⁻¹ + d·r·s⁻¹ mod n
= u₁ + d·u₂ mod n
因此:k×G = u₁×G + u₂×(d×G)
= u₁×G + u₂×Q
= V
由于R = k×G,且r = R.x mod n
所以V.x mod n 应该等于 r
步骤4:安全性与实现细节
关键安全考虑:
- 随机数k的重要性:k必须真正随机且唯一,否则私钥可能泄露
- 侧信道攻击防护:实现时需防范时序攻击、功耗分析等
- 曲线选择:不同曲线有不同的安全性和性能特性
1.3 ECDSA在实际中的应用:比特币的交易验证
让我们以比特币交易为例,看看ECDSA如何保障加密货币的安全:
// 比特币交易签名的简化示例
#include <openssl/ec.h>
#include <openssl/bn.h>
#include <stdint.h>
/**
* @brief 比特币交易签名结构
*/
typedef struct {
uint8_t r[32]; // 签名r值
uint8_t s[32]; // 签名s值
uint8_t recovery_id; // 恢复ID(用于恢复公钥)
} bitcoin_signature_t;
/**
* @brief 签署比特币交易
* @param[in] private_key 私钥(32字节)
* @param[in] transaction 交易数据
* @param[in] tx_len 交易长度
* @param[out] sig 输出签名
* @return 成功返回1,失败返回0
*/
int sign_bitcoin_transaction(const uint8_t private_key[32],
const uint8_t *transaction,
size_t tx_len,
bitcoin_signature_t *sig) {
// 1. 序列化交易数据
uint8_t tx_hash[32];
double_sha256(transaction, tx_len, tx_hash); // 比特币使用双SHA-256
// 2. 创建ECDSA签名(使用secp256k1曲线)
ECDSA_SIG *ecdsa_sig = ECDSA_do_sign(tx_hash, 32,
load_private_key(private_key));
if (!ecdsa_sig) return 0;
// 3. 提取r和s值
const BIGNUM *r, *s;
ECDSA_SIG_get0(ecdsa_sig, &r, &s);
// 4. 转换为规范格式
BN_bn2binpad(r, sig->r, 32);
BN_bn2binpad(s, sig->s, 32);
// 5. 计算恢复ID(用于公钥恢复)
sig->recovery_id = calculate_recovery_id(r, s, tx_hash);
ECDSA_SIG_free(ecdsa_sig);
return 1;
}
比特币中的ECDSA流程:
交易数据 → 双SHA-256哈希 → ECDSA签名 → 包含在交易中 → 全网验证
第二部分:RSASSA-PSS - 概率签名的智慧设计
2.1 RSA的数学基础:大数分解的难题
RSA的安全性基于一个简单而深刻的事实:将两个大质数相乘很容易,但将乘积分解回质数极其困难。
数学原理:
密钥生成:
1. 选择两个大质数 p 和 q
2. 计算 n = p × q
3. 计算 φ(n) = (p-1)(q-1)
4. 选择 e 使得 1 < e < φ(n) 且 gcd(e, φ(n)) = 1
5. 计算 d 使得 d × e ≡ 1 (mod φ(n))
公钥: (n, e)
私钥: (n, d)
加密: c = m^e mod n
解密: m = c^d mod n
签名: s = m^d mod n
验证: m = s^e mod n
2.2 PSS填充方案:为什么需要"加盐"?
传统的RSA签名存在安全隐患,特别是确定性签名容易受到选择明文攻击。PSS(Probabilistic Signature Scheme)通过引入随机性解决了这一问题。
PSS的核心思想:
- 随机化:每次签名都不同,即使对同一消息
- 编码结构:确保签名格式的一致性
- 安全性证明:可证明安全(在随机预言机模型下)
2.3 RSASSA-PSS算法详解:精心设计的签名流水线
步骤1:编码操作 - 消息的"包装艺术"
EM = MGF1(盐) ⊕ 填充字符串 || 哈希(消息) || 盐 || BC
其中:
- MGF1:掩码生成函数,通常基于SHA-256
- 盐:随机生成,通常与哈希输出长度相同
- BC:固定的尾部字节
// PSS编码的核心过程(概念性代码)
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
/**
* @brief PSS编码函数
* @param[in] mHash 消息哈希值
* @param[in] salt_len 盐的长度
* @param[out] EM 编码后的消息(输出参数)
* @param[in] emBits 编码消息的比特长度
* @return 成功返回1,失败返回0
*/
int pss_encode(const unsigned char *mHash,
int salt_len,
unsigned char *EM,
int emBits) {
int hLen = SHA256_DIGEST_LENGTH; // 哈希长度
int emLen = (emBits + 7) / 8; // 字节长度
// 1. 生成随机盐
unsigned char *salt = malloc(salt_len);
RAND_bytes(salt, salt_len);
// 2. 构造数据块 M'
// M' = 填充字符串 || mHash || 盐
unsigned char M_prime[8 + hLen + salt_len];
memset(M_prime, 0, 8); // 前8字节为0
memcpy(M_prime + 8, mHash, hLen);
memcpy(M_prime + 8 + hLen, salt, salt_len);
// 3. 计算 H = Hash(M')
unsigned char H[SHA256_DIGEST_LENGTH];
SHA256(M_prime, sizeof(M_prime), H);
// 4. 构造数据块 DB
// DB = 填充字符串2 || 盐
unsigned char *DB = malloc(emLen - hLen - 1);
memset(DB, 0, emLen - hLen - salt_len - 2);
DB[emLen - hLen - salt_len - 2] = 0x01;
memcpy(DB + emLen - hLen - salt_len - 1, salt, salt_len);
// 5. 生成掩码 dbMask
unsigned char *dbMask = malloc(emLen - hLen - 1);
mgf1(dbMask, emLen - hLen - 1, H, hLen);
// 6. 计算 maskedDB = DB ⊕ dbMask
for (int i = 0; i < emLen - hLen - 1; i++) {
EM[i] = DB[i] ^ dbMask[i];
}
// 7. 设置最左边的比特为0(确保EM < n)
EM[0] &= 0x7F;
// 8. 组合最终EM
memcpy(EM + emLen - hLen - 1, H, hLen);
EM[emLen - 1] = 0xBC;
free(salt);
free(DB);
free(dbMask);
return 1;
}
步骤2:签名生成 - RSA加密的精髓
/**
* @brief 生成RSASSA-PSS签名
* @param[in] rsa_key RSA私钥
* @param[in] message 待签名的消息
* @param[in] message_len 消息长度
* @param[out] signature 生成的签名(输出参数)
* @param[out] sig_len 签名长度(输出参数)
* @return 成功返回1,失败返回0
*/
int rsassa_pss_sign(RSA *rsa_key,
const unsigned char *message,
size_t message_len,
unsigned char *signature,
unsigned int *sig_len) {
// 1. 计算消息哈希
unsigned char mHash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, mHash);
// 2. 获取RSA模数长度
int rsa_size = RSA_size(rsa_key);
int emBits = rsa_size * 8 - 1; // 留出1比特确保签名 < n
// 3. 使用PSS编码
unsigned char *EM = malloc(rsa_size);
pss_encode(mHash, SHA256_DIGEST_LENGTH, EM, emBits);
// 4. RSA私钥操作:s = EM^d mod n
int result = RSA_private_encrypt(rsa_size, EM, signature,
rsa_key, RSA_NO_PADDING);
*sig_len = result;
free(EM);
return (result > 0) ? 1 : 0;
}
步骤3:签名验证 - 解构与确认
/**
* @brief 验证RSASSA-PSS签名
* @param[in] rsa_key RSA公钥
* @param[in] message 原始消息
* @param[in] message_len 消息长度
* @param[in] signature 待验证的签名
* @param[in] sig_len 签名长度
* @return 签名有效返回1,无效返回0,错误返回-1
*/
int rsassa_pss_verify(RSA *rsa_key,
const unsigned char *message,
size_t message_len,
const unsigned char *signature,
unsigned int sig_len) {
// 1. 计算消息哈希
unsigned char mHash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, mHash);
// 2. RSA公钥操作:EM' = s^e mod n
int rsa_size = RSA_size(rsa_key);
unsigned char *EM_prime = malloc(rsa_size);
int result = RSA_public_decrypt(rsa_size, signature, EM_prime,
rsa_key, RSA_NO_PADDING);
if (result != rsa_size) {
free(EM_prime);
return -1;
}
// 3. 验证PSS编码
int emBits = rsa_size * 8 - 1;
int verification = pss_verify(mHash, SHA256_DIGEST_LENGTH,
EM_prime, emBits);
free(EM_prime);
return verification;
}
2.4 PSS验证的详细流程
PSS验证是一个反向解码的过程:
1. 解码结构检查:
- 确认尾部字节为0xBC
- 提取H'(哈希值部分)
- 提取maskedDB(掩码后的数据部分)
2. 恢复DB:
dbMask = MGF1(H', emLen - hLen - 1)
DB = maskedDB ⊕ dbMask
3. 结构验证:
- 确认最左边比特为0
- 提取盐值
- 验证填充字符串
4. 哈希验证:
重新计算 H = Hash(0...0 || mHash || 盐)
比较 H 与 H'
2.5 RSASSA-PSS在现代TLS/SSL中的应用
在TLS 1.3中,RSASSA-PSS成为推荐的RSA签名方案:
// TLS 1.3中证书签名验证的简化示例
#include <openssl/x509.h>
#include <openssl/pem.h>
/**
* @brief 验证TLS证书的RSASSA-PSS签名
* @param[in] cert_file 证书文件路径
* @param[in] ca_file CA证书文件路径
* @return 验证成功返回1,失败返回0
*/
int verify_tls_certificate(const char *cert_file, const char *ca_file) {
// 1. 加载CA证书
FILE *ca_fp = fopen(ca_file, "r");
X509 *ca_cert = PEM_read_X509(ca_fp, NULL, NULL, NULL);
fclose(ca_fp);
// 2. 加载待验证证书
FILE *cert_fp = fopen(cert_file, "r");
X509 *cert = PEM_read_X509(cert_fp, NULL, NULL, NULL);
fclose(cert_fp);
// 3. 提取CA公钥
EVP_PKEY *ca_pubkey = X509_get_pubkey(ca_cert);
// 4. 创建验证上下文
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
// 5. 初始化PSS验证参数
EVP_PKEY_CTX *pkey_ctx = NULL;
EVP_DigestVerifyInit(ctx, &pkey_ctx,
EVP_sha256(), NULL, ca_pubkey);
// 设置PSS参数
EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO);
// 6. 获取证书的签名信息
ASN1_BIT_STRING *signature = cert->signature;
X509_ALGOR *sig_alg = cert->sig_alg;
// 7. 计算证书的TBSCertificate哈希
unsigned char tbs_hash[SHA256_DIGEST_LENGTH];
unsigned char *tbs_data = NULL;
int tbs_len = i2d_re_X509_tbs(cert, &tbs_data);
SHA256(tbs_data, tbs_len, tbs_hash);
// 8. 验证签名
int result = EVP_DigestVerify(ctx, signature->data, signature->length,
tbs_hash, SHA256_DIGEST_LENGTH);
// 9. 清理资源
EVP_MD_CTX_free(ctx);
EVP_PKEY_free(ca_pubkey);
X509_free(ca_cert);
X509_free(cert);
free(tbs_data);
return (result == 1) ? 1 : 0;
}
第三部分:深度对比与选择指南
3.1 性能对比:速度与资源的平衡
| 特性 | ECDSA (P-256) | RSASSA-PSS (2048位) | RSASSA-PSS (3072位) |
|---|---|---|---|
| 签名速度 | 快 (0.3ms) | 中等 (1.2ms) | 慢 (3.5ms) |
| 验证速度 | 中等 (0.6ms) | 快 (0.1ms) | 中等 (0.3ms) |
| 密钥大小 | 32字节私钥 64字节公钥 | 256字节私钥 256字节公钥 | 384字节私钥 384字节公钥 |
| 签名大小 | 64字节 | 256字节 | 384字节 |
| 安全强度 | 128位 | 112位 | 128位 |
| 内存占用 | 低 | 中等 | 高 |
3.2 安全性分析:攻击面与防御机制
ECDSA的攻击面:
- 随机数重用:如果k值重复,私钥可能泄露
- 侧信道攻击:通过时间、功耗等物理泄露获取信息
- 曲线选择:弱曲线可能导致安全问题
RSASSA-PSS的攻击面:
- 填充预言机攻击:已通过PSS的概率特性缓解
- 大数分解进展:量子计算机的潜在威胁
- 实现漏洞:如Bleichenbacher攻击
3.3 选择指南:何时使用哪种算法?
选择ECDSA当:
- ✅ 资源受限环境(物联网设备、智能卡)
- ✅ 带宽敏感应用(区块链交易、移动通信)
- ✅ 需要短签名(证书链优化)
- ✅ 批量签名操作(服务器端)
选择RSASSA-PSS当:
- ✅ 需要兼容传统系统
- ✅ 验证性能是关键需求(TLS握手)
- ✅ 硬件加速支持良好(HSM、智能卡)
- ✅ 需要可证明的安全性保证
3.4 量子计算时代的考量
量子计算机对两种算法的影响:
- ECDSA:易受Shor算法攻击,256位曲线在量子计算机下安全性降至128位
- RSA:同样易受Shor算法攻击,3072位RSA在量子下安全性降至约100位
后量子密码学选项:
- 基于格的签名(如Dilithium)
- 基于哈希的签名(如SPHINCS+)
- 基于多变量的签名
第四部分:完整可运行代码示例
4.1 Makefile:构建自动化
# Makefile for ECDSA and RSASSA-PSS examples
CC = gcc
CFLAGS = -Wall -O2 -std=c11
LDFLAGS = -lssl -lcrypto -lm
# Detect OpenSSL version
OPENSSL_CFLAGS := $(shell pkg-config --cflags openssl 2>/dev/null || echo "")
OPENSSL_LIBS := $(shell pkg-config --libs openssl 2>/dev/null || echo "-lssl -lcrypto")
# Default target
all: ecdsa_example rsassa_pss_example comparison
# ECDSA example
ecdsa_example: ecdsa_example.c
$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) -o $@ $< $(OPENSSL_LIBS)
# RSASSA-PSS example
rsassa_pss_example: rsassa_pss_example.c
$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) -o $@ $< $(OPENSSL_LIBS)
# Comparison tool
comparison: comparison.c
$(CC) $(CFLAGS) $(OPENSSL_CFLAGS) -o $@ $< $(OPENSSL_LIBS)
# Clean up
clean:
rm -f ecdsa_example rsassa_pss_example comparison *.pem *.sig *.key
# Test targets
test-ecdsa: ecdsa_example
./ecdsa_example
test-rsa: rsassa_pss_example
./rsassa_pss_example
test-all: test-ecdsa test-rsa
./comparison
.PHONY: all clean test-ecdsa test-rsa test-all
4.2 ECDSA完整示例代码
/**
* @file ecdsa_example.c
* @brief 完整的ECDSA签名和验证示例
*
* 这个程序演示了如何使用OpenSSL库生成ECDSA密钥对,
* 对消息进行签名,然后验证签名。
*
* 编译命令: make ecdsa_example
* 运行命令: ./ecdsa_example
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/ec.h>
#include <openssl/ecdsa.h>
#include <openssl/evp.h>
#include <openssl/sha.h>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/bn.h>
/**
* @brief 生成ECDSA密钥对
* @param[in] curve_name 椭圆曲线名称(如"NID_secp256k1")
* @param[out] private_key 生成的私钥(输出参数)
* @param[out] public_key 生成的公钥(输出参数)
* @return 成功返回1,失败返回0
*/
int generate_ecdsa_keypair(int curve_name,
EC_KEY **private_key,
EC_KEY **public_key) {
// 创建密钥结构
EC_KEY *key = EC_KEY_new_by_curve_name(curve_name);
if (!key) {
fprintf(stderr, "Error creating EC key structure\n");
return 0;
}
// 生成密钥对
if (!EC_KEY_generate_key(key)) {
fprintf(stderr, "Error generating EC key pair\n");
EC_KEY_free(key);
return 0;
}
// 确保私钥设置了适当的参数
if (!EC_KEY_check_key(key)) {
fprintf(stderr, "Error: invalid EC key generated\n");
EC_KEY_free(key);
return 0;
}
*private_key = key;
// 复制公钥
*public_key = EC_KEY_new_by_curve_name(curve_name);
if (!*public_key) {
EC_KEY_free(key);
return 0;
}
const EC_POINT *pub_point = EC_KEY_get0_public_key(key);
if (!EC_KEY_set_public_key(*public_key, pub_point)) {
EC_KEY_free(key);
EC_KEY_free(*public_key);
return 0;
}
return 1;
}
/**
* @brief 使用ECDSA签名消息
* @param[in] private_key 私钥
* @param[in] message 待签名的消息
* @param[in] message_len 消息长度
* @param[out] signature 生成的签名(输出参数)
* @param[out] sig_len 签名长度(输出参数)
* @return 成功返回1,失败返回0
*/
int ecdsa_sign_message(EC_KEY *private_key,
const unsigned char *message,
size_t message_len,
unsigned char **signature,
size_t *sig_len) {
// 计算消息的SHA-256哈希
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, hash);
// 计算签名长度
*sig_len = ECDSA_size(private_key);
*signature = malloc(*sig_len);
if (!*signature) {
fprintf(stderr, "Memory allocation failed\n");
return 0;
}
// 生成ECDSA签名
unsigned int tmp_sig_len;
if (!ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH,
*signature, &tmp_sig_len, private_key)) {
fprintf(stderr, "Error creating ECDSA signature\n");
free(*signature);
return 0;
}
*sig_len = tmp_sig_len;
return 1;
}
/**
* @brief 验证ECDSA签名
* @param[in] public_key 公钥
* @param[in] message 原始消息
* @param[in] message_len 消息长度
* @param[in] signature 待验证的签名
* @param[in] sig_len 签名长度
* @return 签名有效返回1,无效返回0,错误返回-1
*/
int ecdsa_verify_signature(EC_KEY *public_key,
const unsigned char *message,
size_t message_len,
const unsigned char *signature,
size_t sig_len) {
// 计算消息的SHA-256哈希
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(message, message_len, hash);
// 验证ECDSA签名
int result = ECDSA_verify(0, hash, SHA256_DIGEST_LENGTH,
signature, sig_len, public_key);
return result;
}
/**
* @brief 将签名转换为DER格式
* @param[in] r BIGNUM格式的r值
* @param[in] s BIGNUM格式的s值
* @param[out] der 输出的DER编码
* @param[out] der_len DER编码长度
* @return 成功返回1,失败返回0
*/
int ecdsa_signature_to_der(const BIGNUM *r, const BIGNUM *s,
unsigned char **der, size_t *der_len) {
ECDSA_SIG *sig = ECDSA_SIG_new();
if (!sig) return 0;
// 注意:ECDSA_SIG_set0会转移所有权,所以需要复制
BIGNUM *r_copy = BN_dup(r);
BIGNUM *s_copy = BN_dup(s);
if (!r_copy || !s_copy || !ECDSA_SIG_set0(sig, r_copy, s_copy)) {
BN_free(r_copy);
BN_free(s_copy);
ECDSA_SIG_free(sig);
return 0;
}
// 计算DER编码长度
*der_len = i2d_ECDSA_SIG(sig, NULL);
*der = malloc(*der_len);
if (!*der) {
ECDSA_SIG_free(sig);
return 0;
}
// 编码为DER
unsigned char *p = *der;
*der_len = i2d_ECDSA_SIG(sig, &p);
ECDSA_SIG_free(sig);
return 1;
}
/**
* @brief 打印十六进制数据
* @param[in] label 标签
* @param[in] data 数据
* @param[in] len 数据长度
*/
void print_hex(const char *label, const unsigned char *data, size_t len) {
printf("%s (%zu bytes):\n", label, len);
for (size_t i = 0; i < len; i++) {
printf("%02x", data[i]);
if ((i + 1) % 32 == 0) printf("\n");
else if ((i + 1) % 4 == 0) printf(" ");
}
printf("\n\n");
}
/**
* @brief 主函数:演示ECDSA签名和验证
*/
int main() {
printf("=== ECDSA签名和验证示例 ===\n\n");
// 初始化OpenSSL
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
EC_KEY *private_key = NULL;
EC_KEY *public_key = NULL;
unsigned char *signature = NULL;
size_t sig_len = 0;
// 1. 生成密钥对
printf("1. 生成ECDSA密钥对 (使用secp256k1曲线)...\n");
if (!generate_ecdsa_keypair(NID_secp256k1, &private_key, &public_key)) {
fprintf(stderr, "密钥对生成失败\n");
goto cleanup;
}
printf(" 密钥对生成成功!\n\n");
// 2. 准备消息
const char *message = "这是一条需要签名的测试消息";
printf("2. 准备消息:\n");
printf(" \"%s\"\n\n", message);
// 3. 生成签名
printf("3. 生成ECDSA签名...\n");
if (!ecdsa_sign_message(private_key,
(const unsigned char *)message,
strlen(message),
&signature, &sig_len)) {
fprintf(stderr, "签名生成失败\n");
goto cleanup;
}
printf(" 签名生成成功!\n");
print_hex(" 签名", signature, sig_len);
// 4. 验证签名
printf("4. 验证签名...\n");
int verify_result = ecdsa_verify_signature(public_key,
(const unsigned char *)message,
strlen(message),
signature, sig_len);
if (verify_result == 1) {
printf(" ✓ 签名验证成功!\n\n");
} else if (verify_result == 0) {
printf(" ✗ 签名验证失败!\n\n");
} else {
printf(" ! 验证过程中发生错误\n\n");
}
// 5. 演示篡改检测
printf("5. 演示篡改检测...\n");
unsigned char *tampered_signature = malloc(sig_len);
memcpy(tampered_signature, signature, sig_len);
// 篡改签名的一个字节
tampered_signature[10] ^= 0xFF;
int tamper_result = ecdsa_verify_signature(public_key,
(const unsigned char *)message,
strlen(message),
tampered_signature, sig_len);
if (tamper_result == 1) {
printf(" ✗ 篡改后的签名竟然验证通过了!\n");
} else {
printf(" ✓ 成功检测到签名被篡改!\n");
}
free(tampered_signature);
cleanup:
// 清理资源
if (private_key) EC_KEY_free(private_key);
if (public_key) EC_KEY_free(public_key);
if (signature) free(signature);
// 清理OpenSSL
EVP_cleanup();
ERR_free_strings();
return 0;
}
4.3 RSASSA-PSS完整示例代码
/**
* @file rsassa_pss_example.c
* @brief 完整的RSASSA-PSS签名和验证示例
*
* 这个程序演示了如何使用OpenSSL库生成RSA密钥对,
* 使用PSS填充方案对消息进行签名和验证。
*
* 编译命令: make rsassa_pss_example
* 运行命令: ./rsassa_pss_example
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#include <openssl/rand.h>
#define RSA_KEY_BITS 2048
/**
* @brief 生成RSA密钥对
* @param[in] bits 密钥位数
* @param[out] private_key 生成的私钥(输出参数)
* @param[out] public_key 生成的公钥(输出参数)
* @return 成功返回1,失败返回0
*/
int generate_rsa_keypair(int bits, EVP_PKEY **private_key, EVP_PKEY **public_key) {
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
if (!ctx) {
fprintf(stderr, "Error creating EVP_PKEY_CTX\n");
return 0;
}
// 初始化密钥生成上下文
if (EVP_PKEY_keygen_init(ctx) <= 0) {
fprintf(stderr, "Error initializing keygen\n");
EVP_PKEY_CTX_free(ctx);
return 0;
}
// 设置RSA密钥位数
if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, bits) <= 0) {
fprintf(stderr, "Error setting RSA key bits\n");
EVP_PKEY_CTX_free(ctx);
return 0;
}
// 生成密钥对
if (EVP_PKEY_keygen(ctx, private_key) <= 0) {
fprintf(stderr, "Error generating RSA key pair\n");
EVP_PKEY_CTX_free(ctx);
return 0;
}
EVP_PKEY_CTX_free(ctx);
// 从私钥提取公钥
BIO *bio = BIO_new(BIO_s_mem());
if (!bio) {
return 0;
}
if (PEM_write_bio_PUBKEY(bio, *private_key) <= 0) {
BIO_free(bio);
return 0;
}
*public_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL);
BIO_free(bio);
return (*public_key != NULL);
}
/**
* @brief 使用RSASSA-PSS签名消息
* @param[in] private_key 私钥
* @param[in] message 待签名的消息
* @param[in] message_len 消息长度
* @param[out] signature 生成的签名(输出参数)
* @param[out] sig_len 签名长度(输出参数)
* @return 成功返回1,失败返回0
*/
int rsassa_pss_sign(EVP_PKEY *private_key,
const unsigned char *message,
size_t message_len,
unsigned char **signature,
size_t *sig_len) {
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) {
fprintf(stderr, "Error creating EVP_MD_CTX\n");
return 0;
}
// 初始化签名上下文
EVP_PKEY_CTX *pkey_ctx = NULL;
if (EVP_DigestSignInit(ctx, &pkey_ctx, EVP_sha256(), NULL, private_key) <= 0) {
fprintf(stderr, "Error initializing digest sign\n");
EVP_MD_CTX_free(ctx);
return 0;
}
// 设置PSS填充参数
if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) <= 0) {
fprintf(stderr, "Error setting RSA PSS padding\n");
EVP_MD_CTX_free(ctx);
return 0;
}
// 设置盐长度(自动确定)
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO) <= 0) {
fprintf(stderr, "Error setting salt length\n");
EVP_MD_CTX_free(ctx);
return 0;
}
// 计算签名长度
if (EVP_DigestSign(ctx, NULL, sig_len, message, message_len) <= 0) {
fprintf(stderr, "Error getting signature length\n");
EVP_MD_CTX_free(ctx);
return 0;
}
// 分配签名缓冲区
*signature = malloc(*sig_len);
if (!*signature) {
fprintf(stderr, "Memory allocation failed\n");
EVP_MD_CTX_free(ctx);
return 0;
}
// 生成签名
size_t tmp_sig_len = *sig_len;
if (EVP_DigestSign(ctx, *signature, &tmp_sig_len, message, message_len) <= 0) {
fprintf(stderr, "Error creating signature\n");
free(*signature);
EVP_MD_CTX_free(ctx);
return 0;
}
*sig_len = tmp_sig_len;
EVP_MD_CTX_free(ctx);
return 1;
}
/**
* @brief 验证RSASSA-PSS签名
* @param[in] public_key 公钥
* @param[in] message 原始消息
* @param[in] message_len 消息长度
* @param[in] signature 待验证的签名
* @param[in] sig_len 签名长度
* @return 签名有效返回1,无效返回0,错误返回-1
*/
int rsassa_pss_verify(EVP_PKEY *public_key,
const unsigned char *message,
size_t message_len,
const unsigned char *signature,
size_t sig_len) {
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
if (!ctx) {
fprintf(stderr, "Error creating EVP_MD_CTX\n");
return -1;
}
// 初始化验证上下文
EVP_PKEY_CTX *pkey_ctx = NULL;
if (EVP_DigestVerifyInit(ctx, &pkey_ctx, EVP_sha256(), NULL, public_key) <= 0) {
fprintf(stderr, "Error initializing digest verify\n");
EVP_MD_CTX_free(ctx);
return -1;
}
// 设置PSS填充参数
if (EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING) <= 0) {
fprintf(stderr, "Error setting RSA PSS padding\n");
EVP_MD_CTX_free(ctx);
return -1;
}
// 设置盐长度(自动确定)
if (EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO) <= 0) {
fprintf(stderr, "Error setting salt length\n");
EVP_MD_CTX_free(ctx);
return -1;
}
// 验证签名
int result = EVP_DigestVerify(ctx, signature, sig_len, message, message_len);
EVP_MD_CTX_free(ctx);
return result;
}
/**
* @brief 将密钥保存到文件
*/
int save_key_to_file(const char *filename, EVP_PKEY *key, int is_private) {
FILE *fp = fopen(filename, "w");
if (!fp) {
fprintf(stderr, "Error opening file %s\n", filename);
return 0;
}
int result;
if (is_private) {
result = PEM_write_PrivateKey(fp, key, NULL, NULL, 0, NULL, NULL);
} else {
result = PEM_write_PUBKEY(fp, key);
}
fclose(fp);
return (result != 0);
}
/**
* @brief 从文件加载密钥
*/
EVP_PKEY *load_key_from_file(const char *filename, int is_private) {
FILE *fp = fopen(filename, "r");
if (!fp) {
fprintf(stderr, "Error opening file %s\n", filename);
return NULL;
}
EVP_PKEY *key;
if (is_private) {
key = PEM_read_PrivateKey(fp, NULL, NULL, NULL);
} else {
key = PEM_read_PUBKEY(fp, NULL, NULL, NULL);
}
fclose(fp);
return key;
}
/**
* @brief 主函数:演示RSASSA-PSS签名和验证
*/
int main() {
printf("=== RSASSA-PSS签名和验证示例 ===\n\n");
// 初始化OpenSSL
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
EVP_PKEY *private_key = NULL;
EVP_PKEY *public_key = NULL;
unsigned char *signature = NULL;
size_t sig_len = 0;
// 1. 生成RSA密钥对
printf("1. 生成RSA密钥对 (%d位)...\n", RSA_KEY_BITS);
if (!generate_rsa_keypair(RSA_KEY_BITS, &private_key, &public_key)) {
fprintf(stderr, "密钥对生成失败\n");
goto cleanup;
}
printf(" 密钥对生成成功!\n\n");
// 保存密钥到文件(可选)
save_key_to_file("private_key.pem", private_key, 1);
save_key_to_file("public_key.pem", public_key, 0);
printf(" 密钥已保存到文件: private_key.pem, public_key.pem\n\n");
// 2. 准备消息
const char *message = "这是一条使用RSASSA-PSS签名的测试消息";
printf("2. 准备消息:\n");
printf(" \"%s\"\n\n", message);
// 3. 生成签名
printf("3. 生成RSASSA-PSS签名...\n");
if (!rsassa_pss_sign(private_key,
(const unsigned char *)message,
strlen(message),
&signature, &sig_len)) {
fprintf(stderr, "签名生成失败\n");
goto cleanup;
}
printf(" 签名生成成功!\n");
printf(" 签名长度: %zu bytes\n\n", sig_len);
// 保存签名到文件
FILE *sig_file = fopen("message.sig", "wb");
if (sig_file) {
fwrite(signature, 1, sig_len, sig_file);
fclose(sig_file);
printf(" 签名已保存到文件: message.sig\n\n");
}
// 4. 验证签名
printf("4. 验证签名...\n");
int verify_result = rsassa_pss_verify(public_key,
(const unsigned char *)message,
strlen(message),
signature, sig_len);
if (verify_result == 1) {
printf(" ✓ 签名验证成功!\n\n");
} else if (verify_result == 0) {
printf(" ✗ 签名验证失败!\n\n");
} else {
printf(" ! 验证过程中发生错误\n\n");
}
// 5. 演示概率性签名
printf("5. 演示PSS的概率性特性...\n");
printf(" 对同一消息进行两次签名:\n");
unsigned char *signature2 = NULL;
size_t sig_len2 = 0;
if (rsassa_pss_sign(private_key,
(const unsigned char *)message,
strlen(message),
&signature2, &sig_len2)) {
printf(" 第一次签名: ");
for (size_t i = 0; i < 8 && i < sig_len; i++) {
printf("%02x", signature[i]);
}
printf("...\n");
printf(" 第二次签名: ");
for (size_t i = 0; i < 8 && i < sig_len2; i++) {
printf("%02x", signature2[i]);
}
printf("...\n");
// 检查两个签名是否不同
int signatures_different = 0;
if (sig_len == sig_len2) {
signatures_different = memcmp(signature, signature2, sig_len) != 0;
} else {
signatures_different = 1;
}
if (signatures_different) {
printf(" ✓ 两个签名不同(概率性签名)\n");
} else {
printf(" ✗ 两个签名相同(意外情况)\n");
}
free(signature2);
}
printf("\n");
// 6. 演示篡改检测
printf("6. 演示篡改检测...\n");
unsigned char *tampered_message = malloc(strlen(message) + 1);
strcpy((char *)tampered_message, message);
tampered_message[5] ^= 0xFF; // 篡改消息
int tamper_result = rsassa_pss_verify(public_key,
tampered_message,
strlen(message),
signature, sig_len);
if (tamper_result == 1) {
printf(" ✗ 篡改后的消息竟然验证通过了!\n");
} else {
printf(" ✓ 成功检测到消息被篡改!\n");
}
free(tampered_message);
cleanup:
// 清理资源
if (private_key) EVP_PKEY_free(private_key);
if (public_key) EVP_PKEY_free(public_key);
if (signature) free(signature);
// 清理OpenSSL
EVP_cleanup();
ERR_free_strings();
return 0;
}
4.4 性能对比程序
/**
* @file comparison.c
* @brief ECDSA与RSASSA-PSS性能对比
*
* 编译命令: make comparison
* 运行命令: ./comparison
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <openssl/ec.h>
#include <openssl/rsa.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/sha.h>
#define TEST_ITERATIONS 1000
#define MESSAGE_SIZE 1024
// 性能测试结构
typedef struct {
const char *name;
double keygen_time;
double sign_time;
double verify_time;
size_t signature_size;
size_t public_key_size;
size_t private_key_size;
} benchmark_result_t;
/**
* @brief 执行ECDSA性能测试
*/
benchmark_result_t benchmark_ecdsa() {
benchmark_result_t result = {"ECDSA (P-256)", 0, 0, 0, 0, 0, 0};
clock_t start, end;
EC_KEY *private_key = NULL;
EC_KEY *public_key = NULL;
// 生成测试消息
unsigned char message[MESSAGE_SIZE];
RAND_bytes(message, MESSAGE_SIZE);
// 1. 密钥生成性能
printf("测试ECDSA密钥生成...\n");
start = clock();
for (int i = 0; i < TEST_ITERATIONS; i++) {
EC_KEY *key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(key);
EC_KEY_free(key);
}
end = clock();
result.keygen_time = ((double)(end - start)) / CLOCKS_PER_SEC / TEST_ITERATIONS * 1000;
// 生成实际使用的密钥
private_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_generate_key(private_key);
// 计算密钥大小
result.private_key_size = i2d_ECPrivateKey(private_key, NULL);
// 获取公钥大小
const EC_POINT *pub_point = EC_KEY_get0_public_key(private_key);
public_key = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
EC_KEY_set_public_key(public_key, pub_point);
result.public_key_size = i2d_EC_PUBKEY(public_key, NULL);
// 2. 签名性能
printf("测试ECDSA签名...\n");
unsigned char hash[SHA256_DIGEST_LENGTH];
SHA256(message, MESSAGE_SIZE, hash);
start = clock();
for (int i = 0; i < TEST_ITERATIONS; i++) {
unsigned char *sig = NULL;
unsigned int sig_len = 0;
ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH, sig, &sig_len, private_key);
free(sig);
}
end = clock();
result.sign_time = ((double)(end - start)) / CLOCKS_PER_SEC / TEST_ITERATIONS * 1000;
// 获取签名大小
unsigned char *sig = NULL;
unsigned int sig_len = 0;
ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH, NULL, &sig_len, private_key);
result.signature_size = sig_len;
free(sig);
// 3. 验证性能
printf("测试ECDSA验证...\n");
// 先生成一个签名用于验证
sig = malloc(result.signature_size);
ECDSA_sign(0, hash, SHA256_DIGEST_LENGTH, sig, &sig_len, private_key);
start = clock();
for (int i = 0; i < TEST_ITERATIONS; i++) {
ECDSA_verify(0, hash, SHA256_DIGEST_LENGTH, sig, sig_len, public_key);
}
end = clock();
result.verify_time = ((double)(end - start)) / CLOCKS_PER_SEC / TEST_ITERATIONS * 1000;
// 清理
free(sig);
EC_KEY_free(private_key);
EC_KEY_free(public_key);
return result;
}
/**
* @brief 执行RSA-PSS性能测试
*/
benchmark_result_t benchmark_rsa_pss(int rsa_bits) {
char name[64];
snprintf(name, sizeof(name), "RSA-PSS (%d位)", rsa_bits);
benchmark_result_t result = {name, 0, 0, 0, 0, 0, 0};
clock_t start, end;
EVP_PKEY *private_key = NULL;
EVP_PKEY *public_key = NULL;
EVP_PKEY_CTX *ctx = NULL;
// 生成测试消息
unsigned char message[MESSAGE_SIZE];
RAND_bytes(message, MESSAGE_SIZE);
// 1. 密钥生成性能
printf("测试RSA-%d密钥生成...\n", rsa_bits);
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
start = clock();
for (int i = 0; i < TEST_ITERATIONS / 10; i++) { // RSA密钥生成较慢,减少测试次数
EVP_PKEY_CTX *tmp_ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);
EVP_PKEY_keygen_init(tmp_ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(tmp_ctx, rsa_bits);
EVP_PKEY *tmp_key = NULL;
EVP_PKEY_keygen(tmp_ctx, &tmp_key);
EVP_PKEY_free(tmp_key);
EVP_PKEY_CTX_free(tmp_ctx);
}
end = clock();
result.keygen_time = ((double)(end - start)) / CLOCKS_PER_SEC / (TEST_ITERATIONS / 10) * 1000;
// 生成实际使用的密钥
EVP_PKEY_keygen_init(ctx);
EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, rsa_bits);
EVP_PKEY_keygen(ctx, &private_key);
// 计算密钥大小
result.private_key_size = i2d_PrivateKey(private_key, NULL);
result.public_key_size = i2d_PublicKey(private_key, NULL);
// 2. 签名性能
printf("测试RSA-PSS签名...\n");
EVP_MD_CTX *md_ctx = EVP_MD_CTX_new();
EVP_PKEY_CTX *pkey_ctx = NULL;
start = clock();
for (int i = 0; i < TEST_ITERATIONS; i++) {
EVP_MD_CTX *tmp_ctx = EVP_MD_CTX_new();
EVP_DigestSignInit(tmp_ctx, &pkey_ctx, EVP_sha256(), NULL, private_key);
EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO);
size_t sig_len = 0;
EVP_DigestSign(tmp_ctx, NULL, &sig_len, message, MESSAGE_SIZE);
EVP_MD_CTX_free(tmp_ctx);
}
end = clock();
result.sign_time = ((double)(end - start)) / CLOCKS_PER_SEC / TEST_ITERATIONS * 1000;
// 获取签名大小
EVP_DigestSignInit(md_ctx, &pkey_ctx, EVP_sha256(), NULL, private_key);
EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO);
size_t sig_len = 0;
EVP_DigestSign(md_ctx, NULL, &sig_len, message, MESSAGE_SIZE);
result.signature_size = sig_len;
// 3. 验证性能
printf("测试RSA-PSS验证...\n");
// 先生成一个签名用于验证
unsigned char *signature = malloc(sig_len);
EVP_DigestSign(md_ctx, signature, &sig_len, message, MESSAGE_SIZE);
start = clock();
for (int i = 0; i < TEST_ITERATIONS; i++) {
EVP_MD_CTX *tmp_ctx = EVP_MD_CTX_new();
EVP_DigestVerifyInit(tmp_ctx, &pkey_ctx, EVP_sha256(), NULL, private_key);
EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PSS_PADDING);
EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_ctx, RSA_PSS_SALTLEN_AUTO);
EVP_DigestVerify(tmp_ctx, signature, sig_len, message, MESSAGE_SIZE);
EVP_MD_CTX_free(tmp_ctx);
}
end = clock();
result.verify_time = ((double)(end - start)) / CLOCKS_PER_SEC / TEST_ITERATIONS * 1000;
// 清理
free(signature);
EVP_MD_CTX_free(md_ctx);
EVP_PKEY_free(private_key);
EVP_PKEY_CTX_free(ctx);
return result;
}
/**
* @brief 打印性能结果
*/
void print_results(const benchmark_result_t *results, int count) {
printf("\n" "="*70 "\n");
printf("性能对比结果 (基于 %d 次迭代的平均值)\n", TEST_ITERATIONS);
printf("="*70 "\n\n");
printf("%-20s %12s %12s %12s %12s %12s %12s\n",
"算法", "密钥生成(ms)", "签名(ms)", "验证(ms)",
"签名大小", "公钥大小", "私钥大小");
printf("-"*70 "\n");
for (int i = 0; i < count; i++) {
printf("%-20s %12.3f %12.3f %12.3f %12zu %12zu %12zu\n",
results[i].name,
results[i].keygen_time,
results[i].sign_time,
results[i].verify_time,
results[i].signature_size,
results[i].public_key_size,
results[i].private_key_size);
}
printf("\n说明:\n");
printf("1. 时间单位: 毫秒(ms),越低越好\n");
printf("2. 大小单位: 字节(bytes),越小越好\n");
printf("3. ECDSA使用P-256曲线,提供约128位安全性\n");
printf("4. RSA-2048提供约112位安全性,RSA-3072提供约128位安全性\n");
}
/**
* @brief 打印推荐场景
*/
void print_recommendations() {
printf("\n" "="*70 "\n");
printf("算法选择推荐\n");
printf("="*70 "\n\n");
printf("推荐使用ECDSA的场景:\n");
printf(" ✓ 移动设备和物联网(IoT)设备\n");
printf(" ✓ 带宽受限的应用(如区块链、移动通信)\n");
printf(" ✓ 需要短签名的场景(如证书链优化)\n");
printf(" ✓ 需要高性能签名的服务器应用\n\n");
printf("推荐使用RSASSA-PSS的场景:\n");
printf(" ✓ 需要与传统系统兼容\n");
printf(" ✓ 验证性能是关键需求的场景(如TLS握手)\n");
printf(" ✓ 有硬件加速支持的环境(HSM、智能卡)\n");
printf(" ✓ 需要可证明安全性的应用\n\n");
printf("后量子密码学考虑:\n");
printf(" ⚠ 两种算法都易受量子计算攻击\n");
printf(" 💡 长期安全考虑应关注后量子密码算法:\n");
printf(" - 基于格的签名(Dilithium)\n");
printf(" - 基于哈希的签名(SPHINCS+)\n");
printf(" - 基于多变量的签名\n");
}
/**
* @brief 主函数
*/
int main() {
printf("开始ECDSA与RSASSA-PSS性能对比测试...\n\n");
// 初始化OpenSSL
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();
// 运行性能测试
benchmark_result_t results[3];
printf("测试 1/3: ECDSA (P-256)\n");
results[0] = benchmark_ecdsa();
printf("\n测试 2/3: RSA-PSS (2048位)\n");
results[1] = benchmark_rsa_pss(2048);
printf("\n测试 3/3: RSA-PSS (3072位)\n");
results[2] = benchmark_rsa_pss(3072);
// 打印结果
print_results(results, 3);
print_recommendations();
// 清理OpenSSL
EVP_cleanup();
ERR_free_strings();
return 0;
}
第五部分:总结与未来展望
5.1 技术总结:各有所长的双雄
ECDSA和RSASSA-PSS代表了现代数字签名的两个重要方向:一个追求效率和简洁,一个注重安全性和可证明性。
ECDSA的核心优势:
- 密钥和签名尺寸小,适合带宽受限环境
- 签名生成快,适合服务器端批量操作
- 基于离散对数问题,后量子安全性略好于RSA
RSASSA-PSS的核心优势:
- 验证速度极快,适合客户端验证场景
- 可证明安全性(在随机预言机模型下)
- 与传统基础设施兼容性好
5.2 应用实践建议
-
TLS/SSL场景:
- 服务器证书:优先考虑ECDSA(减少握手数据量)
- 客户端验证:可考虑RSASSA-PSS(验证速度快)
-
区块链场景:
- 比特币:ECDSA secp256k1是标准选择
- 智能合约:考虑签名尺寸对gas费用的影响
-
嵌入式系统:
- 资源极度受限:ECDSA P-256是理想选择
- 有硬件加速:根据硬件支持选择
5.3 安全最佳实践
-
密钥管理:
- 使用硬件安全模块(HSM)存储私钥
- 定期轮换密钥,建立完善的密钥生命周期管理
-
随机数生成:
- 使用密码学安全的随机数生成器
- 对于ECDSA,确保每个签名的k值都是唯一的
-
算法参数选择:
- ECDSA:使用标准化曲线(如P-256、secp256k1)
- RSA:密钥长度至少2048位,推荐3072位以上
5.4 未来发展趋势
-
后量子密码学迁移:
- 逐步部署抗量子算法
- 考虑混合方案(如RSA+后量子签名)
-
标准化进展:
- NIST后量子密码学标准化的推进
- 新算法的互操作性测试
-
硬件加速:
- 专用密码学处理器的发展
- 量子安全算法的硬件实现优化
结语:密码学的永恒追求
数字签名算法的演进,是安全、效率、实用性之间不断平衡的艺术。ECDSA和RSASSA-PSS各自在不同的应用场景中发挥着重要作用,它们的设计哲学体现了密码学家的智慧:既要抵抗已知的攻击,又要预见未来的威胁。
正如现实世界中的签名不断演变一样,数字签名技术也必将继续发展。理解这些算法的原理、优势和局限,能帮助我们在数字世界的安全建设中做出明智的选择。
致读者:密码学是一个需要持续学习的领域。本文虽力求详尽,但技术仍在不断演进。建议您关注NIST、IETF等标准化组织的最新动态,以及密码学社区的研究进展。安全之路,永无止境。
附录:编译与运行说明
环境要求
- 操作系统:Linux/macOS/Windows (WSL)
- 编译器:GCC 或 Clang
- OpenSSL库:版本1.1.1或更高
- Make工具
编译步骤
# 1. 下载所有源代码文件
# 2. 确保Makefile在同一目录
# 3. 执行编译命令
make clean && make
# 或者单独编译
make ecdsa_example
make rsassa_pss_example
make comparison
运行示例
# 运行ECDSA示例
./ecdsa_example
# 运行RSA-PSS示例
./rsassa_pss_example
# 运行性能对比
./comparison
预期输出
每个程序都会显示详细的步骤和结果,包括:
- 密钥生成过程
- 签名生成和验证
- 性能数据(对比程序)
- 错误处理和安全性演示
故障排除
-
OpenSSL库缺失:
# Ubuntu/Debian sudo apt-get install libssl-dev # CentOS/RHEL sudo yum install openssl-devel # macOS brew install openssl -
编译错误:
- 检查OpenSSL版本
- 确保所有头文件正确包含
-
运行错误:
- 检查文件权限
- 确保动态库路径正确
通过这个完整的解析,您应该对ECDSA和RSASSA-PSS有了全面而深入的理解。从数学原理到实际实现,从性能对比到应用选择,这两个算法代表了现代数字签名的精华所在。在日益数字化的世界中,掌握这些技术将为您的安全实践提供坚实的基础。
更多推荐
所有评论(0)