从私钥标量算公钥点很快,反过来是椭圆曲线离散对数问题,现实中算不动。
Ed25519 是什么:从一条 ssh-keygen 命令到数字签名原理
配置服务器免密登录、给 GitHub 添加 SSH 公钥、给 Git 提交配置签名时,都会遇到这条命令:
ssh-keygen -t ed25519ed25519 到底代表什么?它和 SSH 的数据加密是什么关系?公钥与私钥是怎样生成和验证的?弄清这些问题,才算真正理解了 SSH 公钥认证的工作方式,而不只是停留在复制命令、上传公钥的层面。
从 ssh-keygen -t ed25519 讲起:椭圆曲线上的单向计算、密钥如何从随机种子派生、签名与验证的数学推导,以及 SSH 登录时私钥为什么从不离开本地。
- ssh 和 ssh-keygen 别搞混
- 25519 这个数字从哪来
- 签名与验证的数学推导
- 确定性签名的好处
- X25519 和 Ed25519 的分工
一条生成密钥的命令背后,是随机数、哈希函数、椭圆曲线运算和签名协议的组合。
32 字节随机种子经 SHA-512 派生出私钥标量和签名前缀,公钥 A = aB。
签名是 (R, S) 共 64 字节;验证只需检查 SB = R + kA 是否成立。
Ed25519 管身份认证,X25519 管密钥协商,会话加密交给对称算法。
先分清 ssh 和 ssh-keygen
生成 SSH 密钥用的是 ssh-keygen:-t 指定密钥类型,ed25519 表示使用 Ed25519 数字签名算法。执行后默认生成两个文件:
~/.ssh/id_ed25519 # 私钥,自己妥善保管
~/.ssh/id_ed25519.pub # 公钥,放到服务器 authorized_keys 或 GitHub/GitLab-C 参数可以给公钥加一段方便识别的备注,它不参与任何密码学计算,只是附加文本:
ssh-keygen -t ed25519 -C "[email protected]"一个常见的手误
ssh -t ed25519 的含义完全不同——ssh 命令的 -t 是强制分配伪终端,后面的 ed25519 会被当作主机名,SSH 会去连一台叫 ed25519 的服务器。生成密钥,认准 ssh-keygen。
Ed25519 是一种数字签名算法
Ed25519 是 EdDSA 签名算法基于 Edwards25519 椭圆曲线的具体实现,它做三件事:根据私钥生成公钥、用私钥对数据签名、用公钥验证签名。数字签名解决的核心问题是:让持有私钥的人能证明某段数据确实由自己签发,同时让任何持有公钥的人都能验证。
在 SSH 公钥认证中,客户端用 Ed25519 私钥对登录过程中的一段认证数据签名,服务器用 authorized_keys 里的公钥验证——验证通过,就说明客户端确实掌握对应的私钥。
要注意的是,Ed25519 在 SSH 里只负责身份认证和签名,不负责加密会话数据。一次完整的 SSH 连接会同时用到好几类算法:
| 角色 | 干什么 | 常见算法 |
|---|---|---|
| 密钥交换 | 协商会话密钥 | X25519、后量子混合算法 |
| 主机密钥 | 证明服务器身份 | Ed25519、RSA |
| 用户公钥 | 证明客户端身份 | Ed25519、RSA |
| 对称加密 | 加密连接数据 | AES-GCM、ChaCha20-Poly1305 |
| 消息认证 | 检测数据篡改 | Poly1305、HMAC |
Ed25519 承担的是其中的签名与身份认证职责。顺带一提,密钥交换那一行正在经历一场悄悄的迁移,之前写过一篇《从一条 SSH 警告说起:后量子密码迁移正在悄悄发生》专门讲它。
名字里的“25519”从哪来
Ed25519 的椭圆曲线运算在一个有限域中进行,这个有限域使用的素数是:
p=2255−19
“25519”就来源于此。所有坐标的加减乘除最终都对 p 取模,无论中间数字多大,结果都被限制在有限范围内。曲线方程写成:
−x2+y2=1+dx2y2,d=−121665/121666(modp)
满足这个方程的坐标对 (x,y) 就是曲线上的一个点。曲线点之间定义了一种特殊的“点加法”;把同一个点重复相加,叫标量乘法:
A=aB
其中 B 是算法规定的固定基点,a 是一个非常大的整数。关键性质在于:从 a 和 B 算 A 非常高效,但只知道 A 和 B 想反推 a 极其困难——这就是椭圆曲线离散对数问题。Ed25519 的安全性很大程度上就建立在这种“正向容易、反向不可行”的数学结构上。
私钥和公钥是怎样生成的
执行 ssh-keygen -t ed25519 时,系统先用安全随机数生成器产生一个 32 字节的随机种子,这是私钥最核心的秘密材料。然后计算:
h=SHA512(seed)
SHA-512 输出 64 字节,拆成两半用:
随机种子 seed
│
▼
SHA-512(64 字节)
│
├── 前 32 字节 → 私钥标量 a(经过 clamping 位处理)
└── 后 32 字节 → 签名前缀 prefix
公钥 A = aB前半部分要做一组固定的位运算(称为 clamping),清除和设置若干特定位,让私钥标量符合曲线运算需要的结构。得到 a 后,用基点算出公钥 A=aB。
拥有种子就能重新算出私钥标量和公钥;只拥有公钥,在现实可行的算力下无法还原私钥。
标准 Ed25519 的公钥编码只有 32 字节,但 OpenSSH 的 .pub 文件还包了算法名、Base64 编码和备注,所以看起来长得多:
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... [email protected]
└─ 算法名 └─ 编码后的公钥数据 └─ 可选备注Ed25519 怎么签名
设消息为 M,私钥标量为 a,公钥为 A。签名分四步:
先用私钥派生的 prefix 和消息算一个临时标量(∥ 表示字节拼接,L 是基点所在子群的阶):
r=SHA512(prefix∥M)modL
用它算出一个临时曲线点:
R=rB
再把临时点、公钥和消息一起哈希:
k=SHA512(R∥A∥M)modL
最后算出:
S=r+ka(modL)
签名就是 (R,S) 两部分,各占 32 字节,完整签名固定 64 字节。注意签名里有 R 和 S,但不包含私钥本身。
公钥为什么能验证签名
验证者手里有:公钥 A、原始消息 M、签名中的 R 和 S。验证时先重新算出 k=SHA512(R∥A∥M)modL,然后检查一个等式:
SB=?R+kA
这个等式为什么成立?从签名过程直接推出来。签名时 S=r+ka,两边同乘基点 B:
SB=(r+ka)B=rB+k(aB)=R+kA
中间用到了标量乘法的分配性质,以及 R=rB、A=aB 这两个定义。
妙处在于:验证者用的全是公开数据(R、S、A、M),却无法从中得到私钥标量 a。而只要消息被改动,重新算出的 k 就变了,等式不再成立;R 或 S 被改也一样。所以一次验证同时完成了两件事——确认签名者掌握私钥(身份),确认数据没被动过(完整性)。
为什么 Ed25519 用确定性签名
很多传统椭圆曲线签名算法(比如 ECDSA)每次签名都要生成一个新的随机临时数,而且对随机数质量要求极高——一旦临时数重复、可预测或有偏差,攻击者就可能从多个签名反推出私钥。历史上确实发生过多起这样的私钥泄露事故。
Ed25519 的临时标量是 r=H(prefix∥M),由私钥派生数据和消息内容共同决定:同一把私钥对同一条消息签名,得到的签名相同。这叫确定性签名,它把日常签名对系统随机数生成器的依赖降到了零。
当然,这不等于 Ed25519 完全不需要随机数——最初生成私钥种子时仍然必须用高质量随机数。两者的关系是:
生成私钥时:必须依赖安全随机数
日常签名时:不需要重新生成随机临时数种子安全生成并妥善保管,后续签名就不会因为临时随机数出问题而泄露私钥。
为什么 SSH 偏爱 Ed25519
Ed25519 在安全性、性能和实现难度之间取得了不错的平衡:公钥 32 字节、签名 64 字节,传输和存储开销小;Edwards 曲线的点加法公式规整,适合实现成统一的运算路径;确定性签名消除了随机数故障风险;参数由标准固定死,用户不需要自己选曲线和基点。
和传统 RSA 对比一下就有体感:
ssh-keygen -t ed25519 # 现代默认选择
ssh-keygen -t rsa -b 4096 # 兼容老系统时仍会用到RSA 依然被大量系统支持,兼容老旧环境时可能还得用;但在客户端和服务器都支持 Ed25519 的情况下,它通常是更简洁的选择——密钥更短,签名计算更快。
算法好 ≠ 实现随便写
成熟实现还要严格检查公钥和签名编码、拒绝非法曲线点、检查签名标量范围、正确处理小阶点等边界问题。实际使用请认准 OpenSSH、OpenSSL、libsodium 这类经过广泛审查的实现,不要自己手写椭圆曲线签名代码。
Ed25519、Curve25519、X25519 是什么关系
三个名字长得很像,确实同源,但分工不同。Curve25519 最初指 Daniel J. Bernstein 设计的椭圆曲线密码系统,有限域同样基于 2255−19。在它之上:
X25519:用于协商秘密(Diffie-Hellman 密钥交换,见于 SSH、TLS、WireGuard)
Ed25519:用于证明身份(数字签名,见于 SSH 认证、Git 签名、软件包签名)它们的数学结构相关,但曲线表示、编码规则和协议用途都有差异,不能把 Ed25519 密钥文件直接当 X25519 密钥用。某些密码学库提供两者的密钥转换,但那必须遵守明确的算法规则,不是复制、截断或重命名文件能完成的。
SSH 登录时实际发生了什么
假设已经生成密钥并用 ssh-copy-id [email protected] 把公钥送上了服务器(保存在目标用户的 ~/.ssh/authorized_keys)。再次连接时,认证过程可以简化成:
客户端 服务器
│ │
│ 声明希望使用某个公钥登录 │
├─────────────────────────────────►│
│ 服务器检查公钥是否被允许 │
│◄─────────────────────────────────┤
│ 使用私钥对认证数据签名 │
├─────────────────────────────────►│
│ 用 authorized_keys 中的 │
│ 公钥验证签名 → 允许登录 │
│◄─────────────────────────────────┤整个过程中私钥从不离开客户端。服务器能验证签名,却无法从公钥或签名还原私钥——所以服务器就算存着你的公钥,也没法冒充你去登录别的服务器。这正是公钥认证相比直接传密码最重要的特征:真正的秘密始终留在本地。
私钥文件里存的到底是什么
从算法层面看,Ed25519 私钥的核心就是那 32 字节种子,但 ~/.ssh/id_ed25519 存的远不止一个裸数字:还有密钥算法标识、公钥数据、校验信息、备注、文件格式信息,以及可选的密码保护参数。
如果生成密钥时设置了密码(passphrase),OpenSSH 会用密钥派生函数从密码生成加密密钥,把文件中的敏感部分加密。私钥密码保护的是磁盘上的文件:没有密码保护的私钥文件被复制走就能直接用;有强密码保护的,攻击者还得先破解密码。
给已有私钥补设或修改密码:
ssh-keygen -p -f ~/.ssh/id_ed25519设了密码之后配合 ssh-agent 使用,就不用每次连接都重复输入。
日常使用的几条建议
- 在可信设备上生成密钥,确保系统随机数来源正常;
-C备注写上用途或设备名,方便日后管理。 - 私钥不进聊天工具、邮件、网盘,更不进 Git 仓库;权限保持
chmod 600 ~/.ssh/id_ed25519。 - 一把私钥长期通吃所有服务器,泄露时的攻击面会非常大。按设备、用途或安全边界拆分密钥:个人代码托管一把、公司一把、生产运维一把、自动化部署专用一把。
- 密钥失效或设备丢失后,及时从服务器
authorized_keys、代码托管平台和其他授权系统里删掉对应公钥——公钥不删,授权就还在。
结语
ssh-keygen -t ed25519 里的 ed25519,指的是用 Ed25519 数字签名算法生成 SSH 密钥。它以安全随机的种子为根,经 SHA-512 派生出私钥标量,用椭圆曲线标量乘法算出公钥 A=aB;签名产出 R=rB 和 S=r+kamodL;验证只需检查 SB=R+kA。
整套机制利用的是椭圆曲线标量乘法“正向容易、反向不可行”的性质:私钥持有者能生成只有自己才能产生的签名,任何拿到公钥的人都能验证,而私钥始终留在客户端。
理解了这些,ssh-keygen -t ed25519 就不再是一条照抄的命令,而是一套由随机数、哈希函数、椭圆曲线运算和签名协议共同构成的身份认证机制的入口。
- Ed25519 是数字签名算法,在 SSH 里负责身份认证,不负责加密会话数据;名字来自素数 2²⁵⁵ − 19。
- 私钥核心是 32 字节随机种子,经 SHA-512 派生标量 a 和前缀,公钥 A = aB;反推私钥是离散对数问题。
- 签名 (R, S) 共 64 字节,验证即检查 SB = R + kA;确定性签名消除了对每次签名随机数的依赖。
- 私钥从不离开客户端;按用途拆分密钥、给私钥设密码、失效后及时清理服务器上的公钥。
版权所有
版权归属:Shuo Liu
