ククログ

株式会社クリアコード > ククログ > 秘密鍵やパスワードなどの機密情報をバージョン管理システムのリポジトリーに安全に置く方法

秘密鍵やパスワードなどの機密情報をバージョン管理システムのリポジトリーに安全に置く方法

プロジェクトに関わる情報の散逸を防ぐためには、情報を可能な限り一箇所に集約しておきたくなるところです。 複数人が関わるソフトウェア開発プロジェクトでは、ソースコードのGitリポジトリー、イシュートラッカーのチケットやWiki、ファイル共有サーバーなど、情報の置き場所が複数あると、人によって情報の置き場所がばらけてしまうリスクがあります。 「ファイルはすべてGitリポジトリーに集約する」と運用が定まっていれば、プロジェクトにファイルを追加するときも探すときも迷わずに済みます。

ただ、セキュリティを重視すると、「電子署名のための秘密鍵や、サービスの管理画面にアクセスするためのパスワード、関係者のプライバシーに関わる情報などのような機密情報だけは、バージョン管理システムのリポジトリーには集約できない」というジレンマが生じます。 多くのオープンソース開発プロジェクトのように、リポジトリーを公開する運用を取っている場合に、生の機密情報を格納しては駄目なのは分かりやすいでしょう。 しかし、非公開のリポジトリーであっても、「ゾーン分けによるセキュリティ1を過信してはいけない」と「分散バージョン管理システムのリポジトリーはその特性上、一度入れてしまった情報を完全に消去するのが難しい」という2つの理由から、生の機密情報は格納するべきではありません。 では、こればかりは諦めるしかないのでしょうか?

本記事ではこのジレンマの解決方法として、公開・非公開を問わず、バージョン管理システムのリポジトリーに機密情報を集約しつつ安全な状態を保つ方法を紹介します。

(なお、本記事にはCC BY-SA 4.0およびGFDLが適用されないプロプライエタリな画像として、商業書籍からの引用画像が含まれます。記事を再利用される際は当該画像を省いてお使いください。)

暗号化という解決策

端的に言えば、機密情報は暗号化してからリポジトリーに格納すればよいです。

暗号は「機密情報を、宛先以外の第三者には取り出せない形に変換(暗号化)して、誰の目に触れても大丈夫な状態で遠くに送り、受け取った相手が機密情報を取り出す」ように発達した技術です。 この「遠く」とは、物理的な距離だけでなく、時間的な距離も当てはまります。 小説や映画などで度々ある「古代の財宝の隠し場所を記した暗号を現代の主人公が解いて(復号して)財宝を手に入れる」といった話と同じです。 電子的に言えば、元の「機密情報を記したファイル」ではなく、「そのファイルを機密情報にアクセスしてよい人だけが復号できるように暗号化したファイル」をリポジトリーに格納する、ということになります。

これを実際にやるには、以下の3点に気をつける必要があります。

  • 公開鍵暗号を使う
  • 関係者それぞれに個別の鍵ペアを使う
  • 充分に安全な鍵を使う

共通鍵暗号では、暗号化された機密情報を復号するための鍵を共有しなくてはなりません。 復号に必要な鍵自体もまた機密情報なので、これでは、「鍵という機密情報を安全に共有するために暗号化して、その復号に必要な2つめの鍵を……」と、堂々巡りになってしまいます。 「暗号化には公開鍵を使い、復号には秘密鍵を使う」公開鍵暗号方式であれば、情報を取り出すために必要な機密情報(秘密鍵)は共有せずに済みます。
(画像:公開鍵暗号において秘密鍵がネットワーク越しにやり取りされない様子を解説した漫画の1コマ。「ITエンジニア1年生のためのまんがでわかるLinux シェルスクリプト応用&ネットワーク操作編」305ページより引用) 2

また、各関係者それぞれが自分用の鍵ペアを持つ前提で、機密情報は各人の公開鍵で個別に暗号化し、そのすべてをリポジトリーに入れる全員で1つの鍵ペアを使い回す、という運用にはしない)ことも必要です。
(画像:1つのファイルを各人の公開鍵で個別に暗号化してリポジトリーに格納する様子)
ぱっと見では容量的には無駄が多く見えるでしょうが、これは必要なことです。 単一の鍵ペアを関係者間で共有して、暗号化後のファイルを1つだけ格納するやり方では、「秘密鍵という機密情報をどうやって安全に共有するか?」が問題になってしまいます。

さらに、公開鍵暗号なら何でもよいわけでもありません。 力業で暗号を解かれてしまっては元も子もないので、安全のためには、力業では解けない強度の暗号にする必要があります。 ファイルを暗号化するためのツールとしてGnuPG3を使う場合だと、著名な暗号アルゴリズムにおいては、以下の強度があれば当面は安全とされています(本稿執筆時点)

暗号アルゴリズム パラメーター
RSA 鍵長3072bit
ECC(楕円曲線暗号) NIST4 P-256、Curve 255195

「暗号強度要件(アルゴリズム及び鍵長選択)に関する設定基準」というデジタル庁の2022年の資料(PDF)では、暗号強度ごとに安全に運用できる期間の試算がまとめられていて、この資料によると、これらの鍵は2040年までは安全に使用できる6と見積もられています。

具体的なやり方

鍵ペアの作成

