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

相关推荐

RSA 算法 - 非对称加密的基石

深入解析 RSA 算法的数学原理、密钥生成、加密解密过程、数字签名应用,以及安全性分析和最佳实践

·37 分钟·
#RSA#非对称加密

HMAC - 基于哈希的消息认证码

深入解析 HMAC 的工作原理、安全特性、应用场景和多语言实现,理解为什么 HMAC 比简单的哈希更安全

·31 分钟·
#HMAC#加密算法