Upgrade to Pro — share decks privately, control downloads, hide ads and more …

人命を救う技術としてのEnd-to-End暗号化とMessaging Layer Security

Avatar for sylph01 sylph01
January 08, 2026
48

人命を救う技術としてのEnd-to-End暗号化とMessaging Layer Security

2026/1/9 @ BuriKaigi 2026

Avatar for sylph01

sylph01

January 08, 2026
Tweet

More Decks by sylph01

Transcript

  1. いろいろやります 音ゲー ( 主にDanceDanceRevolution) クラシック音楽 ( オーケストラでファゴットを吹きます) DJ ( 音ゲー楽曲を中心に)

    鉄道乗りつぶし (JR 線99%+) 自作キーボード 気になることがあったら懇親会で話しましょう! 5
  2. いつもの注意事項 暗号API は誤った使い方をしやすいです プロトコルが安全でも運用をミスることがあります 「私は暗号の専門家ではありません」 a.k.a. "I am not djb"

    production で使う場合には専門家のレビューを受けてください あと私は該当分野で博士号はおろか修士号すら持ってないので… 8
  3. 10

  4. End-to-End 暗号化 = サービス運営者がメッセージを読めない LINE, Facebook Messenger, Signal, WhatsApp, X

    Chat, ... それぞれ(似てるけど)独自のE2EE のプロトコルを持っている 一部のアプリでは明示的に有効にしないとE2EE にならないことに注意 14
  5. 25

  6. Hybrid Public Key Encryption MLS のビルディングブロックの一つ 「公開鍵で暗号化して秘密鍵で復号」をよりフォーマルにしたやつ 以下の組み合わせ Key Encapsulation

    Mechanism (KEM) (≒ 公開鍵暗号) Key Derivation Function (KDF) (≒ ハッシュ) Authenticated Encryption with Associated Data (AEAD) (= 共通鍵 暗号) Ruby では hpke gem として提供しています だいたい2023-2024 にかけての主な活動。2025/12/31 にver 1.0.0 をリリースしました 29
  7. 2 人の場合は簡単 # 両者とも共通の秘密から始めて chain_key[0] = "some common secret" #

    HMAC は「鍵付きハッシュ」 # n 周期目のメッセージの鍵はn 周期目のchain_key から作る message_key[n] = hmac_sha256(chain_key[n], 0x02) # n+1 周期目のchain key はn 周期目のchain key から # メッセージの鍵とは違うHMAC 鍵を使って作る chain_key[n+1] = hmac_sha256(chain_key[n], 0x01) Double Ratchet algorithm の "symmetric key ratchet" と呼ばれる部分。要するにハッシュをチェーンしてる。 33
  8. Secret Tree def self.populate_tree_impl(suite, tree, index, secret) tree.array[index] = {

    ... } unless Melos::Tree.leaf?(index) left_secret = Melos::Crypto.expand_with_label(suite, secret, "tree", "left", suite.kdf.n_h) right_secret = Melos::Crypto.expand_with_label(suite, secret, "tree", "right", suite.kdf.n_h) populate_tree_impl(suite, tree, Melos::Tree.left(index), left_secret) populate_tree_impl(suite, tree, Melos::Tree.right(index), right_secret) end end encryption_secret から再帰的に木の葉ノードまでsecret を作る file lib/melos/secret_tree.rb 43
  9. Secret Tree ratcheting を行う部分は以下: def self.ratchet_application(suite, tree, leaf_index) node_index =

    leaf_index * 2 generation = tree.array[node_index]['next_application_ratchet_secret_generation'] application_ratchet_secret = tree.array[node_index]['application_ratchet_secret'] application_nonce = Melos::Crypto.derive_tree_secret(suite, application_ratchet_secret, "nonce", generation, suite.hpke.n_n) application_key = Melos::Crypto.derive_tree_secret(suite, application_ratchet_secret, "key", generation, suite.hpke.n_k) next_application_ratchet_secret = Melos::Crypto.derive_tree_secret(suite, application_ratchet_secret, "secret", generation, suite.kdf.n_h) tree.array[node_index]['next_application_ratchet_secret_generation'] = generation + 1 tree.array[node_index]['application_ratchet_secret'] = next_application_ratchet_secret tree.array[node_index]['application_nonce'] = application_nonce tree.array[node_index]['application_key'] = application_key end file lib/melos/secret_tree.rb 44
  10. Secret Tree だけど実質はこう: key = Crypto.derive_tree_secret(ratchet_secret[n], "key", n) nonce =

    Crypto.derive_tree_secret(ratchet_secret[n], "nonce", n) ratchet_secret[n + 1] = Crypto.derive_tree_secret(ratchet_secret[n], "secret", n) これでメッセージごとの key と nonce が得られる。 2-party のratcheting と比較するとわかりやすい: chain_key[0] = "some common secret" message_key[n] = hmac_sha256(chain_key[n], 0x02) chain_key[n+1] = hmac_sha256(chain_key[n], 0x01) 45
  11. TreeKEM 各ユーザーは 葉ノード が割り当て られ、 それぞれ leaf key pair を持つ

    各ノードに対応するkey pair は path_secret から導出できる (後述) 48
  12. TreeKEM 0 が UpdatePath ( 黄) を作るとき UpdatePath 沿いの copath

    nodes (green) を探す ParentNode が全部埋まっている とき、 path secret を copath node の鍵それぞれに対して暗号化する ParentNode の公開鍵は path_secret から導出できる 52
  13. TreeKEM 最も単純な2-leaf tree で見てみる 0 がUpdatePath 作成、 1 は path_secret

    を持つ path_secret は 2 の公開鍵で暗号 化される ノード 2 は自身の秘密鍵を知ってい るので復号可能 53
  14. TreeKEM commit_secret を直接全員に対 して暗号化しても動作はする けどそれは O(n) (n = # of

    members) commit_secret をUpdatePath 沿 いのcopath node に対して暗号化し ていくことで暗号化の数は O(log n) 59
  15. まとめ Secret Tree によって epoch_secret から各メッセージ ごとの鍵を得られる Key Schedule で今の世代の

    epoch_secret と commit_secret から次の世代の epoch_secret が得られる TreeKEM で commit_secret の合 意ができる 60
  16. 実際のメッセージの送信 アプリケーションメッセージは暗号化+ 署名された PrivateMessage に 包まれて送られる。 framed_content = Melos::Struct::FramedContent.create(...) authenticated_content

    = Melos::Struct::AuthenticatedContent.create(...) authenticated_content.sign(...) app_message = Melos::Struct::PrivateMessage.protect( authenticated_content, secret_tree, ... ) ハンドシェイクなどは署名のみの PublicMessage の場合がある 61
  17. グループの一生 グループは proposal と commit を通して更新される。 Proposal ユーザーの追加と削除 ユーザーのleaf key

    の更新 Pre-Shared Keys の挿入 Commit はこれらを確定し次の世代(epoch) に進める UpdatePath はCommit の中で伝えられる 62
  18. グループの一生 Group はユーザーの LeafNode を0-node group に追加して作られる ランダムな epoch_secret から開始する

    def init_group(node) # single node, a leaf node containing an HPKE PK and credential for the creator @ratchet_tree = [node] @leaf_index = 0 @tree_hash = Melos::Struct::RatchetTree.root_tree_hash(@cipher_suite, @ratchet_tree) @confirmed_transcript_hash = '' @epoch_secret = SecureRandom.random_bytes(@cipher_suite.kdf.n_h) ... @group_initialized = true end lib/group.rb from a work-in-progress version 63
  19. グループの一生 グループにjoin するユーザーは自 身の公開鍵を含むアイデンティ ティを KeyPackage の形で Directory に公開する グループの状態を含む

    Welcome メッセージを受け取りjoin する Figure 2 in RFC 9420 Section 3.2. The Delivery Service is out of scope for this implementation 64
  20. グループの一生 ユーザー A がグループ作成 B を追加するには A が B を追加する

    Add proposal を送信して Commit A は B に対して Welcome を送信する Figure 3 in RFC 9420 Section 3.2. 65
  21. グループの一生 Remove Z は B を削除する Remove と Commit を送信

    B は Commit に含まれる UpdatePath の path_secret を復号化でき ず、次の epoch_secret を 知ることができない 69
  22. 73

  23. 実装どんな感じよ? テストベクターは全部通る RubyKaigi → RubyConf Taiwan で以下をやった 諸々リファクタリング メッセージのparse を

    StringIO 化 Group の状態を Group オブジェクトにまとめた まだやってない クライアント向けAPI 他の実装に対する相互運用性テスト ほんとはここでクライアント向けAPI 終わった!って話をしたかったんだけど、年末にインフルをやってしまってな… 75
  24. クライアントインターフェース 鍵ペアの生成 グループにjoin する 0 人→1 人の場合は自身の鍵ペアをtree に登録してepoch 0 を初

    期化 n 人→n+1 人 (n>0) のときは KeyPackage を受け取りそこから鍵 を取り出してグループに追加、 Welcome メッセージを作る group ブランチ https:/ /github.com/sylph01/melos/commits/group でやってる 76
  25. クライアントインターフェース メッセージを作る FramedContent : application / proposal / commit を送信

    者の情報と合わせてラッピングする AuthenticatedContent : FramedContentTBS + FramedContentAuthData を使って FramedContent を署名し たもの PublicMessage : AuthenticatedContent に membership_tag というMAC を付加したもの RFC 9420, Section 6 77
  26. クライアントインターフェース 暗号化されたメッセージを作る FramedContent 、 AuthenticatedContent は同じ PrivateMessage : SenderData と

    PrivateMessageContent が暗号化されたもの。 PrivateMessageContent に AuthenticatedContent が含まれる 78
  27. クライアント向けAPI cipher_suite_id = 1 client = Melos::Client.new(cipher_suite_id) # generate key

    pairs key_package = Melos::KeyPackage.new(client) # create KP to publish to DS leaf_node = key_package.create_leaf_node('credential_name') node = Melos::Struct::Node.new_leaf_node(leaf_node) group = Melos::Group.new(client, 'group_id') group.init_group(node) # create a 1-node group 79
  28. クライアント向けAPI client2 = Melos::Client.new(cipher_suite_id) key_package2 = Melos::KeyPackage.new(client2) leaf_node2 = key_package2.create_leaf_node('credential_name2')

    key_package_message2 = key_package2.create_message(leaf_node2) # User 1 creates add proposal prop = group.create_add_proposal( key_package_message2, client.signature_private_key) # something like this...? commit = create_commit(prop, ...) group.apply_commit(commit) group2.apply_welcome(welcome) 80
  29. あったら嬉しい機能 鍵ペアの対応を調べる(Edwards curve とそうでないEC でAPI が違う) def self.signature_key_pair_corresponds?(suite, private_key, public_key)

    private_pkey = suite.pkey.deserialize_private_signature_key(private_key) public_pkey = suite.pkey.deserialize_public_signature_key(public_key) if suite.pkey.equal?(Melos::Crypto::CipherSuite::X25519) || suite.pkey.equal?(Melos::Crypto::CipherSuite::X448) # is an Edwards curve; check equality of the raw public key private_pkey.raw_public_key == public_pkey.raw_public_key else # is an EC; check equality of the public key Point private_pkey.public_key == public_pkey.public_key end end 83
  30. あったら嬉しい機能 UncompressedPointRepresentation 形式の鍵を得る(のもEdwards curve かそうでないかで違う) def self.derive_key_pair(suite, secret) pkey =

    suite.hpke.kem.derive_key_pair(secret) if suite.pkey.equal?(Melos::Crypto::CipherSuite::X25519) || suite.pkey.equal?(Melos::Crypto::CipherSuite::X448) # is an Edwards curve [pkey.raw_private_key, pkey.raw_public_key] else # is an EC [pkey.private_key.to_s(2), pkey.public_key.to_bn.to_s(2)] end end 84
  31. あったら嬉しい機能 OpenSSL 3.5.0 には post-quantum cryptography( 耐量子暗号) のAPI がある ML-KEM,

    ML-DSA, SLH-DSA MLS にML-KEM を導入するInternet-Draft がすでに出てる そういえば耐量子暗号のセッションありますね?暗号のセッション3 つもあるし2026 年は大暗号時代ですよ 85
  32. 86

  33. Why do you need End-to-End Encryption in Ruby? Because... DanceDanceRevolution

    のネタです。MAX.(period), Over the "Period" のニコニコ大百科が詳しい 88
  34. そのうちハンズオンをやります 実装がある程度でき次第 日本でMLS やMIMI(More Instant Messaging Interoperability) やる人増 やしたい Ruby

    実装があると irb で途中状態追いかけられて嬉しい Internet Society Japan Chapter のWorkshop などの形式を予定 95
  35. Shoutouts The Messaging Layer Security Working Group @ IETF Ruby

    でプロトコルを実装してる皆様(最近増えてる!) BuriKaigi のオーガナイザーの皆様 96