前述のとおり、機密情報を暗号化してリポジトリーに格納する運用では、機密情報にアクセスできる人全員が各自の鍵ペアを持っている必要があります。 GnuPGの鍵ペアをまだ持っていない人や、脆弱な鍵しか持っていない人は、まず充分な強度の鍵を作るところから始めます。 ここでは、RSAより優れているとされるECCを使うことにします。 具体的な手順は以下の通りです。

  1. GnuPGをインストールします。(Ubuntuの場合、sudo apt install gpgでインストールできます。)
  2. gpg --full-generate-key --expert または gpg --full-gen-key --expert を実行し、鍵ペア作成のウィザードを開始します。
    • gpg --generate-key または gpg --gen-key でも鍵ペアは作成できますが、確実に充分な強度の鍵を作るため、ここではウィザードを使用します。 (古いバージョンのGnuPGでは、gpg --gen-key でウィザードを開始する仕様でした。)
    • --expert オプションは、次の作成する鍵ペアの種類の選択肢にECCを表示するために必要な指定です。
  3. ウィザードが開始され、作成する鍵ペアの種類を以下のように訊ねられます。
    gpg (GnuPG) 2.2.27; 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.
    
    Please select what kind of key you want:
       (1) RSA and RSA (default)
       (2) DSA and Elgamal
       (3) DSA (sign only)
       (4) RSA (sign only)
       (7) DSA (set your own capabilities)
       (8) RSA (set your own capabilities)
       (9) ECC and ECC
      (10) ECC (sign only)
      (11) ECC (set your own capabilities)
      (13) Existing key
      (14) Existing key from card
    Your selection? 
    
    今回は暗号化用のECC鍵ペアを作るので、9 と入力します。
  4. ECCの種類(パラメータ)を以下のように訊ねられます。
    Please select which elliptic curve you want:
       (1) Curve 25519
       (3) NIST P-256
       (4) NIST P-384
       (5) NIST P-521
       (6) Brainpool P-256
       (7) Brainpool P-384
       (8) Brainpool P-512
       (9) secp256k1
    Your selection?
    
    前述の通り、ECCではパラメーターがCurve 25519かNIST P-256であれば当面は安全とのことなので、Curve 25519として 1 を入力します。
  5. 作成する鍵ペアの有効期限を以下のように訊ねられます。
    Please specify how long the key should be valid.
             0 = key does not expire
          <n>  = key expires in n days
          <n>w = key expires in n weeks
          <n>m = key expires in n months
          <n>y = key expires in n years
    Key is valid for? (0)
    
    前述の通り、Curve 25519の鍵は2040年までは安全に使える見込みなので、2040から現在の年までの差を入力します。 現在が2024年の場合、2040年までは16年あるので 16y と入力します。
  6. 有効期限の具体的な日時が以下のように表示され、確認を求められます。
    Key expires at Wed Jul 25 16:28:38 2040 JST
    Is this correct? (y/N)
    
    例えば、有効期限の入力時に「年」を示すyを付け忘れると数字が「日数」と解釈されてしまいますが、そのような誤入力がなかったかをこの時点で表示される日時で確認できます。 問題なければ y と入力します。
  7. 鍵に紐付ける名前、メールアドレス、備考(コメント)の入力を以下のように求められます。
    GnuPG needs to construct a user ID to identify your key.
    
    Real name:
    Email address: (氏名の入力後に表示)
    Comment: (メールアドレスの入力後に表示)
    
    英数字・記号でそれぞれを入力します。 例えば筆者の場合は、名前はYUKI Hiroshi、メールアドレスはyuki@clear-code.com、コメントは空欄といった要領です。
  8. 入力内容の確認を以下のように求められます。
    You selected this USER-ID:
        "YUKI Hiroshi <yuki@clear-code.com>"
     
    Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit?
    
    問題無ければ o と入力します。
  9. 秘密鍵を使うときに秘密鍵の封印を解くための合い言葉として使用する、パスフレーズの入力を求められます。 確認のための再入力も含めて2回入力する必要があります。
    • 暗記する前提の場合には、今後も何度も入力することになるため、覚えられる・忘れない物でないといけません。 が、同時に、第三者が容易に想像できる物であってもいけません。
    • 覚えられる自信が無い場合には、「完全にランダムな長い文字列を自動生成させてパスフレーズに設定した上で、パスワードマネージャーに格納しておき、必要になる度にパスワードマネージャーから取り出して使う」という運用がおすすめです7
  10. 鍵ペアの生成が始まります。 生成中、鍵の強度を増すために8コンピューターに入力を与えることを以下のように求められます。
    We need to generate a lot of random bytes. It is a good idea to perform
    some other action (type on the keyboard, move the mouse, utilize the
    disks) during the prime generation; this gives the random number
    generator a better chance to gain enough entropy.
    
    作成完了のメッセージが出るまで、キー入力やマウスカーソルの移動などの操作を繰り返して下さい。
  11. 鍵ペアの作成完了を示すメッセージが以下のように出力され、鍵ペアの作成が完了します。
    gpg: key XXXXXXXXXXXXXXXX marked as ultimately trusted
    gpg: revocation certificate stored as '/home/XXXXX/.gnupg/openpgp-revocs.d/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.rev'
    public and secret key created and signed.
    
    pub   ed25519 2024-07-31 [SC] [expires: 2040-07-25]
          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    uid                      YUKI Hiroshi <yuki@clear-code.com>
    sub   cv25519 2024-07-31 [E] [expires: 2040-07-25]
    
    このときpub ed25519 2024-07-31 [SC] [expires: 2040-07-25]という行の次に出力されているランダム風の英数字列はフィンガープリント指紋)と呼ばれ、鍵の識別子として使われます。
  12. 作成した秘密鍵をバックアップ用にエクスポートします。 秘密鍵は gpg --armor --export-secret-keys <鍵のフィンガープリント> > ./<任意のファイル名>.pem という要領でファイルに保存できます。 フィンガープリントが XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX なら、gpg --armor --export-secret-keys XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX > ./yuki@clear-code.com.pem という要領です。 鍵のエクスポート時にはパスフレーズの入力を求められるので、作成時に決めたパスフレーズを入力して下さい7。 (このファイルには公開鍵の情報も含まれるので、公開鍵を別途保存する必要はありません。)
  13. エクスポートした秘密鍵を、USBメモリーに保存するなどの方法でバックアップします。 秘密鍵を喪失してしまうと自分宛に暗号化されたファイルを復号できなくなってしまうので、鍵のバックアップは安全に保管して下さい。
    • このようにバックアップした秘密鍵は、gpg --import ./yuki@clear-code.com.pem でインポートすることができます。 (エクスポート時とは異なり、インポート時にはパスフレーズの入力は要求されません。)
    • 鍵のバックアップを別のデバイスに保存し終えたら、鍵のバックアップ用ファイルはローカルPCからは必ず削除しておいてください。 ファイルがローカルに残っていると、「バックアップ用ファイル経由で秘密鍵が漏洩してしまう」事故の元になります。

