← トップへ戻る

ちいさな金庫のファイル形式(公開仕様)

KAGI v1 / KAGIBAK v1 ・ 最終更新日: 2026年9月25日

ちいさな金庫は、写真・動画を次の形式で保存します。アプリがなくなっても、合言葉があれば中身を取り出せるように公開しています。

数値はすべてリトルエンディアンです。暗号は AES-256-GCM(ノンス 12 バイト、タグ 16 バイト)、HKDF-SHA256、HMAC-SHA256、PBKDF2-HMAC-SHA256 だけを使います。

1. 鍵

名前作り方
VK(金庫の鍵)256 bit の乱数。端末のキーチェーン(この端末だけ)に保存
ファイル鍵HKDF-SHA256(IKM = VK, salt = itemID の16バイト, info = "kagi/v1/file/<kind>", 32バイト)。kind は 1(原本)または 2(サムネイル)
カタログ鍵HKDF(VK, salt = vaultID の16バイト, info = "kagi/v1/catalog")
KCV 用の鍵HKDF(VK, salt = なし, info = "kagi/v1/kcv")
重複検知の鍵HKDF(VK, salt = なし, info = "kagi/v1/dedupe")
バックアップの MAC 鍵HKDF(VK, salt = なし, info = "kagi/v1/backup-mac")
KEK(合言葉の鍵)PBKDF2-HMAC-SHA256(UTF-8(NFKC(合言葉) の前後の空白を削ったもの), salt 16バイト, 反復回数(既定 600,000), 32バイト)

2. vault.json(金庫フォルダ。平文。秘密は含まない)

{"format":1,"vaultID":"…","createdAt":"2026-09-25T09:00:00Z","kcv":"<base64>",
 "wrap":{"alg":"pbkdf2-sha256","iterations":600000,"salt":"<base64>","wrappedKey":"<base64>"}}

控えを index/vault.json.bak に置きます。

3. 暗号化ファイル(KAGI v1)

置き場所: objects/<先頭2文字>/<itemID>.kga、thumbs/…/<itemID>.kgt

ヘッダ 64 バイト
  0  "KAGI"             4B
  4  formatVersion      u8   1
  5  kind               u8   1=原本, 2=サムネイル(長辺400px の JPEG)
  6  flags              u16  0
  8  itemID             16B  UUID のバイト列
 24  chunkSize          u32  1,048,576
 28  plaintextLength    u64
 36  chunkCount         u32  ceil(plaintextLength / chunkSize)。0バイトなら 1
 40  reserved           24B  0
メタ情報ブロック
  u32 length ‖ AES-GCM combined(ファイル鍵、AAD = ヘッダ64B ‖ "meta")
  平文は JSON: {"v":1,"itemID","mediaType":"photo|video","uti","originalFilename","captureDate","importDate",
               "pixelWidth","pixelHeight","duration","isLivePhotoStill"}(日時は 1970 年からの秒数)
チャンク i = 0 … chunkCount-1
  ノンス 12B ‖ 暗号文(最後以外は chunkSize)‖ タグ 16B
  AAD = ヘッダ64B ‖ u32 i ‖ u8 isFinal(最後のチャンクだけ 1)

ファイルの末尾に余分なバイトがあってはいけません。

4. バックアップ(KAGIBAK v1、.kagibackup)

"KAGIBAK1"(8B) ‖ u32 headerLength(64KB 以下)‖ ヘッダ JSON
  {"format":1,"vaultID":…,"createdAt":…,"kdf":{"alg":"pbkdf2-sha256","iterations":…,"salt":b64},"wrappedKey":b64}
エントリ(くり返し)
  u8 type(1=原本, 2=サムネイル, 3=カタログ)‖ 16B itemID(カタログはゼロ)‖ u64 length ‖ 中身
  原本・サムネイル: .kga / .kgt のバイト列をそのまま
  カタログ: AES-GCM(カタログ鍵(バックアップ元の vaultID), AAD = "kagi/v1/backup-catalog") で暗号化した JSON
    {"v":1,"albums":[{"id","name","createdAt","sortIndex","coverItemID","kind"}],"items":[{"id","albumID","trashedAt"}]}
終端
  u8 0xFF ‖ HMAC-SHA256(バックアップの MAC 鍵、先頭から 0xFF の直前まで)

アプリはカタログを最初のエントリとして書き、復元ではカタログが原本より前にある前提でアルバムを割り当てます(後ろにある場合、その件は「はじめのアルバム」に入ります)。

取り出しの手順(概要)

  1. .kagibackup のヘッダ JSON から salt・反復回数・wrappedKey を読み、合言葉から KEK を作って VK を取り出す(AAD は "kagi/v1/wrap|" + vaultID)。
  2. VK からバックアップの MAC 鍵を作り、末尾の HMAC を確かめる。
  3. 各エントリの .kga について、itemID からファイル鍵を作り、メタ情報ブロックとチャンクを順に復号してつなげる。