---
title: "PCに秘密副鍵を残したままYubiKeyへGPG鍵を登録する"
url: "https://blog.101ta28.com/posts/keep-gpg-subkeys-when-moving-to-yubikey/"
language: "ja"
description: "一時的なGNUPGHOMEを使い、PC上のGPG秘密副鍵を残したまま、既存の署名、認証、暗号化副鍵をYubiKeyへ登録する。"
---

# PCに秘密副鍵を残したままYubiKeyへGPG鍵を登録する

> 一時的なGNUPGHOMEを使い、PC上のGPG秘密副鍵を残したまま、既存の署名、認証、暗号化副鍵をYubiKeyへ登録する。

`keytocard`でGPGの秘密副鍵をYubiKeyへ登録すると、操作したGnuPG環境では秘密副鍵の実体がカード参照用の情報へ置き換わる。
PCにも秘密副鍵を残すには、一時的な`GNUPGHOME`へ秘密副鍵を複製し、その環境で`keytocard`を実行すればよい。



## 作業後の鍵構成

作業前のPCには、公開主鍵と、署名、認証、暗号化に使う三つの秘密副鍵がある。
秘密主鍵はオフラインのバックアップだけに保存している。

作業後もPCの秘密副鍵は変わらず、YubiKeyにも同じ副鍵が入る。

```text
オフラインバックアップ
└─ 秘密主鍵

PC
├─ 署名用の秘密副鍵
├─ 認証用の秘密副鍵
└─ 暗号化用の秘密副鍵

YubiKey OpenPGP
├─ Signature slot      署名用の秘密副鍵
├─ Authentication slot 認証用の秘密副鍵
└─ Encryption slot     暗号化用の秘密副鍵
```

これは、PC上の秘密副鍵を失わずにYubiKeyの利用を始めるための構成である。

## `keytocard`が変更するもの

GnuPGの公式マニュアルは、`keytocard`を選択した秘密鍵をスマートカードへ転送するコマンドとして説明している。
転送に成功して`save`すると、操作中のGnuPG環境では秘密鍵の実体が**カード参照用stub**へ置き換わる。
このstubはカードのシリアル番号などを保持し、秘密鍵を使うときに対応するカードを要求する。

したがって、普段使っている`~/.gnupg`で`keytocard`を実行すると、その環境にある秘密副鍵の実体がstubへ置き換わる。
一時的な`GNUPGHOME`で実行すれば、一時環境の秘密副鍵だけがstubへ置き換わり、通常環境の秘密副鍵は残る。

GnuPG 2.1以降では、秘密鍵とstubを`gpg-agent`が`private-keys-v1.d`以下で管理する。
秘密鍵の実体が従来の秘密鍵リングだけに入っている、という説明は現在のGnuPG 2.xには当てはまらない。

## 検証対象の環境

この記事では、次の環境を前提にする。

```text
OS: Ubuntu 24.04.4 LTS
GnuPG: 2.4.4
YubiKey: YubiKey 5C NFC
YubiKey firmware: 5.7.4
OpenPGP application: 3.4
署名副鍵: Ed25519
認証副鍵: Ed25519
暗号化副鍵: Curve25519
```

YubiKey 5のOpenPGPアプリケーションが対応するアルゴリズムは、ファームウェアや製品系列によって異なる。
Yubicoの資料では、Ed25519とX25519を含む楕円曲線暗号はファームウェア5.2.3以降でサポートされている。

GnuPG 2.4.0にあったYubiKey向け`keytocard`の不具合は2.4.1で修正されている。
別のバージョンで作業する場合は、そのバージョンのリリースノートも確認する。

## 鍵とカードの事前確認

最初に、GnuPGのバージョンと現在の秘密鍵を確認する。

```bash
gpg --version
gpg --list-secret-keys \
  --keyid-format LONG \
  --with-subkey-fingerprint
```

対象の鍵は、短いキーIDではなく公開主鍵の完全なフィンガープリントで指定する。
次の変数に、空白を除いたフィンガープリントを設定する。

```bash
PRIMARY_FINGERPRINT="ここに公開主鍵の完全なフィンガープリント"
```

秘密鍵一覧の先頭に表示される記号にも意味がある。

- `sec`、`ssb`：利用できる秘密鍵の実体がある
- `sec#`、`ssb#`：対応する秘密鍵の実体がない
- `sec>`、`ssb>`：秘密鍵がスマートカード上にある

作業前に、秘密主鍵が`sec#`、三つの秘密副鍵が`ssb`と表示されることを確認する。

YubiKeyも接続して状態を確認する。

```bash
gpg --card-status
```

次の項目を確認する。

- OpenPGPアプリケーションとして認識されていること
- `Key attributes`が登録する鍵のアルゴリズムと一致していること
- `Signature key`、`Encryption key`、`Authentication key`のフィンガープリント
- 初期値ではないUser PINとAdmin PINを設定済みであること

