シークレットと暗号化
dotweave が内蔵の age 実装でシークレット artifact を暗号化するしくみと、keys.txt を失ったときに起きることを説明します。
シークレット artifact とは
secret モードでトラッキングしている項目は、平文のままリポジトリに入ることはありません。push はファイルの内容を暗号化し、profiles/<profile>/<repoPath>.dotweave.secret に保存します。この artifact は ASCII armor で包まれているため -----BEGIN AGE ENCRYPTED FILE----- の行で始まり、git がバイナリとして扱わずに転送・diff・マージできる通常のテキストのままです。
dotweave は age の実装自体を同梱しています。packages/cli/lib/src/crypto/age/ に X25519 recipient スタンザ、ChaCha20-Poly1305 によるペイロード暗号化、bech32 の鍵文字列、ASCII armor のコーデックが揃っています。外部プロセスは呼び出さないので、secret モードを使うために age バイナリを別途インストールする必要はありません。
暗号化の対象は通常ファイルのバイト列です。シンボリックリンクには暗号化する内容ではなくリンク先しか入っていないため、secret のパスはシンボリックリンクではなく通常ファイルでなければなりません。
暗号化されるのは内容だけです。ファイル名やディレクトリ名、リポジトリ上のパスは、リポジトリを読める相手にはそのまま見えます。.ssh/config.dotweave.secret は SSH 設定をトラッキングしているという事実を依然として伝えてしまいます。パス自体が機微な場合は repoPath を上書きして、何も明かさない名前で保存してください。
identity と recipient
役割の異なる 2 種類の鍵素材があり、意図的に別の場所に置かれています。
identity は秘密鍵です。<dotweave-home>/keys.txt に 1 行 1 つの AGE-SECRET-KEY-... 文字列として並び、空行と # で始まるコメント行も許容されます。このファイルは同期ディレクトリの外にあるので git が触れることはなく、dotweave はこのファイルを含むパスをトラッキングしようとすると TARGET_OVERLAPS_IDENTITY で拒否します。
recipient は公開鍵で、manifest.jsonc の age.recipients に少なくとも 1 つ保存されます。dotweave init は identity から recipient を導出してそこに記録するため、作りたての設定でもすぐに暗号化と復号ができます。
両者の使われ方は非対称です。push は manifest に並ぶすべての recipient 向けに暗号化します。pull は keys.txt の identity をすべて読み込み、合致したもので復号するので、使える鍵が 1 つあれば足ります。複数の recipient 向けに暗号化されたファイルはそのいずれかで独立して開けるため、複数のマシンが秘密鍵を共有しなくても 1 つのリポジトリを一緒に使えます。
push は書き込む前に、保存済みの artifact を復号して平文を比較します。内容が変わっていないシークレットは新しい暗号文を生まないので、git の履歴に無駄な差分が積み上がりません。この比較は recipient の一覧を見ないため、age.recipients だけを書き換えても、内容が同じ artifact は書き直されません。
別のマシンに専用の鍵ペアを持たせるには、次の手順で進めます。
新しいマシンで、リポジトリ引数を付けずに
dotweave initを実行してください。keys.txtが生成され、導出されたage1...の recipient がローカルのmanifest.jsoncに記録されます。その recipient の値をコピーし、keys.txtの控えを dotweave ホームディレクトリの外に保管してください。すでに復号できるマシンで、共有リポジトリの
manifest.jsoncのage.recipientsに新しい recipient を追加し、暗号化し直したい.dotweave.secretの artifact を削除してからdotweave pushを実行し、結果を git でコミットしてください。新しいマシンに戻り、共有リポジトリを接続しながらそのマシンの鍵を渡してください。
dotweave init <your-repo-url> --force --key-file ~/dotweave.agekey
ファイルをシークレットとしてトラッキングする
secret は項目ごとの同期モードなので、パスをトラッキングするときに指定します。
dotweave track ~/.ssh/config --mode secret
dotweave push
同じ対象を再トラッキングすると、渡したフィールドだけが更新されます。そのため dotweave track ~/.ssh/config --mode normal で項目を secret モードから戻しても、repoPath、permission、profiles はそのまま残ります。入れ子の項目が親ディレクトリからモードを引き継ぐしくみは同期モードで説明しています。
2 台目のマシンでは、シークレットを読む前に identity が届いている必要があります。ファイルとして渡してください。
dotweave init <your-repo-url> --key-file ~/dotweave.agekey
dotweave pull
対話型のターミナルであれば、dotweave init <your-repo-url> を実行してプロンプトに鍵を貼り付けてもかまいません。鍵がない場合、recipient が登録済みのリポジトリへの接続は INIT_AGE_IDENTITY_REQUIRED で失敗します。
keys.txt をバックアップする
dotweave は keys.txt を同期しません。リポジトリにも保存されず、マシン間で運んでくれるコマンドもないので、移動は自分で行ってください。パスワードマネージャー、暗号化されたボリューム、信頼できる経路での scp などが使えます。
鍵の控えをすべて失うと、もう取り返せません。artifact は git に残りますが、dotweave も age も二度と開けません。頼れるのは identity が生きているマシンに残った平文だけです。だからこそバックアップは、dotweave が管理しない場所に置いてください。
dotweave init --force は、同期ディレクトリと設定ファイルに加えて既存の identity ファイルまで削除します。keys.txt の唯一の控えを持つマシンで実行すると鍵は失われ、その鍵だけを対象に暗号化されたシークレット artifact にもアクセスできなくなります。先に鍵を別の場所へ保存するか、同じコマンドで --key-file として鍵を渡し直してください。
dotweave doctor は、解決されたパスに identity ファイルがあるかを確認します。pull で必要になる前に、keys.txt の欠落や置き場所の間違いを見つけられます。
復号に失敗したとき
シークレット関連のエラーはおおむね 4 つのいずれかで、どれに当たったかはメッセージが示します。
Failed to decrypt a secret artifact. は、keys.txt の identity が artifact のどの recipient とも一致しないか、保存されたデータが壊れていることを意味します。運んできた鍵が age.recipients に登録された recipient の対になっているか確認してください。
No age identities were found in the configured identity file. は、keys.txt はあるものの空行と # コメントしか入っていないことを意味します。秘密鍵を追加するか、dotweave init で新しく生成してください。
Secret sync path is stored as a plain artifact in the repository. は、manifest が secret としているのに保存されたファイルが暗号化されていないことを意味します。artifact を書いたあとにモードを変えた場合がほとんどです。逆の Plain sync path is stored as a secret artifact in the repository. は、manifest が normal なのに暗号化された artifact が残っていることを意味します。どちらも、その項目を読めるマシンから push するか、古い artifact を取り除けば解消します。
実装とリファレンス
age v1 の実装は packages/cli/lib/src/crypto/age/、暗号化と復号の入り口は lib/src/lib/crypto.dart、identity のパス解決は lib/src/config/identity_file.dart にあります。artifact の比較と書き込みは lib/src/services/repo_artifacts.dart で行われます。
age.recipients フィールドをほかの設定と合わせて見るには manifest.jsonc、--key-file と --force が既存の設定に何をするかは dotweave init を参照してください。