Signalプロトコルの解説とRuby実装

SignalプロトコルをRubyで実装してみる。

Signalプロトコルはメッセージングアプリケーションのエンドツーエンド暗号化でもっとも人気のあるプロトコルです。

Signalアプリの公式Webサイトに[技術的なホワイトペーパー](https://signal.org/docs/specifications/)がある。今回はこのドキュメントにできるだけ沿った形での実装を目指す。

## Signalプロトコルとは

エンドツーエンド暗号化を提供する暗号通信プロトコルです。それに加えて、相互認証、前方秘匿性も提供します。

オープン標準なので、WhatsApp、Facebook Messanger、Skypeなど多くのメッセージングアプリケーションで採用されています。

XEdDSAやVXEdDSA方式による署名、X3DHによる鍵交換、ダブルラチェットによる鍵更新から構成されています。

## 他の暗号通信プロトコルとの違い

Signalプロトコルの登場前は、PGP、S/MIME、OTRなどがセキュア通信プロトコルとしてよく用いられていました。これらのプロトコルはたいてい連合プロトコル（相互運用が可能なプロトコル）を使用しており、ネットワークの動作に中央の主体を必要としません。

Signalプロトコルではこの慣習をほぼ排し、中央のサービスを提供して、正式なクライアントアプリケーションを1つだけ提供するものとなっています。他のサーバーとの相互運用は妨げられているものの、オープン標準なので、多くの世界的なメッセージングアプリケーションで採用されています。



## Signalプロトコルで特徴的な点

Signalプロトコルは、それまでのセキュア通信プロトコル（例：PGPやOTR）の多くの欠点の修正を試みており、以下のような特徴があります。


#### TOFU（Trust On First Use、初回使用時に信頼）

初回の通信時に、安全ではない初回の鍵交換に依存して無条件に他のユーザーを信頼し、長期的に安全な通信チャンネルを確保する。通信の2回目以降には、セッションの秘密を経路外で照合して、初回の鍵交換がMITM（悪意の第三者）によるものではなかったことをいつでも自由に確認できる、という考え方のことです。
身近な例では、SSHの初期接続プロセスでも同じことを行っています。

#### 対話を開始するたびに前方秘匿性を確保する

たいていのセキュアトランスポートプロトコル（例：TLS）では、対話を開始する際の前方秘匿性を確保するために鍵交換を行います。これはSignalプロトコルでも同様です。ただし、SignalプロトコルではX3DHという特殊な鍵交換を用います。

#### メッセージ1つごとに前方秘匿性を確保する

同じセッション内での会話が数年間続いたとします。ある時点でセキュリティが破られた場合に、それまでの何年分かの通信に漏洩があってはいけません。Signalプロトコルでは対象鍵ラチェットによる逐次的な鍵更新でこれを解決しています。さらに、DHラチェットも組み合わせることで、ある程度の後方秘匿性も提供しています。

#### 漏洩後のセキュリティ（PCS、post-compromise security）というセキュリティ特性の導入

ある会話セッションの秘密がどこかの時点で漏洩した場合、それ以降の会話はすべて漏洩してしまうのか、それとも回復の余地があるのでしょうか。SignalプロトコルではPCSという新しいセキュリティ特性を導入した上で、DHラチェットという仕組みでそれに対処します。

## Signalプロトコルの構成要素とRubyでの実装

Rubyでの実装にはlibsodiumのRubyバインディングであるRbNaCl gemを用いています。

暗号プリミティブの独自実装はせずにライブラリで用意されているものを利用しています。

#### XEdDSAやVXEdDSA方式による署名

EdDSAという比較的新しい電子署名方式があります。XEdDSAは、X25519（またはX448）形式での公開鍵と秘密鍵を使用してEdDSA互換の署名を作成、検証する方法のことです。これによって、ECDHと署名の両方に単一の鍵ペア形式を使用することを可能にし、場合によっては両方のアルゴリズムに同じ鍵ペアを使用することもできるようになります。

VXEdDSAはXEdDSAを検証可能（Verifiable）にする拡張のことです。追加の証明情報を付加することで、署名鍵と暗号鍵の対応関係を検証可能にします。

これらの署名方式はライブラリで実装されていないため、X25519形式とEd25519形式の鍵ペアをそれぞれ用意することで対応しました。[x3dh-rubyリポジトリの該当箇所](https://github.com/ts-3156/x3dh-ruby/blob/master/x3dh.rb#L90)

#### X3DHによる鍵交換

X3DH鍵交換は、事前の共有鍵なしで2つの当事者間の共有秘密鍵を確立します。長期キー（IDキー）、署名済みキー、一時キー、ワンタイムキーを組み合わせ、前方秘匿性と暗号化の否認可能性を提供します。

X3DHのRuby実装は[x3dh-rubyリポジトリ](https://github.com/ts-3156/x3dh-ruby/blob/master/x3dh.rb)で公開しています。実装の解説は[X3DH 鍵合意プロトコルをRubyで実装する](https://qiita.com/ts-3156/items/73f177af97c71cf9a87e)で説明しています。


#### ダブルラチェットによる鍵更新

ダブルラチェットアルゴリズムは、暗号通信に用いる秘密鍵を随時更新することでその通信における前方秘匿性、後方秘匿性を提供します。つまり、秘密鍵が漏洩したとしても過去のメッセージの復号化はできず、将来のメッセージの復号化にもある程度の耐性があります。この性質は対象鍵ラチェットとDHラチェットを組み合わせることで実現されています。

ダブルラチェットアルゴリズムのRuby実装は[doubleratchet-ruby](https://github.com/ts-3156/doubleratchet-ruby/blob/master/double_ratchet.rb)で公開しています。実装の解説は[ダブルラチェットアルゴリズムをRubyで実装する](https://qiita.com/ts-3156/items/9ef770017ca88345df26)で説明しています。


## Signalプロトコルで未解決の課題

それぞれの課題について、各種のアプリケーションで色々と実装はされているもの、標準化された解決策はないのが現状です。

#### グループメッセージング

グループメッセージングにおいて、メンバーの管理方法が1つの課題となっています。ある1人がグループの参加者にメッセージを送信する時、クライアントが参加者すべてに送信する方式（= クライアント側ファンアウト）とサーバー側が参加者すべてに送信する方式（= サーバー側ファンアウト）が考えられ、それぞれに利点と欠点があります。
また、参加者が非常に多い（100人を超えるような）グループで本当に気密性を保てるのかはっきりしていません。
これらの課題についてはメッセージングレイヤーセキュリティ（MLS）という規格で標準化が進められています。

#### 複数デバイスのサポート

あるユーザーが複数デバイスを利用している場合について、確立した考え方はまだありません。TOFUモデルで複数デバイスを扱おうとするととたんに複雑度が上がります。例えば、自分が使うデバイス、友人が使うデバイスのそれぞれでIDキーが変わり、公開鍵の検証が必要になり、セッションの管理が必要になります。

#### TOFUより優れたセキュリティ保証

TOFUモデルは最初に確認した公開鍵を信頼するということを基盤としており、ユーザーのほとんどは公開鍵が一致しているかどうかを後から検証しません。この問題を解決しようとする取り組みの1つとして、Googleが提唱する鍵の透明性（Key Transparency）があります。しかし、Googleが公開していたリポジトリは2024年10月にアーカイブされてしまっています。


## 参考情報

https://signal.org/docs/specifications/

https://github.com/RubyCrypto/rbnacl

https://doc.libsodium.org/

https://github.com/ts-3156/x3dh-ruby

https://github.com/ts-3156/doubleratchet-ruby/

https://qiita.com/ts-3156/items/73f177af97c71cf9a87e

https://qiita.com/ts-3156/items/9ef770017ca88345df26