公開鍵の共有

鍵ペアを作成した時点では、生成した鍵ペアはローカルPC上にのみあり、暗号化も復号もそのPCでしかできません。 「機密情報を含むファイルをリポジトリーに追加する前に、各関係者のそれぞれの公開鍵でファイルを暗号化してからリポジトリーに追加する」という運用を行うためには、公開鍵を他の関係者へ共有する(他の関係者の公開鍵も共有してもらう)必要があります。

物理媒体、利用者が限定されたサーバー、組織内に閉じたネットワークでの共有

公開鍵を安全に共有する単純な手段としては、公開鍵をファイルとしてエクスポートした上で、安全な経路で渡す方法があります。 具体的には、gpg --armor --export <鍵のフィンガープリントまたはメールアドレス> > ./<任意のファイル名>.asc という要領で公開鍵をファイルに保存できますので、このファイルを何らかの方法で渡すことになります。

最も分かりやすいのは、USBメモリーやSDカードなどの物理的な媒体にコピーして媒体ごと渡す方法です。 ただ、同じオフィスに勤務する人同士であったり、オフラインイベントに同時に参加していたりと、直接相対する機会がある場合や、それと同等の安全性で媒体を届けられる術がないと、この方法はとれません。 また、セキュリティの観点から外部持ち込みの物理媒体を使用できない場合などにも、この方法はとれません。

社内LANやVPN上のSambaサーバー、自社専用のNextcloudやGitLabインスタンス、社内Slackなど、そこにアクセスできる人が限られている場所があるならば、そこに公開鍵を置いて共有する方法もあります。 自社運用のメールサーバーがあって、自分も相手も同じメールサーバー上にメールアカウントがある場合なら、メールに公開鍵を添付して送ってもよいでしょう9

このようにして安全に公開鍵ファイルを受領できたら、gpg --import ./<受領した公開鍵ファイルの名前>.asc のようなコマンド列を実行して、公開鍵をGnuPGの鍵データベースにインポートします。

では、こういった方法をとれず、公共のネットワーク経由で公開鍵を共有しなくてはならない場合10にはどうすれば良いのでしょうか?

鍵サーバー経由での共有

インターネットのように公共のネットワーク越しで公開鍵を安全に共有する方法としては、一般的には鍵サーバーを使うのがおすすめです。

鍵サーバーとは、公開鍵を安全に共有するために運営されている11公開のサーバーです。 GnuPGでは初期状態でkeys.openpgp.orgを使うようになっており、本記事でもこちらの鍵サーバーを使う前提で説明します。 鍵サーバーごとに手順や仕様は異なりますので、別の鍵サーバーを使う場合は適宜説明を読み替えて下さい。

公開鍵を鍵サーバーに公開する手順は以下の通りです。

  1. 自分の鍵のフィンガープリントを調べます。鍵を生成したのであれば、最後に表示された以下の箇所の2行目にあるランダム風の長い英数字列がそれです。
    pub   ed25519 2024-07-31 [SC] [expires: 2040-07-25]
          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    uid                      YUKI Hiroshi <yuki@clear-code.com>
    sub   cv25519 2024-07-31 [E] [expires: 2040-07-25]
    
    以前に生成した鍵を公開する場合は、gpg --list-keysを実行してローカルの鍵データベース内の鍵一覧を出力し、自分のメールアドレスに紐付けられた鍵の情報を探して、フィンガープリントを調べて下さい。
  2. gpg --send-key <公開する鍵のフィンガープリント> を実行します。 フィンガープリントが XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX なら、gpg --send-key XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX という要領です。 この操作により、公開鍵の情報が鍵サーバーに送信されます。 鍵サーバーを明示していないため、ここでは既定値のkeys.openpgp.orgが使われます。
  3. 前項の操作を行った直後に、鍵作成時に入力したメールアドレス宛にkeys.openpgp.orgからメールが届きます。 このメールは、keys.openpgp.orgのWebサイト上でメールアドレスから公開鍵を第三者が検索できるようにするための手続きを促す物です。 メール本文中に書かれたURLをブラウザーで開き、指示に従って手続きを進めて下さい。
  4. 公開鍵の登録手続きが完了したら、「このメールアドレスに公開鍵を紐付けて公開しました」という旨を関係者に連絡します。

次は、連絡を受けた側が行うことを説明します。

他の関係者から鍵サーバーへの公開鍵登録の連絡を受けとった場合、鍵サーバーから公開鍵をインポートする必要があります。 インポートの手順は以下の通りです。

  1. 相手の鍵のフィンガープリントを調べます。 keys.openpgp.orgのWebサイトを開き、相手のメールアドレスを入力してください。 公開鍵が見付かった場合、 https://keys.openpgp.org/vks/v1/by-fingerprint/XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX のようなURLが検索結果として表示されます。 この末尾の by-fingerprint/ に続く英数字列が、相手の鍵のフィンガープリントです。
  2. gpg --recv-key <インポートする鍵のフィンガープリント> を実行します。 この操作により、公開鍵の情報がkeys.openpgp.orgのサーバーから取得されて、ローカルの鍵データベースに格納されます。

