Macでgit-cryptとGPGを導入する時の覚え書き

Gitリポジトリを作ってgit-cryptのセットアップをする際、手順を忘れがちなのでメモに残しておく。

git-cryptとは

git-cryptというのは、Gitリポジトリ内のファイルを暗号化する為のGitプラグインであり、Gitのclean/smudgeフィルターを活用して、.gitattributesに指定したファイルをgit add時に暗号化、git checkout時には復号化といった具合で、透過的に暗号処理を施してくれるツール。

個人的に、Terraformを使ってインフラ構築する事がよくあるのだが、パスワード類や各種APIキー等の秘匿情報を、変数ファイル等にハードコーディングせざるを得ないケースがある為、そういったファイルをGit上で安全に管理するのに使っている。

git-cryptは暗号処理にAES-256のCTRモードが、認証と改竄検知にはHMAC-SHA1が使われており、これらの処理はOpenSSLに依存している。

また、暗号処理に使う対称鍵を安全に共有する手段として、GPGを利用出来る仕様にもなっている為、別途でGPGもインストールしておくと良い。

なお、類似ツールにgit-secretとか、暗号アルゴリズムを自分で選択可能にしたtranscryptというのもあるが、これらはWindowsをサポートしていない。

開発環境をMacLinuxに限定出来るのであれば、より強度の高い暗号アルゴリズムを設定出来るtranscryptを使う方が良いかと思う。

GPG(GNU Privacy Guard, GnuPG)とは

GPGについても軽く触れておくと、公開鍵暗号方式を利用して署名や暗号化したメールの送受信の実現を目的とする、Pretty Good Privacyの公開された標準仕様となるOpenPGPの実装であり、暗号鍵の生成とそれを使ったデータの暗号処理や署名、暗号通信を行うもの同士の公開鍵の束をローカルや公開鍵サーバーで共有&管理する為のツール群である。

コマンドラインで手軽に利用する事が出来て、一般的にはメールの暗号化であったり、Linux等の署名付きパッケージの真正性確認、Gitの署名付きコミットや、Zoomが買収したSNSのIDと暗号鍵を公にマッピングするKeybaseといったサービスで使ったりする。

git-cryptとGPGのインストール

Macの場合、git-cryptもGPGもHomebrewにパッケージがあるので、以下のコマンドを実行すればインストール出来る。

$ brew install git-crypt gnupg

対称鍵の共有にGPGを使わないのであれば、gnupg自体は不要なのだが、より安全な形での運用を目指すのであれば、インストールしておいた方が望ましい。

なお、おまけのツールとして、pinentry-macという、パスフレーズやPINコードをダイアログ入力形式で行える様にする為のツールであるpinentryMac拡張についても紹介しておく。

これを使うと、GPGの鍵に設定するパスフレーズの入力を、GUIのダイアログで行える様になるのだが、別に無くともCUIの画面全体がパスフレーズ入力画面に切り替わるので、必ずしも要る訳では無いものの、入力が分かり易くなるので、好みによって入れておくのも有りかと。

$ brew install pinentry-mac

インストールが完了したら、gpg-agentの設定ファイルとなる「~/.gnupg/gpg-agent.conf」に、pinentry-macを使用する為の下記設定を追記する。

$ echo "pinentry-program /usr/local/bin/pinentry-mac" >> ~/.gnupg/gpg-agent.conf

これで準備はOK。

git-cryptの使い方

ここからは、git-cryptの利用方法について記載する。

まず、導入にあたり、git-cryptを利用したいGitリポジトリのルートディレクトリで、git-crypt initコマンドを実行する。

$ git-crypt init
Generating key...

すると、ローカルリポジトリである.gitディレクトリの中に、新たにgit-cryptディレクトリが作成され、ファイルの暗号処理に使う対称鍵が生成される。

.git/git-crypt/keys/default

また、.git/configファイルにはgit-crypt用のフィルターが追記され、これにより、透過的な暗号処理が実行される様になる。

[filter "git-crypt"]
    smudge = \"git-crypt\" smudge
    clean = \"git-crypt\" clean
    required = true
[diff "git-crypt"]
    textconv = \"git-crypt\" diff

暗号対象のファイルは.gitattributesを作成して、以下の様に指定してやれば良い。

*.txt filter=git-crypt diff=git-crypt

