本文へスキップ
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
公開鍵の形式
spki-pemspki-der
秘密鍵の形式
pkcs8-pempkcs8-derencrypted-pkcs8sec1-pem

openssl gem は Ruby に同梱されており、システムの OpenSSL をラップします(ruby:4.0.7 イメージでは OpenSSL 3.5.7、gem のバージョンは 4.0.2)。OpenSSL::PKey::EC#sign(digest, data) はランダムな nonce で ASN.1 DER の署名を生成し、low-S 正規化は行いません。verify は high-S を受け入れます。dsa_sign_asn1 は計算済みのダイジェストに署名します。

形式の誤った署名は false を返すのではなく例外を発生させます。64 バイトの生の r‖s 署名や 3 バイトのデータでは OpenSSL::PKey::PKeyError: EVP_DigestVerify が発生し、形式が正しく別のメッセージに対する署名では false が返りました。真偽値だけをチェックするコードは JWS 形式の署名でクラッシュするため、PKeyError を捕捉するか、サンプルのように先に変換してください。DER ⇄ 生形式の変換ツールでは 2 つの形式を並べて確認できます。

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 には、この gem を基盤とする ruby-jwt を使います。

事実とその根拠となるチェック

以下の内容はドキュメントからの転記ではありません。2026年10月20日に、チェック content/libraries/verify/ruby-openssl/run.sh がコンテナイメージ 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 はランダムです。同じメッセージの 2 つの署名は異なります。

    チェックによる記録
    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

    OpenSSL::PKey.generate_key で Ed25519 と Ed448 を扱えます。

    チェックによる記録
    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 で SPKI の PEM と DER を読み込めます。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

    PKCS#8 の PEM と DER、暗号化された 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 に変換し、公開鍵だけを公開して、生形式の署名では検証メソッドが例外を発生させることを確認します。
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

ライブラリ