この操作を各関係者の分だけ実施し、機密情報にアクセスしてよい人全員分の公開鍵がローカルの鍵データベースに存在する状態になれば、準備は完了です。

インポート後の鍵を信頼する

ここまでに紹介したような安全な方法で相手の公開鍵を自分のローカルの鍵データベースにインポートできたら、インポートした公開鍵を信頼する12ための署名手続きを行います。

インポートした公開鍵は、そのままだと出所不明・真偽不明の公開鍵として扱われるため、ファイルの暗号化の際に都度警告が行われてしまいます。 自分の秘密鍵で電子署名して「信頼済み」の状態にしておけば、そのような警告が行われなくなります。

公開鍵へ電子署名するためには、gpg --sign-key <インポートした公開鍵のフィンガープリント> というコマンド列を実行します。 すると、以下のように、公開鍵を信頼して署名するかどうかを訊ねられます。

pub  XXXXXXX/XXXXXXXXXXXXXXXX
    created: 2024-07-31  expires: 2040-07-25  usage: SC
    trust: unknown       validity: unknown
sub  XXXXXXX/XXXXXXXXXXXXXXXX
    created: 2024-07-31  expires: 2040-07-25  usage: E
[ unknown] (1). Alice <alice@example.com>


pub  XXXXXXX/XXXXXXXXXXXXXXXX
    created: 2024-07-31  expires: 2040-07-25  usage: SC
    trust: unknown       validity: unknown
Primary key fingerprint: XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX

    Alice <alice@example.com>

Are you sure that you want to sign this key with your
key "YUKI Hiroshi <yuki@clear-code.com>" (XXXXXXXXXXXXXXXX)

Really sign? (y/N)

yを入力すると、自分の秘密鍵を使うためのパスフレーズの入力を求められます7。 パスフレーズが正しければ、電子署名が行われ、以後はその公開鍵は信頼済みとして扱われるようになります。

機密情報を暗号化してリポジトリーに格納

以上の準備が整ったら、実際に機密情報をリポジトリーに格納します。 このときの状況は以下の通りと仮定します。

  • 自分(yuki@clear-code.com)、Aliceさん(alice@example.com)、Bobさん(bob@example.com)の3人が機密情報にアクセスできるものとして、自分を含む3人分の公開鍵の情報が鍵データベースに揃っている。
  • 機密情報を格納したいGitリポジトリーを ~/work/project にclone済みである。

これを踏まえ、手順を以下に説明します。

  1. 機密情報が含まれているファイルをリポジトリーの外に用意します。 ここでは、パスワードが書かれたテキストファイル ~/tmp/password.txt を機密情報として扱うことにします。 過去の記事では、パッケージの電子署名用の秘密鍵が機密情報となっています。
    • このファイルは絶対にリポジトリーに git add しないようにしてください。 git add の後 git commitgit push まで行ってしまった場合、その機密情報はもう流出したものとして扱う必要があります。 このような事態の発生を避けるために、暗号化の作業はリポジトリーとは別の作業ディレクトリーで行うのが安全です。
  2. ファイルを各関係者の公開鍵で暗号化します。 暗号化は gpg --encrypt --armor --recipient <公開鍵の持ち主のメールアドレス> --output <元のファイル名>.<公開鍵の持ち主のメールアドレス>.asc <元のファイル> のような要領で行います13。 今回は関係者が自分を含めて3人いるので、暗号化の操作も3回実行することになります。
    • Aliceさん用の暗号化:gpg --encrypt --armor --recipient alice@example.com --output ~/work/project/secret/password.alice@example.com.asc ~/tmp/password.txt
    • Bobさん用の暗号化:gpg --encrypt --armor --recipient bob@example.com --output ~/work/project/secret/password.bob@example.com.asc ~/tmp/password.txt
    • 自分用の暗号化:gpg --encrypt --armor --recipient yuki@clear-code.com --output ~/work/project/secret/password.yuki@clear-code.com.asc ~/tmp/password.txt このとき、自分用にも暗号化するのを忘れないでください
    • ファイルを暗号化しようとしたとき、以下のような警告が出力されて実施の確認を求められる場合があります。
      gpg: XXXXXXXXXXXXXXXX: There is no assurance this key belongs to the named user
      
      sub  XXXXXXX/XXXXXXXXXXXXXXXX 20XX-XX-XX Alice <alice@example.com>
       Primary key fingerprint: XXXX XXXX XXXX XXXX XXXX  XXXX XXXX XXXX XXXX XXXX
      
      It is NOT certain that the key belongs to the person named
      in the user ID.  If you *really* know what you are doing,
      you may answer the next question with yes.
      
      Use this key anyway? (y/N)
      
      これは、インポートした公開鍵がまだ電子署名されておらず、その公開鍵が本当にその人の物かどうかを検証できない場合に、その人宛として暗号化して本当によいかどうかを確認するものです。 毎回確認を求められるのは煩わしいので、インポートした公開鍵には gpg --sign-key <インポートした公開鍵のフィンガープリント> で電子署名して信頼済みとして扱うようにしておきましょう
  3. git add password.*.asc として、各関係者分の暗号化済みのファイルすべてをリポジトリーに追加します。 続けてgit commitgit pushとすれば、「公開のリポジトリーに機密情報を安全に置いた」状態となります。
    • この例であれば、password.txt という機密情報ファイルを1つ共有するために、リポジトリーにはファイルを3つ追加することになります。
  4. 最後に、生の機密情報が含まれているファイルを削除します。 ここでは、パスワードが書かれたテキストファイル ~/tmp/password.txt が該当します。