この場合だと、リポジトリ内に存在するtxt拡張子のファイルが全て暗号対象となる。

対称鍵の共有

git-cryptを個人利用するのであれば、ここまででOKなのだが、複数人で利用する場合となると、この暗号処理に使う対称鍵をメンバー間で共有する必要が出てくる。

そこで、まずはgit-cryptの導入者が、git-crypt export-keyコマンドを使用して、ローカルリポジトリにある対称鍵をファイルにエクスポートする。

$ git-crypt export-key ファイル名

このエクスポートした対称鍵を、何らかの安全な方法で共同作業するメンバーに受け渡す。

対称鍵を受け取ったメンバーは、git-cryptをインストールした後、暗号処理を施しているGitリポジトリをクローンして、リポジトリのルートディレクトリで以下のコマンドを実行する。

$ git-crypt unlock 対称鍵のファイルパス

すると、git-cryptは.git/git-crypt/keysディレクトリにこの対象鍵を配置し、.git/configにgit-cryptフィルターの設定を追記した上で、暗号化されているファイル群の復号処理を行う。

以後は各メンバーの環境でも、git-cryptによる透過的な暗号処理が行われる様になる。

この対称鍵の共有手段として、後述するGPGを利用する方法があり、セキュリティを考慮するのであればそちらが推奨されるのだが、CircleCIやGitHub Actions等でCI/CDを実施する場合等では、CI/CDの実行環境でGPGを使うのが手間だったりする為、対称鍵をBase64エンコードして環境変数に設定し、この方法を使っている。

GPGで安全に対称鍵を管理する

git-cryptの対称鍵をGPGを使ってより安全に共有する場合、まず、メンバー全員がgit-cryptに加えてgnupgのインストールも行い、gpgコマンドで公開鍵暗号方式のキーペアを作成する。

キーペアを作成する為のコマンドオプションは幾つかあるのだが、ここでは鍵の仕様を引数で指定する事が可能となっている--quick-generate-keyオプションを使う。

$ gpg --quick-generate-key "ユーザー名 <メールアドレス>" アルゴリズム 用途 有効期限

[実行例]
$ gpg --quick-generate-key "heroween <heroween@example.com>" default default 0

執筆時点のgnupgのバージョン2.3.7では、例示しているコマンドの様に第二引数と第三引数を「default」にすると、主鍵と副鍵それぞれのキーペアが作成され(第二引数が「default」以外の場合は主鍵のみ作成される)、アルゴリズムは主鍵が「ed25519」で副鍵が「cv25519」、用途は主鍵が「署名と証明」で副鍵が「暗号」となり、第四引数に「0」を指定しているので、各鍵の有効期限が無期限になる。

コマンドを実行したら、パスフレーズの入力画面に表示が切り替わるので、確認を含めて2回の入力を行うとキーペアの生成が完了(秘密鍵を使う際にはこのパスフレーズが必要となるので、忘れずに控えておく)。

gpg --list-keysコマンド(公開鍵の表示)やgpg --list-secret-keysコマンド(秘密鍵の表示)を実行すると、ローカルの鍵束に保存された鍵を確認する事が出来る。

$ gpg --list-keys
-------------------------------
pub   ed25519 2022-08-23 [SC]
      25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A
uid           [  究極  ] heroween <heroween@example.com>
sub   cv25519 2022-08-23 [E]

$ gpg --list-secret-keys
-------------------------------
sec   ed25519 2022-08-23 [SC]
      25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A
uid           [  究極  ] heroween <heroween@example.com>
ssb   cv25519 2022-08-23 [E]

メンバー各自の鍵が生成出来たら、以下のコマンドを実行してそれぞれの公開鍵をエクスポートしてもらい、その公開鍵をgit-cryptの導入者に受け渡す。

$ gpg -o ファイル名 --export 鍵ID

[実行例]
$ gpg -o heroween.public.gpg --export 25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A

指定する鍵IDについては、ユーザー名やメールアドレスでも代替が可能(上述の例だと、「heroween」や「heroween@example.com」となり、もしユーザー名にスペースが含まれる場合であればクォートで囲えばOK、以降の例示ではユーザー名を使用する)。

ちなみに、標準だと公開鍵はバイナリー形式でエクスポートされるが、--armorオプションを加えればASCII Armorでエクスポートする事も可能。

