技術フォーマット

Azivaultリポジトリデザイン

リポジトリルート

ルートには、小さなメタデータファイル、SQLiteカタログ、パスフリーのPhotosスナップショットインデックス、暗号化されたブロブオブジェクト、パスフリーの実行履歴、一時的な実行状態が含まれています。S3互換ストレージは、ローカルディレクトリレイアウトと同じキーを使用します。

repo.json
plans.json
catalog.sqlite
photos/index.json
blobs/<first-two-hex>/<64-char-hex-blob-key>
history/runs/v1/<encoded-plan-id>/<yyyy>/<mm>/<yyyy-mm-dd>.json
history/run-details/v1/<encoded-plan-id>/<encoded-run-uuid>/manifest.json
history/run-details/v1/<encoded-plan-id>/<encoded-run-uuid>/chunks/<chunk-number>.bin
run-state/

リーダーの起動

  1. repo.jsonを開きます。ファイルが欠落している場合、サポートされているリポジトリではありません。
  2. formatVersionがサポートされている書き込み可能なバージョンである必要があります。
  3. このリーダーにはminimumReaderVersion <= 3が必要です。
  4. バージョン1からバージョン3の固定版レイアウトを必要とします:catalog.sqliteblobshmac-sha256、およびblobs内のチェックポイントストレージ。
  5. 暗号化されたリポジトリが必要です。
  6. catalog.sqliteを作成したり移行したりせずに開き、PRAGMA user_version = 1を要求します。

バージョン1とバージョン2のリポジトリは引き続き読み取れます。写真プランにはバージョン2が必要です。フォルダーバックアップは、ファイルシステムメタデータまたはシンボリックリンクエントリを書き込む前に、以前のリポジトリをバージョン3にアップグレードし、その後minimumReaderVersionを上げることで、古いリーダーがリポジトリを安全に拒否するようにします。

repo.json

repo.jsonはカタログデータより先に書き込まれ、すべての読み取りツールに対する互換性と暗号方式の判定基準になります。

{
  "createdAt": "2026-05-21T20:00:00Z",
  "formatVersion": 3,
  "layout": {
    "blobKeyAlgorithm": "hmac-sha256",
    "blobsPath": "blobs",
    "catalogPath": "catalog.sqlite",
    "checkpointStorage": "blobs"
  },
  "encryption": {
    "blobAlgorithm": "AES-256-GCM",
    "compressionAlgorithm": "LZFSE",
    "keyDerivationAlgorithm": "HKDF-SHA256",
    "recoveryWrappedKey": {
      "algorithm": "AES-256-GCM",
      "iterations": 210000,
      "kdf": "PBKDF2-HMAC-SHA256",
      "salt": "base64",
      "wrappedKey": "base64"
    },
    "version": 1
  },
  "minimumReaderVersion": 3
}

キー素材

リポジトリルートキーは32バイトのランダムなバイトです。パスワード復元には、recoveryWrappedKeysaltiterationsを使用して、PBKDF2-HMAC-SHA256で32バイトのラッピングキーを生成し、次にAES-256-GCMのwrappedKeyを開きます。その結果がリポジトリルートキーです。

saltazivault.repository.v1、リポジトリルートキーを入力キー材料として、32バイトの出力長さを使用してHKDF-SHA256を使用してサブキーを導出します。

次の表に、リポジトリルートキーから導出する各キーを示します。

目的HKDF情報
ブロック暗号化azivault.blob-encryption.v1
BLOBアイデンティティazivault.blob-key.v1
パス照合IDazivault.path-id.v1
パスの暗号化azivault.path-encryption.v1

ネイティブAPIの注記:Azivaultは、HKDF-SHA256、HMAC-SHA256、SHA-256、AES-GCMにはSwift CryptoKit、PBKDF2-HMAC-SHA256にはCommonCrypto、ルートキーのランダム性にはSecuritySecRandomCopyBytesを使用します。

パスのプライバシー