ここでは各関係者向けの暗号化をすべて手作業で行っていますが、機密情報が度々更新される場合には、一連の手順をシェルスクリプトなどにしておいて簡単に実施できるようにしておくと良いでしょう。

繰り返しになりますが、自分用にもファイルを暗号化してリポジトリーに追加するのを忘れないで下さい。 何らかの事故などで機密情報を記したファイルを紛失してしまった場合には、自分も暗号化済みのファイルから再度情報を取り出すことになるからです。

機密情報の取り出し

今度は、リポジトリーに含める形で共有された暗号化済みのファイルから、元の機密情報を取り出す手順です。

  1. 機密情報を暗号化したファイルのうち、自分の公開鍵が使われた物を用意します。 先の例の通りの手順で password.txt が暗号化されてリポジトリーに追加されている場合、ファイルは password.yuki@clear-code.com.asc となります。
  2. ファイルの内容を自分の秘密鍵で復号します。 復号は gpg --decrypt <復号するファイルの名前> という要領で行います。 ここでは gpg --decrypt password.yuki@clear-code.com.asc が実際のコマンド列になります。
    • このとき、秘密鍵のパスフレーズの入力を求められるため、鍵ペア作成時に決めたパスフレーズを入力します7。 パスフレーズが正しければ、秘密鍵の封印が解かれて復号処理が行われ、復号結果が標準出力に出力されます(ファイルとしては保存されません)

取り出し後の機密情報の取り扱い

gpgコマンドは、復号結果をファイルとしては保存せずに、標準出力に出力します。 共有された機密情報の種類・形態によって、この後どう取り扱うべきかが変わってくるので、代表的なケースを挙げて紹介します。

パスワードの類

パスワードのように「認証時にキーボード操作で入力する短い文字列」を受け取った場合、「必要なときに毎回gpgコマンドを実行して復号結果を参照する」というのは、あまりお勧めできません。 というのも、このようにして取り出した平文のパスワードを使うためにはクリップボード経由で目的のアプリケーションに貼り付ける必要がありますが、平文のパスワードをクリップボード経由で悪意あるソフトウェアに読み取られてしまう恐れや、全く別の文脈の投稿フォームなどに誤貼り付け・誤送信してしまったりする恐れがあるからです。

復号後の機密情報がパスワードのように数十文字程度に収まる充分に短い物である場合には、利便性や安全性の面で、それをパスワードマネージャーに改めて格納し、以後は一貫してそちらで管理する平文のパスワードが書かれたファイルをローカルに残さない)のがお勧めです。

  • パスワードマネージャーの中には「パスワードをクリップボードにコピーした後、一定時間経過後に自動的にクリップボードの内容を消去する」といった機能を持っている物もあり、外部からの読み取りや、投稿フォームへの誤貼り付け・誤送信などのトラブルを防ぎやすくなります。
  • Webブラウザーに統合されたパスワードマネージャーの場合、認証が必要なWebページのフォームにパスワードを自動入力させることができ、別の欄への誤貼り付けのようなトラブルの発生を抑止できます。

GnuPGの秘密鍵の類

apt/yumリポジトリー用のパッケージの電子署名用GnuPG秘密鍵を共有する事例のように、GnuPGの秘密鍵を暗号化された状態で受け取った場合には、受け取った秘密鍵を復号してGnuPGの鍵データベースにインポートする必要があります。

受け取った秘密鍵は、自分でバックアップ用にエクスポートした物と同じ要領でインポートできます。 先の説明ではインポート対象の鍵を gpg --import ./yuki@clear-code.com.pem とファイル名で指定していました。 それを踏まえると、gpgコマンドは復号結果を標準出力に出力するため、復号結果をリダイレクトでファイルに保存した上で、改めてファイル名を指定してインポートしなくてはいけないように思えるでしょう。

ですが、この場合も、実は復号結果をファイルに保存する必要はありません。 gpgコマンドは、鍵のファイル名の代わりに - と指定すると、ファイルからではなく標準入力からインポート対象の鍵を受け取るように動作します。 そのため、gpg --decrypt <復号する秘密鍵ファイルの名前> | gpg --batch --import - という要領でコマンド列を実行すれば、暗号化された秘密鍵の復号とインポートを同時に行えるわけです14。 受け取った暗号化済みファイルの名前が secret-key.yuki@clear-code.com.asc であれば、 gpg --decrypt secret-key.yuki@clear-code.com.asc | gpg --batch --import - といった要領です。

復号して取り出した機密情報がファイルとして存在していると、誤操作でリポジトリーに git add してしまったり、誤操作でファイルをアップロードしてしまったり、あるいはバックグラウンドで動作するソフトウェアにファイルを収集されてしまったりと、漏洩のリスクが色々と生じます。 復号結果をファイルに保存せずに目的を達成できる方法があるなら、なるべくそうすることで漏洩リスクを低減できます。 とにかく、「無防備な状態で機密情報のファイルが置かれている時間」を生じさせないことが肝要です。

ファイルの形で使う必要がある機密情報

とはいうものの、使用するソフトウェアやサービスの都合により、復号後の平文の機密情報をファイルの形態で用意しなくてはならない場合もあります。 例えば、Google ChromeやMicrosoft Edgeでは、拡張機能の開発時に開発者向けのパッケージを作成する際、.pem という拡張子を持つ秘密鍵ファイルを都度指定しなくてはなりません。

