2 ポイント 投稿者 GN⁺ 2026-01-01 | 1件のコメント | WhatsAppで共有
  • libsodium の低レベル関数 crypto_core_ed25519_is_valid_point() で、Edwards25519 曲線における不適切な点検証の不具合が見つかった
  • この関数は点が主要な暗号学的グループに属するか確認すべきだが、混合位数(subgroup) の一部の点を誤って通過させていた
  • 原因は内部座標の検証時に X=0 だけを確認し、Y=Z の検証を省略していたコード上のミスで、誤った点が有効と扱われる可能性があった
  • 修正版では 2 つの条件 (X=0, Y=Z) をともに検査するよう変更されており、1.0.20 以下のバージョン または 2025年12月30日以前のリリースが影響を受ける
  • 高レベル API (crypto_sign_*) は影響を受けず、Ristretto255 グループの使用が安全性と性能の両面で推奨される

libsodium プロジェクト概要

  • libsodium は 13 年前に始まったプロジェクトで、暗号を簡単に使えるようにするシンプルな API を提供することを目標としている
    • 内部アルゴリズムを利用者が知る必要なく、高レベルの演算だけを実行できるよう設計されている
  • API の 互換性維持 を重視しており、NaCl API をベースに現在まで一貫性を保っている
  • 一部の利用者がドキュメントに明記された制限を超えて 低レベル関数 を直接使用することで、ライブラリが 暗号ツールキット のように使われる事例が増えている

発見されたバグの原因

  • 問題の関数: crypto_core_ed25519_is_valid_point()
    • Edwards25519 曲線で主要グループ (位数 L) に属さない点を拒否しなければならない
    • しかし 混合位数 (2L, 4L, 8L など) の点の一部が検証を通過していた
  • 内部的には点の位数を確認するために L を掛け、その結果が 単位元(identity) かどうかを検査している
    • 単位元は X=0, Y=Z の形で表現されるが、従来のコードは X=0 だけを検査していた
    • このため Y≠Z の誤った点が有効と扱われていた
  • 例: 主要グループの点 Q に位数 2 の点 (0, -1) を加えた Q+(0, -1) は誤った点だが、修正前は通過していた

修正内容

  • パッチコミット では次のように変更された
    • 従来コード: return fe25519_iszero(pl.X);
    • 修正コード: fe25519_sub(t, pl.Y, pl.Z); return fe25519_iszero(pl.X) & fe25519_iszero(t);
  • これで X=0 と Y=Z の 2 条件をともに確認し、正しい検証を行う

影響範囲

  • 次の条件に該当する場合、影響を受ける可能性がある
    • バージョン 1.0.20 以下 または 2025年12月30日以前のリリースを使用している
    • crypto_core_ed25519_is_valid_point()信頼できない入力点を検証している
    • Edwards25519 曲線演算を直接実装している利用者
  • ただし大半の利用者には影響がない
    • 高レベル API (crypto_sign_*) はこの関数を使用しない
    • crypto_scalarmult_ed25519 は誤った公開鍵でも情報漏えいを起こさない
    • crypto_sign_keypair および crypto_sign_seed_keypair で生成された鍵は正しいグループに属している

推奨される対応

  • Ristretto255 グループ の使用を推奨
    • 2019 年から libsodium に含まれており、cofactor 関連の問題を解決する
    • デコードされた点は自動的に安全で、追加の検証は不要
    • Edwards25519 より高速な演算性能を提供する
  • 更新が不可能な場合は、提示されている アプリケーションレベルの代替関数 (is_on_main_subgroup) を使って検証できる

修正済み配布物とサポート

  • 問題発見直後に即座に修正され、2025年12月30日以降に配布されたすべての安定版に含まれている
    • 公式 tarball、Visual Studio/MingW バイナリ、NuGet パッケージ、Android 向けビルド、swift-sodium xcframework、Rust libsodium-sys-stablelibsodium.js を含む
  • 新しい point release も予定されている
  • プロジェクトは 1 人のメンテナ によって運営されており、OpenCollective 支援 を通じて継続的な改善を支援できる

