今すぐ修復
- ファイルをドロップ
- 修復はローカルで実行
- 結果をダウンロード
Signal Desktopはすべてのメッセージ履歴をdb.sqliteという単一のSQLiteデータベースに保存しますが、これはただのSQLiteではありません。SignalはSQLCipherを使っています。これはSQLiteのフォークで、ヘッダーを含めファイル全体をAES-256で透過的に暗号化します。そのため、ページ復旧エンジン(このツールも含めて)には高エントロピーのノイズしか見えず、ファイルを復号するまでfile is not a database(データベースではありません)と返してきます。だから壊れたSignalのデータベースを復元するのは2段階です。まず、すでにあなたの端末にある鍵でローカルに復号して平文のSQLiteファイルを作り、次にそのファイルを上にドロップすると、ツールがブラウザ内でページを走査し、まだデコードできる行をすべて使ってきれいなデータベースを組み直します。鍵もデータも、あなたのデバイスから外には出ません。
Signalのdb.sqliteは暗号化されている — 復旧の前にまず復号する
Signal Desktopのデータベースは、macOSでは~/Library/Application Support/Signal/sql/db.sqlite、Windowsでは%AppData%\Signal\sql\db.sqlite、Linuxでは~/.config/Signal/sql/db.sqliteにあります。SQLCipherはファイル全体を暗号化するので、file db.sqliteを実行しても、通常のSQLite format 3\0というシグネチャではなくランダムなバイト列が表示されます。普通のSQLiteツール — sqlite3シェル、DB Browser、あるいはこのページの復旧エンジン — にはページではなく暗号文しか見えず、鍵を推測することはできませんし、しようともしません。
64桁の16進文字からなるSQLCipher鍵は、データベースの隣のconfig.jsonにあります。古いバージョンではkeyというフィールドに平文で保存されていますが、2024年以降のSignalはElectronのsafeStorage APIを使ってそれをencryptedKeyとして包んでいます。これはmacOSのキーチェーンにある「Signal Safe Storage」という項目、WindowsのDPAPI、Linuxのgnome-libsecret / kwalletに支えられています。この包み(ラッパー)はあなたのOSアカウントに紐づいているため、鍵を取り出せるのはそれを作成した同じマシン・同じユーザーだけです。
生の鍵が手に入ったら、SQLCipher対応の任意のツール(sqlcipher CLI、またはSQLCipher付きでビルドしたDB Browser for SQLite)でデータベースを開き、PRAGMA key = "x'<64桁の16進鍵>'";に続けてPRAGMA cipher_compatibility = 4;を実行してください — SignalはSQLCipher 4を採用しています。次に.recoverまたはVACUUM INTO 'plain.sqlite'で平文のコピーを書き出します。その平文ファイルこそ、ここにドロップするものです。作業は必ずバイト単位のコピーの上で行い、唯一の原本には決して触れないでください。
Signalのデータベースの中で "malformed" が意味すること
復号してしまえば、Signalのデータベースはごく普通のSQLiteファイルです。固定サイズのページ(各4096バイト)が並んだ平坦な配列です。ページ1はヘッダーとスキーマを収め、各テーブルとインデックスはB木で、その内部ページは下方の、行を保持するリーフページを指します。Signalのデータはmessages(各行はidで識別され、conversationId、sent_at、received_at、type、body列、そしてメッセージの完全なレコードを収めるjson列を持ちます)、conversations、reactions、sessions、items、そして検索に使われる全文インデックスのmessages_fts(FTS5)といったテーブルに置かれています。
破損はほとんどの場合、局所的です。書き込みの途中での停電、中断された同期、フラッシュしたと嘘をついたストレージ上の壊れたページ — B木のたった1つのポインタがデータと食い違うだけで、SQLiteはSQLITE_CORRUPTを投げます。Error: database disk image is malformed (11)(データベースのディスクイメージが不正です)です。構造を信頼できなくなった瞬間にファイル全体を拒否しますが、他のテーブルはリーフページの中で無傷のまま残っています。ファイルは実際よりもずっと悪く見えるのです。
関連するエラーfile is not a database(SQLITE_NOTADB、コード26。ファイルはデータベースではありません)には、ここでは2つの意味があります。ヘッダーの16バイトのシグネチャが壊れた(その先のページはたいてい無傷です)か、あるいは — Signalではこちらのほうがはるかに多いのですが — まだ暗号化されたままのファイルに普通のツールを向けてしまったか、のどちらかです。復号する前にこれが出るなら、それは破損ではなく暗号化が語っているのです。
ページ復旧がSignalのDBから取り戻せるもの — そして取り戻せないもの
ページ復旧は壊れたファイルをその場で直すわけではありません。SQLite自身の.recoverと同じことをします。復号されたファイルをページごとに走査し、テーブルのリーフに見えるページごとに、各セルを直接 — rowid、シリアル型、値を — 読み取り、デコードできる行をすべて、まっさらな空のデータベースに挿入し直します。壊れたページは飛ばされ、読めるページは、それらを索引していたB木が生き残っていようがいまいが、その行を差し出します。ポインタの連鎖がもう届かない行は生のスイープで見つけ出され、由来のテーブルを特定できない行は捨てられるのではなく、ページとセルで索引されたlost_and_foundテーブルに入れられます。実際のところ、壊れたのがFTSインデックスや一つの会話の木だったとしても、あなたのmessagesの行はbodyとjsonを無傷のまま取り戻せます。
クラッシュが兄弟ファイルの-walを残していた場合(WALモードは、最も新しくコミットされたトランザクションをdb.sqliteに畳み込む——チェックポイントする——前に準備します)、そこにあなたの最新のメッセージがあります。ただしこれもSQLCipherで暗号化されているので、本体ファイルと一緒に復号してください。平文のデータベースとその平文のWALがそろえば、エンジンは走査の前にコミット済みのフレームを重ね合わせ、復元が最後にコミットされたトランザクションを反映するようにします。
正直に言えば、限界は本物です。添付ファイルはデータベースの中にはありません。Signalは写真・動画・ファイルを、attachments.noindex/の下に個別に暗号化された別々のblobとして保存します。そのため、復元されたmessagesの行はポインタとメタデータを保持しますが、DBを復旧してもメディアそのものは復号されません。ファイルの末尾で物理的に上書きされたり切り詰められたりしたものはすべて失われています — SQLiteファイルには、それを組み直すための冗長性がありません。そして復元された出力は常に疑ってかかるべきです。走査はfreelistのページを含めて生のセルを読むため、削除されたメッセージが再浮上したり、値が誤ったストレージクラスで戻ってきたりすることがあります。頼りにする前に、既知の信頼できるソースと照合して検証してください。
なぜこれはあなた自身のマシンで行うべきなのか
メッセンジャーのデータベースは、自分が管理していないサーバーに最も置きたくない、まさにそのファイルです — あなたが残してきたすべての会話、すべての連絡先、すべてのグループだからです。アップロード方式の「修復」サービスは、そのファイルを、あなたが中身を検分できないインフラへ、あなたが書いたのではない保持条件のもとでコピーします。そしてSignalのデータベースでそれをやると、Signalを使う意味そのものが静かに台無しになります。
ここでは復旧はすべてあなたのブラウザのタブ内で実行されます。平文のファイルはディスクから読み込まれ、ローカルで組み直され、ダウンロードされます。ネットワーク(Network)タブを開けば、出ていくのが0バイトであることを確かめられます。同じくらい大切なのは、復号のステップもローカルにとどまることです — あなたのSQLCipher鍵はあなた自身のキーチェーン(またはconfig.json)から取り出され、あなたのマシン上のツールが使うだけで、決して送信されません。こうしてSignalのデータベースを復元するということは、データも鍵も、どの第三者にも預けないということです。
修復できるものとできないもの
修復できる
- 平文に復号したSignalのデータベース(sqlcipher、またはSQLCipher付きのDB Browserで復号したもの)が、その後 "database disk image is malformed"(データベースのディスクイメージが不正)と出る場合
- 生き残ったB木のリーフページから取り出した messages の行 — body 列と json レコード全体
- たった1つの不良ページやポインタがファイル全体を落としたときの、conversations、reactions などのテーブル
- ポインタの連鎖が届かない行を生のページスキャンで取り出す。帰属先を特定できない行は lost_and_found テーブルに入れる
- 復号した兄弟ファイル -wal からの最新メッセージを、走査の前に重ね合わせる
修復できない
- まだ暗号化されたままのdb.sqlite そのもの — まずローカルで復号してください。エンジンが復旧するのは平文のSQLiteであって、SQLCipherの暗号文ではありません
- 鍵なしのあらゆるもの — 取り出せない encryptedKey(キーチェーンを失った、別のマシンや別のユーザー)は、決して読めるSQLiteにはなりません
- メッセージの添付ファイル — これらは attachments.noindex/ の下に個別に暗号化された別ファイルとして存在し、データベースの中にはありません
- ファイルの末尾で物理的に上書き・切り詰められたメッセージ — どんなツールもそれを作り出すことはできません
- 正確さの保証 — 削除された行が再浮上したり型が変わったりすることがあります。頼りにする前に、復元したデータを検証してください
修復が失敗した場合、その理由(データの欠落か構造の破損か)をお伝えします。失敗した修復に料金が請求されることはありません。