カード側とPC側のフィンガープリントを用途ごとに比較し、次のいずれかを選ぶ。

- 三つのスロットがすべて空：一時環境を作り、登録作業へ進む
- 三つのスロットがPC側の副鍵とすべて一致：目的の二重保持は完了しているため、以降の登録作業は不要
- 別の鍵がある、または一部だけ一致する：上書きせず、カードの用途とバックアップを確認する

既存の鍵が入っているスロットを上書きすると、その秘密鍵をYubiKeyから取り戻せない。
同じフィンガープリントがすでに表示されている場合も、`keytocard`をやり直す理由にはならない。

秘密主鍵のバックアップがあっても、現在の秘密副鍵まで含まれているとは限らない。
YubiKeyは秘密鍵のバックアップ先にならないため、S、A、Eの各秘密副鍵を復元できるオフラインバックアップも先に確認しておく。

## 一時的なGnuPG環境を作る

一時ディレクトリは`mktemp`で作成し、所有者以外が読み書きできないようにする。

```bash
umask 077
TEMP_GNUPGHOME="$(mktemp -d)"
chmod 700 "$TEMP_GNUPGHOME"
printf '%s\n' "$TEMP_GNUPGHOME"
```

`GNUPGHOME`環境変数は変更せず、各コマンドへ`--homedir "$TEMP_GNUPGHOME"`を渡して通常環境との取り違えを防ぐ。

## 秘密副鍵を一時環境へ複製する

通常環境から秘密副鍵をエクスポートし、そのまま一時環境のGnuPGへ渡す。

```bash
gpg --export-secret-subkeys "$PRIMARY_FINGERPRINT" |
  gpg --homedir "$TEMP_GNUPGHOME" --import
```

`--export-secret-subkeys`は、「指定した副鍵だけを単独で出力する」オプションではない。
公開主鍵を含む鍵ブロックを出力し、秘密主鍵の部分だけを利用不能なダミーに置き換える。
秘密副鍵の実体は含まれるため、出力を安全でない場所へ保存すると秘密副鍵が漏洩する。

この手順はパイプで直接インポートするため、秘密鍵を含む中間ファイルを作らない。
公開部分も含まれるので、公開鍵の別途インポートは不要である。

インポート後の状態を確認する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" \
  --list-secret-keys \
  --keyid-format LONG \
  --with-subkey-fingerprint
```

秘密主鍵が`sec#`、署名、認証、暗号化副鍵が`ssb`と表示されることを確認する。
対象ではない鍵が表示された場合や、必要な副鍵に`#`または`>`が付いている場合は、`keytocard`へ進まない。

## YubiKeyの鍵属性を揃える

YubiKeyの各スロットには、格納する鍵のアルゴリズムを表す鍵属性がある。
`gpg --card-status`で次の表示を確認できた場合、この節の操作は不要である。

```text
Key attributes ...: ed25519 cv25519 ed25519
```

スロットが空で、鍵属性が異なる場合だけカードを編集する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" --edit-card
```

管理者向けのコマンドを有効にして、`key-attr`を実行する。

```text
gpg/card> admin
gpg/card> key-attr
```

Signature、Encryption、Authenticationの順に設定を求められる。
三つとも鍵の種類にECC、曲線にCurve 25519を選ぶ。

```text
Signature key:      ed25519
Encryption key:     cv25519
Authentication key: ed25519
```

設定を終えたら、カード編集を終了する。

```text
gpg/card> quit
```

終了後にカードの状態を表示する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" --card-status
```

`Key attributes`が`ed25519 cv25519 ed25519`になったことを確認する。
鍵属性の変更もYubiKey本体への書き込みなので、各スロットが空の場合に限って実行する。

## YubiKeyへ秘密副鍵を登録する

一時環境の鍵を編集する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" \
  --edit-key "$PRIMARY_FINGERPRINT"
```

`keytocard`は、`key`コマンドで選択した副鍵を対象にする。
副鍵の番号は鍵ごとに異なるため、編集画面の`usage`とフィンガープリントを次の対応表と照合する。

| usage | アルゴリズム | 保存先             |
| :---- | :----------- | :----------------- |
| S     | Ed25519      | Signature key      |
| A     | Ed25519      | Authentication key |
| E     | Curve25519   | Encryption key     |

一つの副鍵だけを選択し、`keytocard`で対応する保存先へ登録する。
選択中の副鍵には`*`が付く。

```text
gpg> key <対象副鍵の番号>
gpg> keytocard
```

登録後は同じ`key`コマンドで選択を解除し、残りの副鍵でも繰り返す。

```text
gpg> key <対象副鍵の番号>
```

三つの副鍵を登録したら、編集内容を保存する。

```text
gpg> save
```

`save`すると、一時環境にあった各秘密副鍵の実体がカード参照用stubへ置き換わる。
通常環境の秘密副鍵には、この編集内容は保存されない。

## カードと通常環境を確認する

まず、一時環境からYubiKeyの各スロットを確認する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" --card-status
gpg --homedir "$TEMP_GNUPGHOME" \
  --list-secret-keys \
  --keyid-format LONG \
  --with-subkey-fingerprint
```