前項でも述べましたが、gpgコマンドは復号結果を標準出力に出力するため、復号時には gpg --decrypt <復号するファイルの名前> > <取り出した後のファイルの名前> という要領でファイルに結果を保存することになります。 まずは誤commitによる漏洩のリスクを最小化するため、復号後の機密情報ファイルの出力先にはリポジトリー外のパスを指定することを強くお勧めします。 またその上で、情報漏洩の原因になるリスクを低減するために、使い終わった復号後のファイルはすぐに削除することも重要です。

こういった運用を毎回手動で行うのは煩雑ですし、ヒューマンエラーによるトラブルの元になるので、「復号を行うコマンド列を自由入力では実行せず、決まった位置に復号して、使用し、削除までするタスクを Makefile などのタスクランナーで定義しておき、機密情報のファイルが存在する期間を最小化する」といった運用の工夫が必要となります。

例えば、Chrome用拡張機能のパッケージを作成するための chrome.pem と、Edge用拡張機能のパッケージを作成するための edge.pem という秘密鍵ファイルについて、「通常時は暗号化されたまま置いておき、パッケージ作成時にのみ一時的に復号して使う」ということをするのであれば、拡張機能のパッケージングをコマンドラインで行う方法と組み合わせて、以下のような運用が考えられます。

  • USER_ID という環境変数を、GnuPGの秘密鍵を紐付けた自分のメールアドレスを値として定義しておく。 例えば、~/.profile に以下のように書いておく。

    export USER_ID=yuki@clear-code.com
    
  • 以下の内容で Makefile をリポジトリーに追加しておく。 (Chrome用拡張機能のソースは chrome ディレクトリーに、Edge用拡張機能のソースは edge ディレクトリーにあるものとします。)

    SECRET_DATA_RECIPIENTS=alice@example.com bob@example.com yuki@clear-code.com
    
    encrypt:
        mkdir -p secret
        for user_id in ${SECRET_DATA_RECIPIENTS}; do gpg --encrypt --armor --recipient $$user_id --output secret/chrome.pem.$$user_id.asc ~/tmp/chrome.pem; done
        for user_id in ${SECRET_DATA_RECIPIENTS}; do gpg --encrypt --armor --recipient $$user_id --output secret/edge.pem.$$user_id.asc ~/tmp/edge.pem; done
        git add secret/*.asc
        git commit -m 'Add or update encrypted secret keys' secret/*.asc
    
    package:
        [ '$$USER_ID' = '' ] && echo 'あなたのメールアドレスを環境変数 USER_ID で明示してから実行してください' && exit 1
        mkdir -p ~/tmp/
        gpg --decrypt secret/chrome.pem.$$USER_ID.asc > ~/tmp/chrome.pem
        gpg --decrypt secret/edge.pem.$$USER_ID.asc > ~/tmp/edge.pem
        /opt/google/chrome/google-chrome --pack-extension="$$(pwd)/chrome" --pack-extension-key=~/tmp/chrome.pem
        /opt/microsoft/msedge/msedge --pack-extension="$$(pwd)/edge" --pack-extension-key=~/tmp/edge.pem
        rm ~/tmp/chrome.pem ~/tmp/edge.pem
    
  • 秘密鍵ファイルを暗号化するときは make encrypt を実行する。

  • パッケージ作成時には make package を実行する。 (この操作により、カレントディレクトリーに chrome.crxedge.crx が作られる。)

  • プロジェクトに機密情報を扱える関係者が増えた場合、Makefile 冒頭の SECRET_DATA_RECIPIENTS にその人のアドレスを追加して make encrypt を実行する。

その他の、ある程度の期間ファイルとして保存しておく必要がある機密情報

先の例では、秘密鍵ファイルはパッケージ作成時にのみあればよいため、一時的に復号したあとすぐに削除していました。 しかし、使用するソフトウェアやサービスの都合により、復号後の平文の機密情報をファイルの形態で一定期間保存しておかなくてはならない場合があります。 例えば、商用のソフトウェアで、動作させる際に必ずライセンスキーのファイルが必要となる、といった場合が有り得ます。

この場合も前項と同様、基本的に、誤commitによる漏洩のリスクを最小化するため、復号後の機密情報ファイルの出力先にはリポジトリー外のパスを指定することを強くお勧めします。

使用するソフトウェアの都合や利便のために、復号後のファイルをリポジトリー内に置いておきたい場合には、復号後のファイルが誤って git add されないよう、.gitignore でそのファイルをリポジトリーの管理対象外にするとよいでしょう。 例えば、例に挙げたライセンスキーのファイルの拡張子が .key である場合には、以下の内容で .gitignore を作成しリポジトリー内に追加しておくと、git add ではライセンスキーのファイルがリポジトリーに追加されないようになります。

*.key

ただ、復号後のファイルの保存場所・保存名は自由に指定できるため、別名で復号したり別の場所に復号したりした物は git add できてしまいます。 これを防ぐためには、「復号を行うコマンド列を自由入力では実行せず、決まった位置に復号するタスクを Makefile などのタスクランナーで定義しておき、確実に .gitignore で除外されるようにする」といった運用の工夫が必要となります。

例えば、secret.key というファイルをリポジトリー直下の secret ディレクトリー内にファイルとして保持しておきたいが git add はさせたくない、という場面では、以下のような運用が有り得るでしょう。

  • 機密情報の暗号化済みファイルの格納先を決める。ここでは仮に、secret/secret.key.(関係者のメールアドレス).ascとする。

  • USER_ID という環境変数を、GnuPGの秘密鍵を紐付けた自分のメールアドレスを値として定義しておく。 例えば、~/.profile に以下のように書いておく。

    export USER_ID=yuki@clear-code.com
    
  • 以下の内容で Makefile をリポジトリーに追加しておく。

    SECRET_DATA_RECIPIENTS=alice@example.com bob@example.com yuki@clear-code.com
    
    encrypt: secret/secret.key
        mkdir -p secret
        for user_id in ${SECRET_DATA_RECIPIENTS}; do gpg --encrypt --armor --recipient $$USER_ID --output secret/secret.key.$$user_id.asc secret/secret.key; done
        git add secret/*.asc
        git commit -m 'Add or update encrypted secret keys' secret/*.asc
    
    decrypt:
        [ '$$USER_ID' = '' ] && echo 'あなたのメールアドレスを環境変数 USER_ID で明示してから実行してください' && exit 1
        gpg --decrypt secret/secret.key.$$USER_ID.asc > secret/secret.key
    
  • 以下の内容で .gitignore をリポジトリーに追加しておく。

    *.key
    
  • 暗号化するときは make encrypt を実行する。

  • 復号するときは make decrypt を実行する。

  • プロジェクトに機密情報を扱える関係者が増えた場合、Makefile 冒頭の SECRET_DATA_RECIPIENTS にその人のアドレスを追加して make encrypt を実行する。

関係者が減った場合

機密情報を扱ってよい権限を持つ人がプロジェクトから離脱した場合は、以下のことをする必要があります。

  • 暗号化後のファイルのうち、離脱した人の公開鍵で暗号化した物をリポジトリーから削除する。
  • 機密情報を更新する。

機密情報の更新とは、何らかのサービスへのログインパスワードであればパスワードを変更し、電子署名用の秘密鍵であれば鍵を作り直して、それらを残存メンバーの公開鍵で暗号化し直すということです。 これらの情報が古いままだと、離脱した人が引き続き古い情報でプロジェクトのコアメンバーと同じ操作をできてしまいます。

離脱後のメンバーが機密情報にアクセスできない状態にしておくことは、プロジェクトを守ることと同時に、離脱後のメンバーを守ることにもなります。 そうしておけば、もし万が一何らかのトラブルが発生しても「離脱したあの人は機密情報にアクセスできなくなっているので、トラブルとは無関係だ(その人が離脱後に何かした可能性はない)」と言えるからです。

機密情報が漏洩した場合

このようにして共有した機密情報が第三者に漏洩した場合には、以下のことをする必要があります。

  • 機密情報を更新する。
  • (秘密鍵の漏洩が原因だった場合)鍵ペアを新たに作り直して、機密情報を新しい公開鍵で暗号化し直す。
    • その際、古い公開鍵は失効させる。

機密情報の更新は言わずもがなですが、情報漏洩の原因が秘密鍵の漏洩だった場合には、以後その人向けの公開鍵で暗号化した情報はすべて第三者に筒抜けになってしまうので、鍵ペアも作り直す必要があります。 また、その際は古い鍵が悪用されないように、古い鍵を失効させ、その公開鍵を使ってはいけないことを他の人にも周知します。

鍵を失効させる手順は以下の通りです。

  1. 鍵の失効証明書を用意します。 これは通常、鍵を生成した環境の ~/.gnupg/openpgp-revocs.d/<鍵のフィンガープリント>.rev の位置に自動的に作成されています。 初期状態では安全のため無効化されていますので、テキストエディターでファイルを開き、:-----BEGIN PGP PUBLIC KEY BLOCK----- という行の行頭の : を削除して保存し、失効証明書を使用できる状態にしておきます。
    • もし失効証明書を紛失してしまっていた場合も、失効証明書は gpg --gen-revoke --output <鍵のフィンガープリント>.rev <鍵に紐付けたメールアドレス> で作成できます。
  2. 失効証明書を gpg --import <失効証明書のパス> でインポートします。
  3. インポートが完了したら、gpg --list-keys で鍵の一覧を確認します。 無事に鍵が失効されていれば、該当する鍵の情報に以下のように「revoked」と表示され、失効済みであると分かります。
    pub   XXXXXXX 20XX-XX-XX [SC] [revoked: 20XX-XX-XX]
          XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
    uid           [ revoked] YUKI Hiroshi <yuki@clear-code.com>
    
  4. gpg --send-key <失効した鍵のフィンガープリント> で、失効情報を鍵サーバーに登録します。

まとめ

以上、公開鍵暗号を使ったGnuPGでの暗号化に基づいた、機密情報を暗号化してリポジトリーに追加する手順を紹介しました。

一読して分かるとおり、この記事で解説しているやり方は複雑で、操作ミスなどのヒューマンエラーが発生する余地があるのは否めません。 冒頭でも述べているとおり、関係者だけがアクセスできる秘密の領域を用意してそこに機密情報を置くようにした方が、よっぽど簡単且つ確実に安全を保てるでしょう。

しかし、「秘密の領域」を管理するのはそれなりにコストがかかります。 個人や小規模の企業・有志のグループなどが手弁当で運用するオープンソース開発プロジェクトのように、開発以外にかかるコストを最小化したい状況においては、公開リポジトリー1つですべてをまかなえた方が都合が良いのもまた事実です15

「暗号化」という技術そのものはHTTPSでの通信などで多くの人が日常的に利用していますが、ほとんどの利用ケースは暗黙的なものとなっていて、「任意のファイルを自分や特定の誰か用の公開鍵で暗号化する」「暗号化されたファイルを秘密鍵で復号する」操作を敢えて行う場面はあまりないのが実情です。 しかし、公開鍵暗号は正しく使えば、今回紹介したように、クリティカルな情報を長期間に渡って公開の場に置いたままにできるほど安全な物です。 公開のリポジトリーで共同作業をせざるを得ない状況にあって、PPAP16などのレガシーな方法ではセキュリティが気になる人は、GnuPGの使用を是非検討してみて下さい。

  1. 「社内ネットワークの中か、外か」というように境界線を定めた上で、その境界線の内側にいるならば安全だとして防御の手を緩める、という考え方。境界防御。

  2. こちらはCC BY-SA 4.0およびGFDLが適用されないプロプライエタリな画像となります。

  3. GNU Privacy Guard。略してGPGとも呼ばれます。

  4. 米国立標準技術研究所。P-256はNISTが推奨しているECCのパラメーターの一種で、端的には「米国政府のお墨付き」と言えます。

  5. SSHの公開鍵認証などで使われるEdwards 25519(Ed25519)は電子署名用のパラメーターで、データの暗号化には使用できません。相当する暗号化用のパラメーターはCurve 25519(Cv25519)と呼ばれます。

  6. スーパーコンピューターを使ったとしても、この宇宙が消滅するまでの時間をかけても暗号を解けないほどの強度があるとされています。電子暗号で「第三者には情報を絶対に取り出せない」と言った場合、このように、力業では現実的な時間スケールでは暗号を破れないことを指しています。

  7. パスフレーズのキー入力の機会が多いと、ショルダーハッキング(キー入力の様子を肩越しに覗き見る行為)やキーロガー(常駐型のソフトウェアを使用したり、物理的にキーボードとコンピューターの間の電気信号のやり取りを盗み取ったりして、入力されたキー操作をすべて記録して機密情報を盗み取る行為)などによってパスフレーズを盗まれてしまう機会が増える、というセキュリティ上のリスクを懸念する人もいます。利便性の面でも、充分に複雑で安全なパスフレーズは覚えるのも入力するのも煩雑である、という問題もあります。こういった問題を解決する方法としては、スマートカード機能を持つデバイスに秘密鍵を格納して物理的なデバイス自体を認証要素として使う日本語での解説)、指紋認証や顔認証などの生体認証に対応したパスワードマネージャーにパスフレーズを格納させておき使用の度に生体認証する、といったやり方があります。

  8. ユーザーによる操作を乱数のシードとして使うことによって、乱雑さが増して、より予測しにくい秘密鍵が生成されます。

  9. 同じメールサーバー上のメールアカウント同士でのメール送受信は、1つのサーバー内でのデータのやり取りのみとなり、外部の第三者が介入する余地はありません。よって、メールサーバーとの通信が安全に行えているのであれば、ファイル共有サーバーでのファイル共有と実質的に同程度の安全性と言えます。その一方で、お互いに別々のメールサーバーを使っていて、公共のネットワーク越しにメールを送受信している場合には、自分のPCとメールサーバーの間・相手のPCと相手のメールサーバーとの通信が安全だったとしても、メールサーバー間でのメール配送が安全ではなく、また、なりすましも容易なため、受領したメールを本物として信用するには危険が伴います。このような場合には、電話等で通話しながらリアルタイムでメールを送受信する、フィンガープリントを事前に安全な方法で共有しておくなど、何か別の手段と組み合わせて安全を担保する必要があります。

  10. このような場合の古典的な解決策として、鍵のフィンガープリントのみを安全な方法で共有するというやり方があります。例えば、鍵のフィンガープリントを名刺に印刷してある場合なら、対面時に名刺交換をした後、実際の公開鍵をメールに添付して送信します。メールを受け取った人は、受け取った名刺に書かれているフィンガープリントと、受け取った公開鍵の実際のフィンガープリントを比較して、両者が一致するならば、鍵の授受に使用した経路が安全でもそうでなくても、「改竄されていない本物の公開鍵を、安全に受領できた」と判断できるわけです。

  11. 例えばkeys.openpgp.orgの場合には、鍵サーバーへの鍵の登録は、鍵と紐付いた電子メールアドレスでメールを送受信できる人だけが行えます。よって、「このメールアドレスでメールを送受信できるのはその人本人だけである」という前提のもとで、鍵サーバー経由で取得した公開鍵は、確かにその人の物であると判断できます。

  12. ここでの「信頼する」とは、「この公開鍵は確かにその相手宛の暗号化用の物であり、他人宛用の物ではないこと」への信頼を意味します。暗号化したメッセージの宛先である相手自身が善人か悪人かとは無関係なので、混同しないように注意しましょう。

  13. 暗号化後のファイルの名前は自由に決めて構いません。ここでは、「元のファイルが何か」「誰用の公開鍵で暗号化されたか」「暗号化済みかどうか」の3つの情報が伝わるように、このような名前付けとしてみました。

  14. ここでは、1つ目のgpgコマンドは復号のために使用し、パイプラインの後の2つ目のgpgコマンドで鍵をインポートしています。なお、インポート用のgpgコマンドに指定しているオプション --batch は、標準入力以外の情報ソースに頼らずに鍵をインポートするために必要な指定です。このオプションを指定しないと、「error sending to agent: Inappropriate ioctl for device」といったエラーが発生してインポートに失敗する場合があります。

  15. その点で、Wikiやイシュートラッカーも付いてくるGitHubやGitLab.comは有力な選択肢になります。

  16. 「Password付きZIPファイルを送ります、Passwordを送ります、Angoka(暗号化)Protocol(プロトコル)」の略。その名の通り、ZIPファイルにパスワードをかけて、そのパスワードを別のメールで送信する、という古典的な手法。共通鍵暗号の一種ではあり、「暗号アルゴリズムとしてAES-256などを選択する」「パスワードは事前に決めた物を安全な通信経路で共有しておき、メールでは授受しない」といった点に気をつければ安全に運用することはできるものの、現実にはAES-256に非対応のZIP展開ツールが多いために脆弱なアルゴリズムであるZipCryptoを使わざるを得ず、安全とは言い難いです。