跳到主要内容
ecdsa.com
Ruby

Ruby OpenSSL (openssl gem)

Ruby 的 openssl gem,所有 Ruby 密码学库和 JWT 库都建立在这个绑定之上:签名为 DER 格式,格式错误的输入会抛出异常而不是返回 false,在 OpenSSL 3 上密钥不可变,并通过通用的 PKey API 支持 ML-DSA。
许可证
Ruby OR BSD-2-Clause
检查的版本
4.0.2 ·
源代码
github.com/ruby/openssl
文档
docs.ruby-lang.org/en/master/OpenSSL.html

兼容性概览

ECDSAEdDSA后量子

ECDSA 曲线
P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1
EdDSA
Ed25519Ed448
后量子算法
ML-DSASLH-DSA
公钥格式
spki-pemspki-der
私钥格式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

openssl gem 随 Ruby 一起发布,封装了系统中的 OpenSSL——在 ruby:4.0.7 镜像中为 3.5.7,gem 版本为 4.0.2。OpenSSL::PKey::EC#sign(digest, data) 生成 ASN.1 DER 签名,使用随机 nonce,不做 low-S 规范化;verify 接受 high-S。dsa_sign_asn1 对你已经计算好的摘要签名。

格式错误的签名会抛出异常,而不是返回 false:64 字节的原始 r‖s 签名或 3 字节的数据块会抛出 OpenSSL::PKey::PKeyError: EVP_DigestVerify,而针对另一条消息、格式正确的签名则返回 false。只检查布尔值的代码遇到 JWS 风格的签名会崩溃,因此要么用 rescue 捕获 PKeyError,要么像示例那样先做转换;DER ⇄ 原始格式转换器可以并排显示两种形式。

在 OpenSSL 3 上密钥是不可变的:对已有的 EC 密钥赋值 private_key 时抛出了 pkeys are immutable on OpenSSL 3.0,那些通过设置点或标量来构造密钥的旧教程因此不再适用。应改为从 DER 或 PEM 构造密钥——裸 SEC1 点必须先包装进 SPKI 结构。注意各个方法写出的内容:私钥上的 to_pem 写出包含秘密值的 SEC1 EC PRIVATE KEY,private_to_pem 写出 PKCS#8,而可以公开发布的是 public_to_pem 的输出。

支持的曲线与 OpenSSL 一致:P-224 到 P-521、secp256k1 和 Brainpool r1 曲线,另外可通过 OpenSSL::PKey.generate_key 使用 Ed25519 和 Ed448。同一套通用 API 完成了 ML-DSA-44/65/87 和 SLH-DSA 密钥的生成、签名与验证;ML-KEM 密钥可以生成,但 4.0.2 中的 PKey 不提供封装方法。JWT 库 ruby-jwt 就构建在这个 gem 之上。

事实及其依据的检查