`Signature key`、`Authentication key`、`Encryption key`に、それぞれ対象の副鍵のフィンガープリントが表示されることを確認する。
一時環境の秘密鍵一覧では、カード上へ移した副鍵に`ssb>`が表示される。

次に、`--homedir`を付けず、通常環境を確認する。

```bash
gpg --list-secret-keys \
  --keyid-format LONG \
  --with-subkey-fingerprint
```

通常環境では、S、A、Eの各副鍵が引き続き`ssb`と表示され、`ssb>`へ変わっていないことを確認する。

## YubiKeyを使って署名と復号を試す

`--card-status`だけでは秘密鍵を使えることまで確認できないため、一時環境で署名を作成する。

```bash
printf 'YubiKey signing test\n' > "$TEMP_GNUPGHOME/test.txt"

gpg --homedir "$TEMP_GNUPGHOME" \
  --armor \
  --detach-sign "$TEMP_GNUPGHOME/test.txt"

gpg --homedir "$TEMP_GNUPGHOME" \
  --verify "$TEMP_GNUPGHOME/test.txt.asc" \
  "$TEMP_GNUPGHOME/test.txt"
```

次に、自分の公開鍵で暗号化したデータをYubiKeyで復号する。

```bash
gpg --homedir "$TEMP_GNUPGHOME" \
  --armor \
  --recipient "$PRIMARY_FINGERPRINT" \
  --output "$TEMP_GNUPGHOME/test.txt.asc.gpg" \
  --encrypt "$TEMP_GNUPGHOME/test.txt"

gpg --homedir "$TEMP_GNUPGHOME" \
  --decrypt "$TEMP_GNUPGHOME/test.txt.asc.gpg"
```

認証副鍵は、SSHなど実際の認証先で確認する。
SSH向けの`gpg-agent`設定はこの記事の対象外とする。

## 一時環境を削除する

カード上の鍵を使えることと、通常環境に秘密副鍵が残っていることを確認してから、一時環境の`gpg-agent`を終了する。

```bash
gpgconf --homedir "$TEMP_GNUPGHOME" --kill all
```

削除対象が`mktemp`で作成したパスであることを確認してから、一時環境を削除する。

```bash
case "$TEMP_GNUPGHOME" in
  /tmp/tmp.*) rm -rf -- "$TEMP_GNUPGHOME" ;;
  *) printf '想定外のパスです: %s\n' "$TEMP_GNUPGHOME" >&2 ;;
esac

unset TEMP_GNUPGHOME
unset PRIMARY_FINGERPRINT
```

この削除は、SSDなどに残ったデータを物理的に消去したことまでは保証しない。
削除後の復元まで脅威として考える場合は、暗号化した一時ファイルシステムや、ネットワークから隔離した使い捨て環境で作業する。

## 同じ秘密副鍵を二重保持する意味

YubiKeyへ登録した秘密鍵は、通常の秘密鍵ファイルとしてカードから再エクスポートできない。
そのため、YubiKeyは秘密鍵の利用場所にはなるが、バックアップにはならない。

一方、今回の構成ではPCにも同じ秘密副鍵が残る。
PCが侵害されて秘密副鍵を取得されれば、攻撃者はYubiKeyを持っていなくても、その副鍵で署名、認証、復号ができる。
YubiKeyへ登録しても、PC側の秘密副鍵はハードウェアで保護されない。

この構成の目的は、PC上の秘密副鍵を失わずにYubiKeyの運用を試すことにある。
動作とバックアップを確認した後、PC側をstubだけにするか、オフライン環境でYubiKey専用副鍵を作るかを次の段階で選ぶとよいだろう。

## 参考資料

- [GnuPG Manual: OpenPGP Key Management](https://gnupg.org/documentation/manuals/gnupg/OpenPGP-Key-Management.html)
- [GnuPG Manual: gpg](https://gnupg.org/documentation/manuals/gnupg26/gpg.1.html)
- [GnuPG Manual: gpg-agent](https://gnupg.org/documentation/manuals/gnupg26/gpg-agent.1.html)
- [GnuPG Manual: gpg-card](https://www.gnupg.org/documentation/manuals/gnupg26/gpg-card.1.html)
- [Yubico: OpenPGP Specifics](https://docs.yubico.com/hardware/yubikey/yk-tech-manual/yk5-apps-openpgp.html)
- [Yubico: YubiKey 5.2.3 Enhancements to OpenPGP 3.4](https://developers.yubico.com/PGP/YubiKey_5.2.3_Enhancements_to_OpenPGP_3.4.html)
- [Yubico: Importing keys](https://developers.yubico.com/PGP/Importing_keys.html)
- [GnuPG 2.4.1 release announcement](https://lists.gnupg.org/pipermail/gnupg-users/2023-April/066502.html)