エクスポートされた公開鍵をメンバーから受け取ったgit-cryptの導入者は、それらをまず自身の鍵束にインポートする。

$ gpg --import ファイル名

[実行例]
$ gpg --import heroween.public.gpg
gpg: 鍵1B2A9FEAD1E71D4A: 公開鍵"heroween <heroween@example.com>"をインポートしました
gpg:           処理数の合計: 1
gpg:             インポート: 1

インポートした公開鍵は信用が「不明」になっている。

$ gpg --list-keys
-------------------------------
pub   ed25519 2022-08-23 [SC]
      25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A
uid           [  不明  ] heroween <heroween@example.com>
sub   cv25519 2022-08-23 [E]

gpg --edit-keyコマンドを使用すると、gpgのプロンプトが展開し、対話式に鍵の設定を変更する事が出来るので、trustコマンドで適切な信用を付与しておく。

$ gpg --edit-key 鍵ID

[実行例]
$ gpg --edit-key heroween
gpg (GnuPG) 2.3.7; Copyright (C) 2021 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

pub  ed25519/1B2A9FEAD1E71D4A
     作成: 2022-08-23  有効期限: 無期限      利用法: SC
     信用: 不明の     有効性: 不明の
sub  cv25519/E625DB7981E23CFC
     作成: 2022-08-23  有効期限: 無期限      利用法: E
[  不明  ] (1). heroween <heroween@example.com>

gpg> trust
pub  ed25519/1B2A9FEAD1E71D4A
     作成: 2022-08-23  有効期限: 無期限      利用法: SC
     信用: 不明の     有効性: 不明の
sub  cv25519/E625DB7981E23CFC
     作成: 2022-08-23  有効期限: 無期限      利用法: E
[  不明  ] (1). heroween <heroween@example.com>

他のユーザの鍵を正しく検証するために、このユーザの信用度を決めてください
(パスポートを見せてもらったり、他から得たフィンガープリントを検査したり、などなど)

  1 = 知らない、または何とも言えない
  2 = 信用し ない
  3 = まぁまぁ信用する
  4 = 充分に信用する
  5 = 究極的に信用する
  m = メーン・メニューに戻る

あなたの決定は? 5
本当にこの鍵を究極的に信用しますか? (y/N) y

pub  ed25519/1B2A9FEAD1E71D4A
     作成: 2022-08-23  有効期限: 無期限      利用法: SC
     信用: 究極        有効性: 不明の
sub  cv25519/E625DB7981E23CFC
     作成: 2022-08-23  有効期限: 無期限      利用法: E
[  不明  ] (1). heroween <heroween@example.com>
プログラムを再起動するまで、表示された鍵の有効性は正しくないかもしれない、
ということを念頭においてください。

gpg> quit

上述の場合、自身で発行した公開鍵をエクスポートしておいて、一旦、削除してからインポートし直しているので、信用に「5 (究極)」を設定している。

再度、gpg --list-keysコマンドを実行すると、信頼が「不明」から「究極」に更新されている事が確認出来る。

$ gpg --list-keys                                                                                                                                                                           -------------------------------
pub   ed25519 2022-08-23 [SC]
      25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A
uid           [  究極  ] heroween <heroween@example.com>
sub   cv25519 2022-08-23 [E]

他人から受け取った公開鍵の場合は、適宜、署名の検証等を行って信頼を設定するが、その辺りの詳細は割愛。

こうして公開鍵をインポートし終えたら、git-cryptの導入者は、暗号処理対象のGitリポジトリをクローンし、リポジトリのルートディレクトリでgit-crypt initコマンドを実行する。

次に、インポートしたメンバー各自の公開鍵ID(自身のも含む)を、git-cryptにcollaboratorとして登録してやる。

$ git-crypt add-gpg-user 鍵ID

[実行例]
$ git-crypt add-gpg-user heroween
[main (root-commit) 7bd9435] Add 1 git-crypt collaborator
 2 files changed, 4 insertions(+)
 create mode 100644 .git-crypt/.gitattributes
 create mode 100644 .git-crypt/keys/default/0/25ABCE8FFDC20D3FE5272C7B1B2A9FEAD1E71D4A.gpg