以下内容均非抄自文档。检查脚本 content/libraries/verify/ruby-openssl/run.sh 于2026年10月20日在容器镜像 ruby:4.0.7-slim-trixie 中运行了 Ruby OpenSSL (openssl gem) 4.0.2(运行时为 Ruby 4.0.7, OpenSSL 3.5.7 9 Jun 2026);本页内容必须与它的输出一致,每条事实都列出了对应的输出行。

  • ECDSA 签名编码

    der

    sign(digest, data) 返回 ASN.1 DER。

    检查记录
    fact ecdsa-signature-encoding = der
  • ECDSA 的 nonce

    random

    nonce 随机:同一条消息的两个签名不同。

    检查记录
    fact ecdsa-nonce = random
  • 签名时的 low-S

    either

    s 没有经过规范化:64 个签名中出现了 high-S 值。

    检查记录
    fact ecdsa-low-s-sign = either
  • 验证时的 high-S

    accepts-high-s

    verify 接受了把 s 替换为 n − s 的签名。

    检查记录
    fact ecdsa-low-s-verify = accepts-high-s
  • 签名调用对哪些内容做哈希

    hashes-message-or-digest

    sign 用传入的摘要算法计算哈希;dsa_sign_asn1 对预先计算的摘要签名,该签名针对原消息验证通过。

    检查记录
    fact ecdsa-prehash = hashes-message-or-digest
  • 格式错误的 ECDSA 签名

    throws

    原始 r‖s 签名和 3 字节的数据块会抛出 OpenSSL::PKey::PKeyError: EVP_DigestVerify;针对另一条消息的有效签名返回 false。

    检查记录
    fact ecdsa-malformed-signature = throws
    evidence ecdsa-malformed-signature: raw r||s: OpenSSL::PKey::PKeyError: EVP_DigestVerify
    evidence ecdsa-malformed-signature: 3 bytes: OpenSSL::PKey::PKeyError: EVP_DigestVerify
    evidence ecdsa-malformed-signature: wrong message: false
  • ECDSA 曲线

    P-224P-256P-384P-521secp256k1brainpoolP256r1brainpoolP384r1brainpoolP512r1

    P-224、P-256、P-384、P-521、secp256k1 和 brainpoolP256r1/P384r1/P512r1 的签名与验证均成功。

    检查记录
    fact ecdsa-curves = P-224, P-256, P-384, P-521, secp256k1, brainpoolP256r1, brainpoolP384r1, brainpoolP512r1
  • EdDSA

    Ed25519Ed448

    Ed25519 和 Ed448,通过 OpenSSL::PKey.generate_key 使用。

    检查记录
    fact eddsa = Ed25519, Ed448
  • 后量子算法

    ML-DSASLH-DSA

    通过通用的 PKey API 完成了 ML-DSA-44/65/87 和 SLH-DSA-SHA2-128s 的密钥生成、签名与验证;ML-KEM 密钥可以生成,但没有封装方法。

    检查记录
    fact pqc = ML-DSA, SLH-DSA
    evidence pqc: ML-KEM-768 key generation, and whether PKey offers encapsulate: false
  • 公钥格式

    spki-pemspki-der

    通过 OpenSSL::PKey.read 读取 PEM 和 DER 格式的 SPKI;裸 SEC1 点只有包装进 SPKI 后才能加载。

    检查记录
    fact public-key-formats = spki-pem, spki-der
    evidence public-key-formats: a 65-byte SEC1 point wrapped in an SPKI structure by hand, then loaded: ok
  • 私钥格式

    pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

    PEM 和 DER 格式的 PKCS#8、加密的 PKCS#8 以及 SEC1 PEM;to_pem 写出 EC PRIVATE KEY,private_to_pem 写出 PKCS#8。

    检查记录
    fact private-key-formats = pkcs8-pem, pkcs8-der, encrypted-pkcs8, sec1-pem
    evidence private-key-formats: key.to_pem writes -----BEGIN EC PRIVATE KEY-----; key.private_to_pem writes -----BEGIN PRIVATE KEY-----
  • EC 密钥对象是否可变

    no

    对已有密钥赋值 private_key 时抛出了 pkeys are immutable on OpenSSL 3.0。

    检查记录
    fact ec-key-mutable = no
    evidence ec-key-mutable: assigning private_key on an existing EC key: OpenSSL::PKey::PKeyError: pkeys are immutable on OpenSSL 3.0

用法

以 DER 格式签名,转换为原始 r‖s,只公开公钥,并观察 verify 遇到原始签名时会抛出异常。
require "openssl"

key = OpenSSL::PKey::EC.generate("prime256v1")
digest = OpenSSL::Digest.new("SHA256")
message = "order #1042 approved"

der = key.sign(digest, message) # ASN.1 DER

# Raw r||s for JWS or WebCrypto: take the two INTEGERs and pad each to 32 bytes.
r, s = OpenSSL::ASN1.decode(der).value.map(&:value)
raw = r.to_s(2).rjust(32, "\0") + s.to_s(2).rjust(32, "\0")

# Publish only the public half: public_to_pem, never to_pem (which writes the private key).
public_key = OpenSSL::PKey.read(key.public_to_pem)
puts "verified: #{public_key.verify(digest, der, message)}, raw bytes: #{raw.bytesize}"

# A raw signature is not "false" here: verify raises OpenSSL::PKey::PKeyError.
begin
  public_key.verify(digest, raw, message)
rescue OpenSSL::PKey::PKeyError => e
  puts "raw signature: #{e.class}"
end

代码片段测试工具会在容器镜像 ruby:4.0.7-slim-trixie 中原样运行每个示例,只要输出不匹配,运行就会失败。

命令
ruby usage.rb
输出必须匹配(正则表达式)
verified: true, raw bytes: 64\nraw signature: OpenSSL::PKey::PKeyError

库