プレーンテキストファイル名と相対パスは、catalog.sqliteplans.json、またはリモートオブジェクトキーに暗号化されていない状態で保存されません。チェックポイントマニフェストも暗号化された状態で保存されており、ロック解除後、解暗されたチェックポイントJSONには、スナップショットを閲覧および復元するために必要な相対パスが含まれています。相対パスは、先頭と末尾のスラッシュを削除することで正規化されます。その照合IDは、正規化されたUTF-8パスに対してパスIDキーを使用して計算される小文字ハックスキーのHMAC-SHA256です。

表示パスは、パス暗号化キーを使用して結合されたAES-GCMペイロードです。結合された表現は、ノンス、暗号化されたデータ、タグです。クライアントは、暗号化されたパスを閲覧、検索、復元、または検証するために、リポジトリキーが必要です。

プランズ.json

plans.jsonはインポート用の索引であり、復元時の正本ではありません。元のアプリデータベースがなくても、バックアップセットとスケジュール設定を検出するために使います。plans.jsonがない場合に空の索引として扱えるのは、repo.jsonの検証後だけです。

{
  "version": 1,
  "plans": [
    {
      "createdAt": "2026-05-21T20:00:00Z",
      "destinationPathCiphertext": "base64-aes-gcm-combined-payload",
      "name": "Documents",
      "planID": "9f4c8d8b-7c2d-4e4b-8b31-fc4f2b5b8d2a",
      "settings": {
        "cloudFilePolicy": "treatAsError",
        "networkPolicy": "avoidExpensive",
        "powerPolicy": "avoidLowPower",
        "scheduleFrequency": "daily",
        "scheduleHour": 9,
        "scheduleMinute": 0,
        "scheduleMode": "automatic",
        "scheduleWeekday": 2,
        "skipExternalSymlinks": true,
        "version": 2
      },
      "sourcePathCiphertext": "base64-aes-gcm-combined-payload",
      "updatedAt": "2026-05-21T20:00:00Z"
    }
  ]
}

プラン設定には、セキュリティ範囲内のブックマーク、ローカル認証情報、復旧パスワード、キーチェーン識別子、プロバイダートークン、S3認証情報、署名済みURL、または明文のソースパスとターゲットパスが含まれてはいけません。

カタログスキーマ

カタログはPRAGMA user_version = 1を含むSQLiteです。これは復元の真実のソースです。status = 'completed'を持つrunsの行のみが復元可能です。

runs(
  id INTEGER PRIMARY KEY,
  run_uuid TEXT NOT NULL UNIQUE,
  plan_id TEXT NOT NULL,
  started_at REAL NOT NULL,
  finished_at REAL,
  source_path_ciphertext BLOB NOT NULL,
  dest_path_ciphertext BLOB NOT NULL,
  notes TEXT,
  case_insensitive INTEGER NOT NULL DEFAULT 0,
  hash_algorithm TEXT NOT NULL DEFAULT 'sha256',
  engine_version TEXT,
  source_volume_uuid TEXT,
  dest_volume_uuid TEXT,
  status TEXT CHECK(status IN ('running', 'completed', 'failed', 'cancelled')),
  files_total_count INTEGER,
  files_total_bytes INTEGER,
  files_copy_count INTEGER,
  files_replace_count INTEGER,
  files_skipped_count INTEGER,
  files_deleted_count INTEGER
)

files_current(
  plan_id TEXT NOT NULL,
  path_id TEXT NOT NULL,
  rel_path_ciphertext BLOB NOT NULL,
  rel_path_norm_ciphertext BLOB NOT NULL,
  blob_key TEXT NOT NULL,
  size_bytes INTEGER NOT NULL,
  created_at REAL,
  modified_at REAL NOT NULL,
  content_id INTEGER,
  last_seen_run_id INTEGER NOT NULL,
  deleted INTEGER NOT NULL DEFAULT 0,
  deleted_at REAL,
  PRIMARY KEY(plan_id, path_id)
)

file_events(
  id INTEGER PRIMARY KEY,
  run_id INTEGER NOT NULL,
  plan_id TEXT NOT NULL,
  path_id TEXT NOT NULL,
  rel_path_ciphertext BLOB NOT NULL,
  rel_path_norm_ciphertext BLOB NOT NULL,
  op TEXT NOT NULL,
  blob_key TEXT,
  size_bytes INTEGER,
  created_at REAL,
  modified_at REAL,
  content_id INTEGER,
  deleted INTEGER NOT NULL DEFAULT 0,
  ts REAL NOT NULL
)