1件のコメント

 
GN⁺ 2026-01-01
Hacker Newsのコメント
  • PHPライブラリ sodium_compat も今回の問題の影響を受けていた
    関連内容は security-advisories PR #756 で確認できる
    今晩にはオープンソース生態系内の他の Ed25519実装 もすべて点検し、同じ検証漏れがあるか確認する予定だ

    • いくつかのライブラリでは、検証ロジック自体が欠けていることを発見した
      ただし、上で言及された脆弱性のように 誤った方法で実装された事例 はなかった
      私がメールを送っていないなら、おそらく ianixのEd25519デプロイ一覧 に載っていないか、私の見落としか、あるいは安全な実装である可能性が高い
    • オープンソースに貢献してくれてありがとうと伝えたい
  • この4か月間、Lean4向け sodium バインディング を開発している
    いまは Ristretto255 の段階に到達したが、なぜ著者がこの技術に夢中なのか理解できるようになった
    Ristretto は Curve25519 上で任意の多項式を構成できる洗練された API で、試していて本当に楽しい
    もし著者がこの投稿を見ているなら、心から感謝を伝えたい

    • このプロジェクトの 公開リポジトリ はあるのだろうか
  • Libsodium の目標は低レベル関数ではなく 高レベルAPI を提供することだった
    ユーザーが内部アルゴリズムを知らなくても済むように設計されていたが、時間が経つにつれて低レベル関数を直接使うケースが増えていった
    結果として Libsodium は アルゴリズムツールキット のように使われるようになった
    重要なのは、ユーザーが望む方向を認識し、プロジェクトを特定のやり方だけに強制しないことだ
    一部のプロジェクトはこの点で独善的になり、失敗を経験する

    • 興味深い指摘だ。だが逆に、ユーザーが望んでいると思われるもの と実際に必要なものは違うかもしれない
      非専門家が暗号学的プリミティブを直接使うのは危険だ
      Libsodium は、ユーザーが自分自身を危険にさらさないよう設計されている
      ライブラリは 誤用できないようにすること が理想的だ
      関連記事として “If You're Typing The Letters A-E-S Into Your Code, You're Doing It Wrong” を勧める
    • すべての内部関数を API 契約とみなすと、ほとんどすべての変更が 互換性破壊 につながる
      そのため、一部の機能を private や internal に制限するのが正しい選択であることが多い
      Libsodium がその境界をうまく引けていたかは確信が持てないが、バランスが重要だ
    • 以前 CeeFIT という C++ テストフレームワークを作ったことがあり、コンパイル時に fixture を登録する方式を誇りに思っていた
      ところが一部のユーザーはそれを バッチランナー のように使っていた
      彼らの要望を支えるためにいくつかバグを修正した
      結局のところ、ユーザーがいるという事実そのものがうれしかった
  • 今回のバグは、微妙だが重要な 暗号検証エラー
    「有効か確認する」という単純なチェックは、実際には非常に複雑だ
    素数位数部分群の外にある点を許してしまうと、即座に脆弱性が見えなくても上位層の前提を壊しうる
    また、低レベルプリミティブは意図以上に広く再利用されるため、小さな検証漏れでも 大きな波及効果 を持ちうる

    • ただし X25519Ed25519 は、そもそもこうした検証を必要としないよう設計されている
      Curve25519 上でより複雑なプロトコルを作るときにだけ部分群の問題が生じる
      だから私は、可能な限りすべての点を素数位数部分群へ再写像する
      Monocypher にはそうした高度な関数がある
      たとえば crypto_x25519_dirty_fast()crypto_elligator_map() のような関数だ
      こうした “dirty” 関数は、曲線全体を覆う公開鍵を生成し、乱数と区別できないようにする
      その後 X25519 鍵交換を行っても、同じ共有秘密を得ることができる
      これは DJB の設計のおかげ で、公開鍵が非正規でも共有秘密が素数位数部分群へ写像されるからだ
      結局、Ristretto が必要なのはこうした再写像が不可能な場合だけだ
      もちろん素数位数群の抽象化は有用だが、そのようなプロトコルを設計できる人なら、自明でない cofactor を扱うこともできるはずだ
  • 大企業に勤めているなら、Frank を会社として支援することを検討してほしい

    • 私は Apple で働いているが、Frank が誰なのか知らないし、支援の手続きも分からない
      たとえ知っていたとしても、会社の口座ではなく 自分の個人資金 から支援することになりそうだ
  • libnacl も影響を受けるのか気になる
    私は毎日 libnacl でコンパイルされたソフトウェアを使っているが、“libsodium” でコンパイルされたものは使っていない

  • 本当に素晴らしいライブラリだ
    Frank Denis に感謝を伝えたい