すると、出力されるメッセージの通り、リポジトリ内に.git-cryptディレクトリが新規作成されて、配下に.gitattributes(初回のみ作成され、以後の編集は不可)と、登録したGPGの公開鍵を使って暗号化された対称鍵が配置される。

git-crypt add-gpg-userコマンドをメンバーの分だけ繰り返すと、.git-crypt/keys/default/0ディレクトリの配下には、メンバーそれぞれの公開鍵で暗号化された対称鍵が追加されていくので、それらの追加が完了したら、この.git-cryptディレクトリをリポジトリにコミットしてやる(.gitignore対称にしてはいけない)。

git-cryptの導入者によって.git-cryptディレクトリがコミットされたら、メンバーは各々の環境に対象のリポジトリをクローンし、以下のコマンドを実行する。

$ git-crypt unlock

コマンド実行したら、GPGの鍵を作成時に設定したパスフレーズを問われるので、そのパスフレーズを入力してやると、git-cryptがGPGの秘密鍵を使って対称鍵を復号して.git/git-crypt/keysディレクトリに配置し、.git/configにgit-cryptフィルターの設定を追記した上で、暗号化されているファイル群の復号処理を行い、以後は各メンバーの環境でも、git-cryptによる透過的な暗号処理が行われる様になる訳である。

git-cryptの対称鍵自体を暗号化してGitリポジトリ上で管理する形になるので、対称鍵を直に受け渡しするよりは安全で、かつ紛失する可能性も低くなる為、git-cryptを使用する場合は、基本的にこちらの方法が推奨される。

GPGのキーペアを共有する場合

クローズドな環境においては、メンバー間で個人に依存しない鍵を使い回す事もあるかと思うので、メンバー各自でGPGのキーペアを作成はせずに、git-cryptの導入者が作成したキーペアをメンバー間で共有する場合についても書いておく。

まずは、git-cryptの導入者が生成したGPGのキーペアをエクスポートするのだが、公開鍵のエクスポートについては既述している通り。

一方で、秘密鍵のエクスポートには、公開鍵のエクスポートとは異なる--export-secret-keysオプションを使う。

$ gpg -o ファイル名 --export-secret-keys 鍵ID

[実行例]
$ gpg -o heroween.private.gpg --export-secret-keys heroween

公開鍵の時とは異なり、秘密鍵のエクスポートコマンドを実行すると、キーペア作成時に設定したパスフレーズの入力を求められるので、控えていたパスフレーズを入力するとエクスポートが完了。

キーペアのエクスポートを完了したら、何らかの安全な方法でメンバーにキーペアを受け渡し、受け取ったメンバーはそのキーペアを自身のGPGの鍵束にインポートしてやる。

エクスポート時とは違って紛らわしいのだが、秘密鍵のインポートには公開鍵と同じ--importオプションを使う。

$ gpg --import ファイル名

[実行例]
 gpg --import heroween.private.gpg                                                                                                                                                         16:24:46
gpg: 鍵1B2A9FEAD1E71D4A:"heroween <heroween@example.com>"変更なし
gpg: warning: lower 3 bits of the secret key are not cleared
gpg: 鍵1B2A9FEAD1E71D4A: 秘密鍵をインポートしました
gpg:           処理数の合計: 1
gpg:               変更なし: 1
gpg:       秘密鍵の読み込み: 1
gpg:     秘密鍵のインポート: 1

これも、エクスポート時と同様に、コマンド実行をしたらパスフレーズを求められるので、メンバーはgit-cryptの導入者からパスワードを共有してもらい、それを入力する。

キーペアを鍵束にインポートしたメンバーは、既述の鍵の信用設定を行い、対象となるgit-cryptを使っているGitリポジトリをクローンし、リポジトリのルートディレクトリで以下のコマンドを実行。

$ git-crypt unlock

ここでもパスフレーズを求められるので、git-cryptの導入者が設定したパスフレーズを入力すれば、各自の環境におけるgit-cryptの設定が完了する。

このGPGのキーペア自体を共有する場合だと、結局、秘密鍵パスフレーズを共有しなければならないので、対称鍵を直に共有するのと安全度合い的にはあまり変わりが無い様な感じもするかもしれないが、暗号化した対称鍵をGitリポジトリで管理するという点が異なるので、対称鍵を紛失するリスクを減らせるという点では、こちらの方が安全ではある。