checkpoints(
  plan_id TEXT NOT NULL,
  run_id INTEGER NOT NULL,
  manifest_blob_key TEXT NOT NULL,
  created_at REAL NOT NULL,
  PRIMARY KEY(plan_id, run_id)
)

ネイティブAPIの注記:アプリはSwiftのSQLite3モジュールを通じてシステムSQLiteライブラリと通信し、FoundationのDateDataFileManager、およびFileHandleプリミティブを使用してカタログ行を保存します。

ブロックキーとパス

ファイルBLOBは、BLOB識別キーを使用して、プレーンテキストファイルバイトに対して小文字のヘクサデシアル HMAC-SHA256 でコンテンツアドレス付けされます。オブジェクトパスは次のとおりです:

blobs/<first-two-hex-chars>/<64-char-hex-blob-key>

ライターはBLOBデータをステージングし、その後原子的にインストールします。リーサーは、既存の暗号化されたBLOBが正常に開いて、パスで指定されたキーに対して再計算されることを確認する必要があります。BLOBキーがカタログによって参照されているが、blobs/に存在しない場合、リポジトリは不完全です。

チェックポイントマスタもblobs/の下でライブにありますが、そのブロブキーは暗号化されたチェックポイントブロブバイトに対するSHA-256です。ファイルブロブキーは、上記の記述されたHMAC-SHA256のプレーンテキスト識別子です。

暗号化されたBLOBコンテナ

暗号化されたBLOBは、既知のサイズの BKBLOB2 ヘッダーまたは PhotoKit リソースに使用されるストリーミング BKBLOB3 ヘッダーを使用します。各ヘッダーの後に、1 つ以上のチャンクが続きます。整数はビッグエンディアンです。固定チャンクサイズは 1 MiB です。

known-size header =
  "BKBLOB2\n"                 // 8 bytes
  uint64 plaintext_size
  uint32 chunk_size            // 1048576

streaming header =
  "BKBLOB3\n"                 // 8 bytes
  uint32 chunk_size            // 1048576

chunk =
  uint32 sealed_size
  uint32 plaintext_chunk_size
  uint8 final_flag             // 0 or 1
  AES_GCM(compress_lzfse(plaintext_chunk))

AES-GCMの追加認証データは次のとおりです。

header || uint64 chunk_index || uint32 plaintext_chunk_size || uint8 final_flag

ファイルを復元するには、各チャンクをBLOB暗号化キーで開き、ここに示されているAADを使用し、LZFSEで明文を解凍し、final_flag1になるまでチャンクを追加します。BKBLOB2の場合、総バイト数がヘッダーの明文サイズと一致している必要があります。BKBLOB3は、PhotoKitがリソースをストリームする前にサイズが不明であるため、認証済みの最終チャンクで終了します。

ネイティブAPIの注記:blobの封印と開封にはCryptoKitAES.GCMとFoundationのLZFSENSData.compressed/NSData.decompressedAPIを使用します。

チェックポイント

チェックポイントマニフェストはBLOBとして保存され、checkpoints.manifest_blob_keyから参照されます。暗号化解除されたチェックポイントJSONの形状は次のとおりです:

写真のスナップショット

リポジトリ形式バージョン2では、一流のPhotosスナップショットが追加されました。リポジトリにはフォルダプラン、Photosプラン、または両方が含まれることができます。photos/index.jsonは、Photosデータベースのコピーではなく、パスのないスナップショットインデックスです。

{
  "version": 1,
  "snapshots": [
    {
      "planID": "photo-library",
      "snapshotUUID": "UUID",
      "createdAt": "2026-09-04T12:00:00Z",
      "manifestBlobKey": "64-char-hex",
      "assetCount": 12000,
      "resourceCount": 14500,
      "totalBytes": 9876543210,
      "coverage": {
        "coreMetadata": true,
        "userAlbums": true,
        "albumFolders": true,
        "titlesCaptionsAndKeywords": true
      }
    }
  ]
}

