Ed25519 - 现代高性能数字签名算法
·32 分钟阅读·6214 字··作者:xinglei.wang
什么是 Ed25519
Ed25519 是一种基于 Curve25519 椭圆曲线的 Edwards-curve Digital Signature Algorithm (EdDSA) 实现,由著名密码学家 Daniel J. Bernstein 于 2011 年设计。它被广泛认为是目前最优秀的数字签名算法之一,在安全性、性能和易用性方面达到了极佳的平衡。
核心特性
| 特性 | 说明 |
|---|---|
| 密钥长度 | 256 位 (32 字节) |
| 签名长度 | 512 位 (64 字节) |
| 安全等级 | 128 位 (等效于 RSA-3072) |
| 签名速度 | 极快 (微秒级) |
| 确定性签名 | 无需随机数生成器 |
为什么选择 Ed25519
传统签名算法的问题:
RSA:
- 密钥太长 (2048-4096 位)
- 计算速度慢
- 签名体积大
ECDSA (P-256):
- 需要高质量随机数 (随机数重复会泄露私钥)
- 实现容易出错
- 存在专利问题 (历史原因)
Ed25519 的优势:
- 密钥短 (32 字节)
- 签名快 (比 RSA 快 100 倍)
- 确定性签名 (不需要随机数)
- 抗侧信道攻击
- 没有专利限制
- 实现简单,难以出错
应用场景
mindmap
root((Ed25519 应用))
区块链
交易签名
地址生成
共识机制
安全协议
SSH 认证
TLS 1.3
WireGuard VPN
身份认证
FIDO2/WebAuthn
数字身份
护照签名
软件发布
代码签名
包管理器
系统更新
数学基础
1. 椭圆曲线基础
椭圆曲线是满足特定方程的点的集合,在密码学中使用有限域上的椭圆曲线。
"""
Weierstrass 形式 (传统):
y² = x³ + ax + b
Edwards 形式 (Ed25519 使用):
x² + y² = 1 + d·x²·y²
其中 d = -121665/121666 (mod p)
p = 2²⁵⁵ - 19 (这就是 "25519" 的来源)
"""
# Curve25519 参数
p = 2**255 - 19 # 素数域
d = -121665 * pow(121666, -1, p) % p # 曲线参数
print(f"p = {p}")
print(f"p 位数: {p.bit_length()}")
2. 椭圆曲线点运算
"""
椭圆曲线上的运算:
1. 点加法: P + Q = R
给定曲线上两点 P 和 Q,定义它们的"加法"得到第三个点 R
2. 标量乘法: k·P
将点 P 与自身相加 k 次
这是单向函数:知道 k 和 P 容易计算 k·P
但知道 P 和 k·P 很难计算 k (离散对数问题)
Edwards 曲线点加法公式 (统一公式,无需特殊情况):
x₃ = (x₁·y₂ + y₁·x₂) / (1 + d·x₁·x₂·y₁·y₂)
y₃ = (y₁·y₂ - x₁·x₂) / (1 - d·x₁·x₂·y₁·y₂)
"""
def edwards_add(P, Q, d, p):
"""Edwards 曲线点加法"""
x1, y1 = P
x2, y2 = Q
# 计算分子
x3_num = (x1 * y2 + y1 * x2) % p
y3_num = (y1 * y2 - x1 * x2) % p
# 计算分母
dxy = (d * x1 * x2 * y1 * y2) % p
x3_den = (1 + dxy) % p
y3_den = (1 - dxy) % p
# 计算逆元并得到结果
x3 = (x3_num * pow(x3_den, -1, p)) % p
y3 = (y3_num * pow(y3_den, -1, p)) % p
return (x3, y3)
def scalar_multiply(k, P, d, p):
"""标量乘法 (双倍加算法)"""
result = (0, 1) # 单位元 (恒等点)
addend = P
while k:
if k & 1:
result = edwards_add(result, addend, d, p)
addend = edwards_add(addend, addend, d, p)
k >>= 1
return result
3. Ed25519 曲线参数
"""
Ed25519 参数定义:
曲线方程: -x² + y² = 1 + d·x²·y²
(注意: Ed25519 使用扭曲 Edwards 曲线,x² 系数为 -1)
参数:
- p = 2²⁵⁵ - 19 (素数域)
- d = -121665/121666 (mod p)
- 基点 G (生成元)
- L = 2²⁵² + 27742317777372353535851937790883648493 (群的阶)
- cofactor = 8
"""
# Ed25519 完整参数
class Ed25519Params:
# 素数域
p = 2**255 - 19
# 曲线参数 (扭曲 Edwards 曲线)
a = -1
d = -121665 * pow(121666, -1, p) % p
# 群的阶 (基点的阶)
L = 2**252 + 27742317777372353535851937790883648493
# 余因子
cofactor = 8
# 基点 G (x 坐标)
Gx = 15112221349535807912866137220509078935008241517709077347421941336440882714147
# 基点 G (y 坐标)
Gy = 46316835694926478169428394003475163141307993866256225615783033603165251855960
params = Ed25519Params()
print(f"群阶 L: {params.L}")
print(f"L 位数: {params.L.bit_length()}")
4. 密钥生成
import hashlib
import os
def clamp_private_key(key_bytes: bytes) -> int:
"""
钳制私钥 (clamping)
Ed25519 对私钥进行特殊处理:
1. 清除最低 3 位 (确保是 8 的倍数,防止小子群攻击)
2. 清除最高位 (确保 < 2²⁵⁴)
3. 设置第 254 位 (确保固定长度)
"""
h = hashlib.sha512(key_bytes).digest()
a = bytearray(h[:32])
# 清除最低 3 位
a[0] &= 0xF8
# 清除最高位
a[31] &= 0x7F
# 设置第 254 位
a[31] |= 0x40
return int.from_bytes(a, 'little')
def generate_keypair():
"""
生成 Ed25519 密钥对
流程:
1. 生成 32 字节随机种子
2. SHA-512 哈希种子
3. 前 32 字节用于私钥标量 (经过钳制)
4. 后 32 字节用于签名时的随机化
5. 公钥 = 私钥标量 × 基点 G
"""
# 1. 生成随机种子
seed = os.urandom(32)
# 2. 哈希得到扩展密钥
h = hashlib.sha512(seed).digest()
# 3. 钳制私钥
private_scalar = clamp_private_key(seed)
# 4. 计算公钥 (需要完整的点运算实现)
# public_key = scalar_multiply(private_scalar, G)
return seed, h # 简化返回
# 实际使用库实现
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
def generate_ed25519_keypair():
"""使用 cryptography 库生成密钥对"""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
return private_key, public_key
签名与验证
签名算法 (EdDSA)
"""
Ed25519 签名流程:
输入: 私钥种子 sk, 消息 M
输出: 签名 (R, S)
1. H = SHA-512(sk) → (a, prefix)
a: 私钥标量 (前 32 字节,经过钳制)
prefix: 签名前缀 (后 32 字节)
2. r = SHA-512(prefix || M) mod L
r: 每次签名的临时随机数 (确定性生成!)
3. R = r × G
R: 签名的第一部分
4. k = SHA-512(R || A || M) mod L
A: 公钥
k: 挑战值
5. S = (r + k × a) mod L
S: 签名的第二部分
签名 = (R, S),共 64 字节
"""
import hashlib
def ed25519_sign_conceptual(private_seed: bytes, message: bytes, public_key: bytes) -> bytes:
"""
Ed25519 签名 (概念实现,不完整)
实际使用请使用密码学库
"""
# 1. 扩展私钥
h = hashlib.sha512(private_seed).digest()
a = clamp_private_key(private_seed) # 私钥标量
prefix = h[32:] # 签名前缀
# 2. 计算 r (确定性随机数)
r_hash = hashlib.sha512(prefix + message).digest()
r = int.from_bytes(r_hash, 'little') % Ed25519Params.L
# 3. R = r × G
# R = scalar_multiply(r, G) # 需要完整实现
# 4. k = H(R || A || M)
# k_hash = hashlib.sha512(encode_point(R) + public_key + message).digest()
# k = int.from_bytes(k_hash, 'little') % Ed25519Params.L
# 5. S = (r + k × a) mod L
# S = (r + k * a) % Ed25519Params.L
# return encode_point(R) + encode_scalar(S)
pass
验证算法
"""
Ed25519 验证流程:
输入: 公钥 A, 消息 M, 签名 (R, S)
输出: 有效/无效
1. 检查 A 和 R 是否是有效的曲线点
2. 检查 S < L
3. k = SHA-512(R || A || M) mod L
4. 验证: S × G == R + k × A
等价于验证:
(r + k × a) × G == r × G + k × a × G
(r + k × a) × G == r × G + k × A (因为 A = a × G)
这是 Schnorr 签名的变体
"""
def ed25519_verify_conceptual(public_key: bytes, message: bytes, signature: bytes) -> bool:
"""
Ed25519 验证 (概念实现)
"""
# 1. 解析签名
R_bytes = signature[:32]
S_bytes = signature[32:]
# 2. 解码点和标量
# R = decode_point(R_bytes)
# S = int.from_bytes(S_bytes, 'little')
# A = decode_point(public_key)
# 3. 检查 S < L
# if S >= Ed25519Params.L:
# return False
# 4. 计算挑战
# k_hash = hashlib.sha512(R_bytes + public_key + message).digest()
# k = int.from_bytes(k_hash, 'little') % Ed25519Params.L
# 5. 验证等式: S × G == R + k × A
# left = scalar_multiply(S, G)
# right = edwards_add(R, scalar_multiply(k, A))
# return left == right
pass
使用密码学库
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives import serialization
def ed25519_example():
"""Ed25519 完整示例"""
# 生成密钥对
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# 获取原始密钥字节
private_bytes = private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
public_bytes = public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
print(f"私钥 (32 字节): {private_bytes.hex()}")
print(f"公钥 (32 字节): {public_bytes.hex()}")
# 签名
message = b"Hello, Ed25519!"
signature = private_key.sign(message)
print(f"签名 (64 字节): {signature.hex()}")
# 验证
try:
public_key.verify(signature, message)
print("签名验证成功!")
except Exception:
print("签名验证失败!")
# 验证确定性签名 (相同消息产生相同签名)
signature2 = private_key.sign(message)
print(f"确定性签名: {signature == signature2}")
ed25519_example()
安全特性
1. 确定性签名
"""
Ed25519 vs ECDSA 的随机数问题:
ECDSA 的致命弱点:
每次签名需要随机数 k
如果 k 泄露或重复使用,私钥会被恢复!
历史案例:
- 2010 Sony PS3 ECDSA 漏洞:重复使用 k,导致私钥泄露
- 2013 Android SecureRandom 漏洞:弱随机数导致比特币被盗
Ed25519 的解决方案:
r = SHA-512(prefix || M) mod L
prefix 来自私钥扩展,M 是消息
- 完全确定性:相同消息产生相同签名
- 无需额外随机数
- 即使实现者犯错也不会泄露私钥
"""
# 演示确定性签名
def demo_deterministic_signature():
private_key = Ed25519PrivateKey.generate()
message = b"Test message"
signatures = [private_key.sign(message) for _ in range(5)]
# 所有签名都相同
assert all(s == signatures[0] for s in signatures)
print("确定性签名验证通过: 5 次签名完全相同")
demo_deterministic_signature()
2. 抗侧信道攻击
"""
Ed25519 的侧信道防护:
1. 恒定时间实现
- 所有操作的执行时间与数据无关
- 防止时序攻击
2. 无条件分支
- 避免 if-else 依赖秘密数据
- 防止分支预测攻击
3. 完整公式
- Edwards 曲线点加法使用统一公式
- 不需要处理特殊情况 (如 P + P, P + O)
- 减少实现复杂性和错误
4. 小子群攻击防护
- 私钥钳制确保是 8 的倍数
- 验证时检查点的有效性
"""
def constant_time_compare(a: bytes, b: bytes) -> bool:
"""恒定时间比较"""
if len(a) != len(b):
return False
result = 0
for x, y in zip(a, b):
result |= x ^ y
return result == 0
3. 密钥强度
"""
Ed25519 安全等级:
密钥长度: 256 位
安全等级: 128 位 (ECDLP 难度)
对比其他算法:
| 算法 | 密钥长度 | 安全等级 |
|------------|----------|----------|
| Ed25519 | 256 位 | 128 位 |
| RSA-2048 | 2048 位 | 112 位 |
| RSA-3072 | 3072 位 | 128 位 |
| ECDSA P-256| 256 位 | 128 位 |
| AES-128 | 128 位 | 128 位 |
Ed25519 用 256 位密钥达到 RSA-3072 的安全等级!
"""
4. 批量验证
"""
Ed25519 支持批量验证优化:
单独验证 n 个签名: O(n) 次标量乘法
批量验证 n 个签名: 约 O(n/2) 次标量乘法
原理:
验证 n 个签名 (Rᵢ, Sᵢ) 对应消息 Mᵢ 和公钥 Aᵢ
批量验证:
Σ(zᵢ × Sᵢ) × G == Σ(zᵢ × Rᵢ) + Σ(zᵢ × kᵢ × Aᵢ)
其中 zᵢ 是随机权重
如果所有签名都有效,等式成立
如果有任何无效签名,等式以高概率不成立
"""
# 注意: cryptography 库不直接支持批量验证
# 需要使用专门的库如 ed25519-dalek (Rust) 或 PyNaCl
与其他算法对比
Ed25519 vs ECDSA
| 特性 | Ed25519 | ECDSA (P-256) |
|---|---|---|
| 曲线类型 | Edwards (Curve25519) | Weierstrass (P-256) |
| 签名确定性 | 确定性 | 需要随机数 |
| 签名长度 | 64 字节 | 64-72 字节 |
| 公钥长度 | 32 字节 | 33/65 字节 |
| 验证速度 | 更快 | 较慢 |
| 实现复杂度 | 简单 | 复杂 |
| 标准化 | RFC 8032 | FIPS 186-4 |
| 侧信道安全 | 设计优先 | 需要额外措施 |
"""
性能对比 (典型值):
签名速度:
Ed25519: ~60,000 次/秒
ECDSA P-256: ~20,000 次/秒
验证速度:
Ed25519: ~20,000 次/秒
ECDSA P-256: ~8,000 次/秒
Ed25519 比 ECDSA 快约 3 倍
"""
Ed25519 vs RSA
| 特性 | Ed25519 | RSA-3072 |
|---|---|---|
| 安全等级 | 128 位 | 128 位 |
| 公钥长度 | 32 字节 | 384 字节 |
| 私钥长度 | 32 字节 | 1536 字节 |
| 签名长度 | 64 字节 | 384 字节 |
| 签名速度 | 极快 | 较慢 |
| 验证速度 | 快 | 较快 |
| 密钥生成 | 快 | 慢 |
"""
密钥和签名大小对比:
Ed25519:
- 公钥: 32 字节
- 私钥: 32 字节
- 签名: 64 字节
- 总计: 128 字节
RSA-3072:
- 公钥: 384 字节
- 私钥: 1536 字节
- 签名: 384 字节
- 总计: 2304 字节
Ed25519 的密钥和签名比 RSA 小 18 倍!
"""
Ed25519 vs Ed448
| 特性 | Ed25519 | Ed448 |
|---|---|---|
| 曲线 | Curve25519 | Curve448 |
| 安全等级 | 128 位 | 224 位 |
| 公钥长度 | 32 字节 | 57 字节 |
| 签名长度 | 64 字节 | 114 字节 |
| 性能 | 更快 | 较慢 |
| 适用场景 | 大多数应用 | 高安全需求 |
# Ed448 示例
from cryptography.hazmat.primitives.asymmetric.ed448 import Ed448PrivateKey
def ed448_example():
"""Ed448 用于需要更高安全等级的场景"""
private_key = Ed448PrivateKey.generate()
public_key = private_key.public_key()
message = b"High security message"
signature = private_key.sign(message)
print(f"Ed448 公钥长度: {len(public_key.public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw))} 字节")
print(f"Ed448 签名长度: {len(signature)} 字节")
# ed448_example()
实际应用
1. SSH 密钥认证
"""
SSH 中的 Ed25519 应用:
生成 SSH 密钥:
$ ssh-keygen -t ed25519 -C "your_email@example.com"
生成的文件:
- ~/.ssh/id_ed25519 (私钥)
- ~/.ssh/id_ed25519.pub (公钥)
公钥格式:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... email@example.com
优势:
- 比 RSA-4096 的密钥短 (256 位 vs 4096 位)
- 认证速度更快
- 安全性更高
"""
from cryptography.hazmat.primitives.serialization import (
Encoding, PublicFormat, PrivateFormat, NoEncryption
)
def generate_ssh_ed25519_keypair():
"""生成 SSH 兼容的 Ed25519 密钥对"""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# OpenSSH 格式
private_openssh = private_key.private_bytes(
encoding=Encoding.PEM,
format=PrivateFormat.OpenSSH,
encryption_algorithm=NoEncryption()
)
public_openssh = public_key.public_bytes(
encoding=Encoding.OpenSSH,
format=PublicFormat.OpenSSH
)
return private_openssh, public_openssh
private_ssh, public_ssh = generate_ssh_ed25519_keypair()
print("私钥 (OpenSSH 格式):")
print(private_ssh.decode())
print("\n公钥 (authorized_keys 格式):")
print(public_ssh.decode())
2. 区块链交易签名
"""
Ed25519 在区块链中的应用:
使用 Ed25519 的区块链:
- Solana
- Cardano
- Stellar
- Polkadot
- Near Protocol
- Algorand
为什么区块链喜欢 Ed25519:
1. 签名速度快 - 高 TPS 需求
2. 签名体积小 - 节省链上存储
3. 确定性签名 - 交易可预测
4. 安全性高 - 抵抗各种攻击
"""
import hashlib
import json
class SimpleBlockchainTx:
"""简化的区块链交易"""
def __init__(self, sender: bytes, recipient: bytes, amount: int):
self.sender = sender
self.recipient = recipient
self.amount = amount
self.signature = None
def to_bytes(self) -> bytes:
"""序列化交易 (不含签名)"""
tx_data = {
'sender': self.sender.hex(),
'recipient': self.recipient.hex(),
'amount': self.amount
}
return json.dumps(tx_data, sort_keys=True).encode()
def sign(self, private_key: Ed25519PrivateKey):
"""签名交易"""
tx_bytes = self.to_bytes()
self.signature = private_key.sign(tx_bytes)
def verify(self, public_key) -> bool:
"""验证交易签名"""
if self.signature is None:
return False
try:
public_key.verify(self.signature, self.to_bytes())
return True
except Exception:
return False
# 使用示例
def blockchain_tx_example():
# 生成发送者和接收者密钥
sender_private = Ed25519PrivateKey.generate()
sender_public = sender_private.public_key()
recipient_private = Ed25519PrivateKey.generate()
recipient_public = recipient_private.public_key()
# 获取公钥字节 (作为地址)
sender_addr = sender_public.public_bytes(Encoding.Raw, PublicFormat.Raw)
recipient_addr = recipient_public.public_bytes(Encoding.Raw, PublicFormat.Raw)
# 创建并签名交易
tx = SimpleBlockchainTx(sender_addr, recipient_addr, 100)
tx.sign(sender_private)
print(f"交易发送者: {sender_addr.hex()[:16]}...")
print(f"交易接收者: {recipient_addr.hex()[:16]}...")
print(f"交易金额: {tx.amount}")
print(f"签名: {tx.signature.hex()[:32]}...")
print(f"验证结果: {tx.verify(sender_public)}")
blockchain_tx_example()
3. WireGuard VPN
"""
WireGuard 中的 Ed25519/Curve25519:
WireGuard 使用 Curve25519 进行密钥交换
使用相关的 Ed25519 进行身份验证
密钥配置:
[Interface]
PrivateKey = <Base64 编码的 32 字节私钥>
[Peer]
PublicKey = <Base64 编码的 32 字节公钥>
WireGuard 的加密套件 (Noise 协议):
- 密钥交换: Curve25519 (X25519)
- 认证: 基于 Curve25519 密钥对
- 加密: ChaCha20-Poly1305
- 哈希: BLAKE2s
"""
import base64
def generate_wireguard_keypair():
"""生成 WireGuard 兼容的密钥对"""
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey
private_key = X25519PrivateKey.generate()
public_key = private_key.public_key()
private_bytes = private_key.private_bytes(
encoding=Encoding.Raw,
format=PrivateFormat.Raw,
encryption_algorithm=NoEncryption()
)
public_bytes = public_key.public_bytes(
encoding=Encoding.Raw,
format=PublicFormat.Raw
)
private_b64 = base64.b64encode(private_bytes).decode()
public_b64 = base64.b64encode(public_bytes).decode()
print(f"PrivateKey = {private_b64}")
print(f"PublicKey = {public_b64}")
return private_b64, public_b64
generate_wireguard_keypair()
4. FIDO2/WebAuthn
"""
FIDO2/WebAuthn 中的 Ed25519:
WebAuthn 支持的算法:
- ES256 (ECDSA P-256) - 最常用
- ES384, ES512
- RS256, RS384, RS512 (RSA)
- EdDSA (Ed25519) - 越来越流行
Ed25519 在 WebAuthn 中的标识:
- COSE 算法标识: -8 (EdDSA)
- 曲线标识: Ed25519
优势:
- 认证速度更快
- 更好的侧信道保护
- 更短的凭证数据
"""
def webauthn_credential_example():
"""模拟 WebAuthn 凭证创建"""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# COSE Key 格式 (简化)
cose_key = {
1: 1, # kty: OKP (Octet Key Pair)
3: -8, # alg: EdDSA
-1: 6, # crv: Ed25519
-2: public_key.public_bytes(Encoding.Raw, PublicFormat.Raw) # x (公钥)
}
# 模拟认证挑战
challenge = os.urandom(32)
client_data = json.dumps({
'type': 'webauthn.get',
'challenge': base64.urlsafe_b64encode(challenge).decode().rstrip('='),
'origin': 'https://example.com'
}).encode()
# 签名
auth_data = os.urandom(37) # 简化的 authenticator data
signature = private_key.sign(auth_data + hashlib.sha256(client_data).digest())
print(f"Challenge: {challenge.hex()}")
print(f"Signature: {signature.hex()}")
print(f"验证结果: {verify_webauthn_signature(public_key, auth_data, client_data, signature)}")
def verify_webauthn_signature(public_key, auth_data, client_data, signature):
"""验证 WebAuthn 签名"""
try:
signed_data = auth_data + hashlib.sha256(client_data).digest()
public_key.verify(signature, signed_data)
return True
except:
return False
# webauthn_credential_example()
5. 代码签名
"""
代码签名场景:
1. npm 包签名 (sigstore)
2. Docker 镜像签名 (cosign)
3. Git commit 签名
4. 软件发布签名
"""
import hashlib
import json
import time
class CodeSigningCertificate:
"""代码签名证书"""
def __init__(self, subject: str, private_key: Ed25519PrivateKey):
self.subject = subject
self.private_key = private_key
self.public_key = private_key.public_key()
self.created_at = int(time.time())
def sign_artifact(self, artifact_path: str) -> dict:
"""签名软件制品"""
with open(artifact_path, 'rb') as f:
content = f.read()
artifact_hash = hashlib.sha256(content).digest()
signature_payload = {
'artifact': artifact_path,
'sha256': artifact_hash.hex(),
'subject': self.subject,
'timestamp': int(time.time())
}
payload_bytes = json.dumps(signature_payload, sort_keys=True).encode()
signature = self.private_key.sign(payload_bytes)
return {
'payload': signature_payload,
'signature': base64.b64encode(signature).decode(),
'public_key': base64.b64encode(
self.public_key.public_bytes(Encoding.Raw, PublicFormat.Raw)
).decode()
}
@staticmethod
def verify_artifact(artifact_path: str, signature_data: dict) -> bool:
"""验证软件制品签名"""
# 重新计算哈希
with open(artifact_path, 'rb') as f:
content = f.read()
actual_hash = hashlib.sha256(content).hexdigest()
if actual_hash != signature_data['payload']['sha256']:
return False
# 验证签名
public_key_bytes = base64.b64decode(signature_data['public_key'])
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
payload_bytes = json.dumps(signature_data['payload'], sort_keys=True).encode()
signature = base64.b64decode(signature_data['signature'])
try:
public_key.verify(signature, payload_bytes)
return True
except:
return False
多语言实现
Python
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey, Ed25519PublicKey
)
from cryptography.hazmat.primitives import serialization
# 生成密钥对
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# 签名
message = b"Hello, Ed25519!"
signature = private_key.sign(message)
# 验证
try:
public_key.verify(signature, message)
print("Signature is valid!")
except Exception:
print("Signature is invalid!")
# 序列化
private_pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
JavaScript / Node.js
const crypto = require('crypto');
// 生成密钥对
const { publicKey, privateKey } = crypto.generateKeyPairSync('ed25519', {
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// 签名
const message = Buffer.from('Hello, Ed25519!');
const signature = crypto.sign(null, message, privateKey);
console.log('Signature:', signature.toString('hex'));
// 验证
const isValid = crypto.verify(null, message, publicKey, signature);
console.log('Valid:', isValid);
// 使用原始密钥
const { publicKey: pubKey, privateKey: privKey } = crypto.generateKeyPairSync('ed25519');
// 获取原始字节
const rawPublic = pubKey.export({ type: 'spki', format: 'der' }).slice(-32);
const rawPrivate = privKey.export({ type: 'pkcs8', format: 'der' }).slice(-32);
console.log('Public key (32 bytes):', rawPublic.toString('hex'));
Go
package main
import (
"crypto/ed25519"
"crypto/rand"
"encoding/hex"
"fmt"
)
func main() {
// 生成密钥对
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
fmt.Printf("Public key: %s\n", hex.EncodeToString(publicKey))
fmt.Printf("Private key: %s\n", hex.EncodeToString(privateKey[:32])) // 种子部分
// 签名
message := []byte("Hello, Ed25519!")
signature := ed25519.Sign(privateKey, message)
fmt.Printf("Signature: %s\n", hex.EncodeToString(signature))
// 验证
valid := ed25519.Verify(publicKey, message, signature)
fmt.Printf("Valid: %v\n", valid)
// 从种子恢复密钥
seed := privateKey.Seed()
restoredPrivate := ed25519.NewKeyFromSeed(seed)
fmt.Printf("Keys match: %v\n", privateKey.Equal(restoredPrivate))
}
Rust
use ed25519_dalek::{SigningKey, VerifyingKey, Signer, Verifier, Signature};
use rand::rngs::OsRng;
fn main() {
// 生成密钥对
let mut csprng = OsRng;
let signing_key = SigningKey::generate(&mut csprng);
let verifying_key = signing_key.verifying_key();
println!("Public key: {:?}", hex::encode(verifying_key.as_bytes()));
// 签名
let message = b"Hello, Ed25519!";
let signature: Signature = signing_key.sign(message);
println!("Signature: {:?}", hex::encode(signature.to_bytes()));
// 验证
match verifying_key.verify(message, &signature) {
Ok(_) => println!("Signature is valid!"),
Err(_) => println!("Signature is invalid!"),
}
// 批量验证 (ed25519-dalek 支持)
// 这比单独验证多个签名更快
}
Java
import java.security.*;
import java.util.HexFormat;
public class Ed25519Example {
public static void main(String[] args) throws Exception {
// 生成密钥对
KeyPairGenerator generator = KeyPairGenerator.getInstance("Ed25519");
KeyPair keyPair = generator.generateKeyPair();
PublicKey publicKey = keyPair.getPublic();
PrivateKey privateKey = keyPair.getPrivate();
// 签名
byte[] message = "Hello, Ed25519!".getBytes();
Signature signer = Signature.getInstance("Ed25519");
signer.initSign(privateKey);
signer.update(message);
byte[] signature = signer.sign();
System.out.println("Signature: " + HexFormat.of().formatHex(signature));
// 验证
Signature verifier = Signature.getInstance("Ed25519");
verifier.initVerify(publicKey);
verifier.update(message);
boolean valid = verifier.verify(signature);
System.out.println("Valid: " + valid);
}
}
最佳实践
1. 密钥管理
"""
Ed25519 密钥管理最佳实践:
1. 密钥生成
- 使用操作系统提供的 CSPRNG
- 不要自己实现随机数生成
- 验证密钥有效性
2. 密钥存储
- 加密存储私钥
- 使用硬件安全模块 (HSM)
- 实现访问控制
3. 密钥派生
- 可以使用 HKDF 从主密钥派生子密钥
- 保持派生路径的唯一性
"""
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives import hashes
import os
def derive_ed25519_key(master_seed: bytes, context: bytes) -> Ed25519PrivateKey:
"""从主种子派生 Ed25519 密钥"""
# 使用 HKDF 派生 32 字节种子
hkdf = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=b"ed25519-key-derivation",
info=context,
)
derived_seed = hkdf.derive(master_seed)
# 从种子创建私钥
# 注意: 需要使用支持从种子创建的库
# cryptography 库不直接支持,这里仅作概念演示
# 使用 PyNaCl 或 ed25519 库
# from nacl.signing import SigningKey
# return SigningKey(derived_seed)
return derived_seed
def secure_key_storage_example():
"""安全密钥存储示例"""
from cryptography.fernet import Fernet
# 生成加密密钥
encryption_key = Fernet.generate_key()
fernet = Fernet(encryption_key)
# Ed25519 密钥
private_key = Ed25519PrivateKey.generate()
private_bytes = private_key.private_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PrivateFormat.Raw,
encryption_algorithm=serialization.NoEncryption()
)
# 加密存储
encrypted_key = fernet.encrypt(private_bytes)
print(f"加密后的密钥: {encrypted_key[:50]}...")
# 解密使用
decrypted_bytes = fernet.decrypt(encrypted_key)
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
restored_key = Ed25519PrivateKey.from_private_bytes(decrypted_bytes)
return restored_key
2. 错误处理
from cryptography.exceptions import InvalidSignature
class Ed25519Error(Exception):
"""Ed25519 相关错误"""
pass
class SignatureVerificationError(Ed25519Error):
"""签名验证失败"""
pass
def safe_verify(public_key, message: bytes, signature: bytes) -> bool:
"""安全的签名验证"""
try:
# 检查签名长度
if len(signature) != 64:
return False
public_key.verify(signature, message)
return True
except InvalidSignature:
return False
except Exception:
# 记录日志但不泄露细节
return False
def verify_with_exception(public_key, message: bytes, signature: bytes):
"""验证签名,失败时抛出异常"""
if not safe_verify(public_key, message, signature):
raise SignatureVerificationError("Signature verification failed")
3. 测试向量
"""
使用官方测试向量验证实现:
来源: RFC 8032 Section 7.1
"""
def test_ed25519_rfc8032():
"""RFC 8032 测试向量"""
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
# 测试向量 1 (空消息)
secret_key = bytes.fromhex(
"9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"
)
expected_public = bytes.fromhex(
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a"
)
message = b""
expected_signature = bytes.fromhex(
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155"
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"
)
# 从种子创建私钥并验证
# 注意: cryptography 库使用私钥格式,不是种子格式
# 需要使用支持种子的库进行完整测试
print("RFC 8032 测试向量验证需要支持种子导入的库")
def test_deterministic_signature():
"""测试确定性签名"""
private_key = Ed25519PrivateKey.generate()
message = b"Test message for deterministic signature"
# 多次签名应产生相同结果
signatures = [private_key.sign(message) for _ in range(10)]
assert all(s == signatures[0] for s in signatures)
print("确定性签名测试通过")
def test_signature_verification():
"""测试签名验证"""
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key()
# 正常情况
message = b"Valid message"
signature = private_key.sign(message)
assert safe_verify(public_key, message, signature)
# 消息被篡改
tampered_message = b"Tampered message"
assert not safe_verify(public_key, tampered_message, signature)
# 签名被篡改
tampered_signature = bytes([b ^ 0xFF for b in signature])
assert not safe_verify(public_key, message, tampered_signature)
# 错误的公钥
other_private = Ed25519PrivateKey.generate()
other_public = other_private.public_key()
assert not safe_verify(other_public, message, signature)
print("签名验证测试通过")
test_deterministic_signature()
test_signature_verification()
常见问题
1. Ed25519 和 X25519 有什么区别?
"""
Ed25519 vs X25519:
Ed25519:
- 用途: 数字签名
- 曲线: 扭曲 Edwards 曲线 (-x² + y² = 1 + d·x²·y²)
- 操作: 签名 + 验证
X25519:
- 用途: 密钥交换 (Diffie-Hellman)
- 曲线: Montgomery 曲线 (y² = x³ + 486662x² + x)
- 操作: 密钥协商
两者使用相同的底层曲线 (Curve25519),只是表示形式不同。
可以在两种形式之间转换密钥。
"""
from cryptography.hazmat.primitives.asymmetric.x25519 import X25519PrivateKey, X25519PublicKey
def key_exchange_example():
"""X25519 密钥交换示例"""
# Alice 生成密钥对
alice_private = X25519PrivateKey.generate()
alice_public = alice_private.public_key()
# Bob 生成密钥对
bob_private = X25519PrivateKey.generate()
bob_public = bob_private.public_key()
# 密钥交换
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)
# 共享密钥相同
assert alice_shared == bob_shared
print(f"共享密钥: {alice_shared.hex()}")
key_exchange_example()
2. Ed25519 可以用于加密吗?
"""
Ed25519 本身只用于签名,不能直接加密。
但可以:
1. 将 Ed25519 密钥转换为 X25519 密钥
2. 使用 X25519 进行密钥交换
3. 用协商的密钥进行对称加密
这就是 "Sealed Box" 或 "ECIES" 模式:
1. 生成临时 X25519 密钥对
2. 与接收者公钥进行 DH 交换
3. 派生对称密钥
4. 使用对称加密 (如 ChaCha20-Poly1305)
"""
def sealed_box_concept():
"""Sealed Box 概念 (使用 PyNaCl 更简单)"""
# 生成接收者密钥
recipient_private = X25519PrivateKey.generate()
recipient_public = recipient_private.public_key()
# 发送者生成临时密钥
ephemeral_private = X25519PrivateKey.generate()
ephemeral_public = ephemeral_private.public_key()
# 密钥交换
shared_secret = ephemeral_private.exchange(recipient_public)
# 派生加密密钥
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
encryption_key = HKDF(
algorithm=hashes.SHA256(),
length=32,
salt=None,
info=b"sealed-box-encryption"
).derive(shared_secret)
print(f"加密密钥: {encryption_key.hex()}")
# 接下来使用 ChaCha20-Poly1305 或 AES-GCM 加密
# 密文 = 临时公钥 + 加密数据 + 认证标签
# sealed_box_concept()
3. 如何验证 Ed25519 公钥的有效性?
"""
Ed25519 公钥验证:
1. 长度检查: 必须是 32 字节
2. 点验证: 必须是曲线上的有效点
3. 子群检查: 必须在主子群中 (cofactor = 8)
"""
def validate_public_key(public_key_bytes: bytes) -> bool:
"""验证 Ed25519 公钥"""
# 1. 长度检查
if len(public_key_bytes) != 32:
return False
# 2. 尝试解码为公钥对象
try:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
public_key = Ed25519PublicKey.from_public_bytes(public_key_bytes)
return True
except Exception:
return False
# 测试
valid_key = Ed25519PrivateKey.generate().public_key().public_bytes(
Encoding.Raw, PublicFormat.Raw
)
invalid_key = b'\x00' * 32
print(f"有效公钥: {validate_public_key(valid_key)}")
print(f"无效公钥: {validate_public_key(invalid_key)}")
4. Ed25519 的量子安全性如何?
"""
Ed25519 vs 量子计算:
当前状态:
- Ed25519 基于椭圆曲线离散对数问题 (ECDLP)
- Shor 算法可以在量子计算机上高效求解 ECDLP
- 一旦大规模量子计算机可用,Ed25519 将不再安全
时间线预估:
- 2030-2040: 可能出现威胁级量子计算机
后量子替代方案:
- CRYSTALS-Dilithium (NIST 标准)
- SPHINCS+ (无状态哈希签名)
- Falcon (格基签名)
当前建议:
- 短期敏感数据: 继续使用 Ed25519
- 长期敏感数据: 考虑混合方案
- 关注 NIST 后量子密码学标准化进展
"""
总结
Ed25519 是现代密码学中最优秀的数字签名算法之一:
核心优势
| 方面 | 优势 |
|---|---|
| 性能 | 签名/验证极快 |
| 安全 | 128 位安全等级,抗侧信道 |
| 简洁 | 32 字节密钥,64 字节签名 |
| 可靠 | 确定性签名,无随机数问题 |
| 易用 | API 简单,难以误用 |
使用建议
| 场景 | 建议 |
|---|---|
| SSH 密钥 | 优先使用 Ed25519 |
| 区块链签名 | Ed25519 是最佳选择 |
| API 认证 | 可替代 HMAC 或 RSA |
| TLS/HTTPS | 支持 Ed25519 证书 |
| 长期存储 | 考虑后量子混合方案 |
实现检查清单
- 使用成熟的密码学库
- 验证公钥有效性
- 安全存储私钥
- 实现恒定时间比较
- 验证签名长度
- 处理验证失败情况
- 记录安全审计日志
参考资料:
- RFC 8032: Edwards-Curve Digital Signature Algorithm (EdDSA)
- Daniel J. Bernstein: "High-speed high-security signatures"
- NIST SP 800-186: Recommendations for Discrete Logarithm-Based Cryptography
- Curve25519 官方网站: https://cr.yp.to/ecdh.html
系列:加密算法入门
第 3 篇,共 3 篇
相关推荐
RSA 算法 - 非对称加密的基石
深入解析 RSA 算法的数学原理、密钥生成、加密解密过程、数字签名应用,以及安全性分析和最佳实践
·37 分钟·
#RSA#非对称加密
HMAC - 基于哈希的消息认证码
深入解析 HMAC 的工作原理、安全特性、应用场景和多语言实现,理解为什么 HMAC 比简单的哈希更安全
·31 分钟·
#HMAC#加密算法
撮合引擎 - 数字资产交易的核心技术
深入解析撮合引擎的工作原理、核心算法、订单类型和技术架构,理解交易所如何实现高效的买卖订单匹配
·45 分钟·
#撮合引擎#交易所