公開インデックスには、カウント、暗号化されたマニフェストBLOBキー、非機密のカバーフラグが含まれています。PhotoKit識別子、ファイル名、アルバム名、場所、タイトル、キャプション、キーワード、その他のユーザーメタデータは含まれていません。

各スナップショットは、暗号化されたルート記述子と、制限された暗号化されたアセットおよびコレクションのシャードを参照します。これらのシャードは、選択されたアセットに公開されているすべてのPhotoKitリソース、サポートされているメタデータ、ユーザーが作成したフォルダおよびアルバムの関係性、メンバーシップ、アセットの順序、および増分な連続性に使用されるPhotoKit変更トークンを保持します。

Photosのスナップショットは、選択されたリソースと暗号化されたマニフェストが永続化された後にのみ復元できます。復元すると、新しいPhotosのアセット、サポートされている日付と場所、お気に入りと非表示の状態、ユーザーが作成したアルバムの整理方法が再作成されます。カバーリングフラグは、公開されているAppleインターフェースでは再作成できないメタデータを公開します。

バージョン3ファイルシステムメタデータ

リポジトリ形式バージョン3は、フォルダスナップショットに暗号化されたメタデータBLOBと明示的なシンボリックリンクエントリタイプを提供します。メタデータには、POSIXモード、所有権ID、BSDフラグ、日付、拡張属性、ACLテキスト、シンボリックリンクターゲット、検証済みのスパースファイルホールの範囲が含まれる場合があります。

これらのフィールドは暗号化され、コンテンツアドレス付けが維持されます。復元はメタデータを保守的に適用し、部分的に再構築されたファイルを正確なコピーとして扱うのではなく、サポートされていないまたは拒否された属性を報告します。

{
  "version": 1,
  "createdAt": 1782072000.0,
  "files": [
    {
      "relPath": "Reports/Q1.pdf",
      "relPathNorm": "Reports/Q1.pdf",
      "kind": "file",
      "blobKey": "64-char-hex",
      "sizeBytes": 12345,
      "createdAt": 1782070000.0,
      "modifiedAt": 1782071000.0,
      "contentID": 123456
    }
  ]
}

マニフェストのプレーンテキストは、BLOBコンテナ内に暗号化されています。参照されているチェックポイントが破損している場合、リーダーはイベントリプレイに静かにバックアップするのではなく、大きなエラーを発生させて失敗すべきです。

復元アルゴリズム

  1. repo.jsonを検証し、リポジトリキーをアンロックします。
  2. catalog.sqliteを作成したり移行したりせずに開き、user_versionを検証します。
  3. リクエストされたプランと runs から completed の実行を選択します。
  4. その計画/実行にチェックポイントが存在する場合は、暗号化を解除し、そのマニフェストをファイルスナップショットとして使用してください。
  5. それ以外の場合、削除された行を除き、実行の直前または実行中に削除された行を除き、各 path_id の最新の file_events 行を取り出して、各パスを再構築します。
  6. ファイルエントリの場合、blobs/<prefix>/<blob_key>を読み込み、ブロブコンテナを検証して暗号化を解除し、プレーンテキストを選択された復元先の場所に書き込みます。
  7. runningfailed、またはcancelledの実行から決して復元しないでください。

歴史オブジェクト

history/runs/v1/は、バックアップ履歴とインポートUI用のコンパクトな日次JSONシャードを保存します。パス指定がなく、失敗した実行や中断された実行が含まれる場合があります。復元時のファイルメンバーシップは定義していません。

history/run-details/v1/には、実行詳細画面向けの暗号化レコードが保存されます。マニフェストにはパスを含めず、チャンクは暗号化され、解除後にはファイル名、相対パス、問題の詳細、ログ行が含まれる場合があります。これらは検索用の画面メタデータであり、復元時の正本ではありません。

ネイティブAPIの注記:S3対応のリポジトリは、ネットワークリクエストにSwiftURLSessionを使用します。Finderの復元ブラウジングは、NSFileProviderReplicatedExtensionNSFileProviderEnumerator、およびNSFileProviderSearchEnumeratorを含むAppleのFile Provider APIを使用して実装されています。

クライアントのルール

ファイルのみを復元したい場合は、この実装参照の代わりにazi CLIマニュアルを使用してください。