OpenSSLのコマンドでお手軽ファイル暗号化

OpenSSLを使ってファイルをお手軽に暗号化できることを知ったので、メモを兼ねて紹介したいと思います。

## 実行環境について

基本的にはOpenSSLかLibreSSLがインストールされていればよいです。
私はmacOSとLinuxのOpenSSLで確認しましたが、WindowsでもOpenSSLをインストールすれば利用可能だと思います。

一部、macOSのバージョン（Big Sur）によっては使えないコマンドがあるようですが、代替手段はあります（後述）。

## ファイル暗号化に使えるコマンド

OpenSSLにはたくさんのサブコマンドがありますが、今回紹介するファイル暗号化に使えるサブコマンドと特徴は次のとおりです。

- `openssl enc`
    - 暗号方式: 共通鍵暗号
        - パスワードで暗号化と復号
    - ファイル形式: ブロック暗号のバイナリデータ（オプションでBase64処理も追加できる）
- `openssl cms`
    - 暗号方式: 公開鍵暗号
        - X.509証明書で暗号化して、秘密鍵で復号
    - ファイル形式: MIME形式のテキストデータ（ヘッダとボディのやつ）
    - macOS Big Surでは使えない
        - Homebrewで別途OpenSSLを入れるか、次の`openssl smime`で代替可能
- `openssl smime`
    - `openssl cms`とほぼ同じ（データの互換性もある）
    - 一部の`openssl cms`サブコマンドが使えない環境ではこちらを使うとよい


## `openssl enc`

共通鍵暗号でファイルの暗号化・復号を行うコマンドです。

暗号化に使用する鍵はパスワードから導出されますが、別途鍵ファイルやIV（初期化ベクトル）などを直接指定するような使い方も可能です（本記事では割愛します）。

### 暗号化

次のコマンドは`foo.txt`というファイルを暗号化し、`encrypted_foo.dat`ファイルとして保存する例です。

```bash
# 暗号化
openssl enc -aes256 -pbkdf2 -md sha-256 -in foo.txt -out encrypted_foo.dat
```

### 復号

次のコマンドは`encrypted_foo.dat`というファイルを復号し、`decrypted_foo.txt`ファイルとして保存する例です。

```bash
# 復号
openssl enc -d -aes256 -pbkdf2 -md sha-256 -in encrypted_foo.dat -out decrypted_foo.txt
```

### `openssl enc`でよく使うオプション

主に次のようなオプションがあります（全てではないです）。

- `-d`: 復号
- `-pbkdf2`: 鍵の導出にPBKDF2を使用する（推奨）
- `-md`: 鍵の導出に使用するメッセージダイジェスト関数（`sha-256`, `md5`など）
    - OpenSSLのバージョンによってデフォルト値が異なるので、トラブルを避けるためにも明示的に指定したほうがよい
    - OpenSSL 1.0.2以前: `md5`
    - OpenSSL 1.1.0以降: `sha-256`
- `-base64`: 暗号化（または復号）の後（前）にBase64処理を行う
- `-in`: 入力ファイル
- `-out`: 出力ファイル
- `-p`: salt, key, ivを表示する
- `-P`: salt, key, ivを表示するだけ（暗号復号処理は行わない）
- `-list`: サポートしている暗号スイートのリストを表示する

## `openssl cms`

S/MIME 3.1に準拠したメッセージの暗号化・復号・署名・署名検証のためのコマンドです。
公開鍵で暗号化を行います。

ファイル暗号化というよりはメールの暗号化や署名のための機能ですが、ファイルの暗号化機能として活用することも可能です。

:::note alert
macOS Big SurではLibreSSLのバージョンの都合で利用できません。
Homebrewで別途最新のOpenSSLをインストールするか、次の`openssl smime`で代替可能です。
:::

### 前準備: 鍵と証明書の作成
まず、RSA鍵（`key.pem`）とX.509証明書（`cert.pem`）を作成する必要があります（ファイル名は一例です）。

証明書の`/CN=alice`の`alice`の部分は、証明書のCommon Nameです。自分のアカウント名などにすればよいでしょう（ちなみに、Webサーバー用の証明書の場合はドメイン名にします）。

```bash
# 鍵の生成
openssl genrsa -out key.pem 4096

# 証明書の生成（自己署名証明書）
# "alice"の部分は自分のアカウント名などにします
openssl req -new -x509 -key key.pem -out cert.pem -days 36500 -subj "/CN=alice"

# 証明書の内容の確認
openssl x509 -in cert.pem -noout -subject -dates
```

### 暗号化

次のコマンドは`cert.pem`という証明書を使って、`foo.txt`を暗号化し、`encrypted_cms_foo.txt`として保存する例です。

```bash
# 暗号化
openssl cms -encrypt -aes256 -binary -in foo.txt -out encrypted_cms_foo.txt cert.pem
```

### 復号

次のコマンドは`key.pem`という秘密鍵を使用し、`encrypted_cms_foo.txt`を復号し、`decrypted_cms_foo.txt`として保存する例です。

```bash
# 復号
openssl cms -decrypt -inkey key.pem -in encrypted_cms_foo.txt -out decrypted_cms_foo.txt
```

## 暗号化されたファイルについて

`openssl cms`で暗号化されたファイルの中身は次のようなテキストファイルになります。

いくつかのヘッダとBase64エンコードされたペイロードで構成されています。

```text
MIME-Version: 1.0
Content-Disposition: attachment; filename="smime.p7m"
Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name="smime.p7m"
Content-Transfer-Encoding: base64

MIICoAYJKoZIhvcNAQcDoIICkTCCAo0CAQAxggJIMIICRAIBADAsMBQxEjAQBgNV
BAMMCXluYWthbXVyYQIUF5dLx3pVTTQT5m6Z64PQVWn+KfwwDQYJKoZIhvcNAQEB
BQAEggIAPvjGzsikMqHesMO90wJdKJxWDH0fqAUIqubTroPu09HZ6XrXWeWJ79fC
HYM9iAILv...
```

## `openssl smime`

`openssl cms`と使い方は同じです（サブコマンドの`cms`が`smime`になっただけ）。

```bash
# 鍵の生成
openssl genrsa -out key.pem 4096

# 証明書の生成（自己署名証明書）
openssl req -new -x509 -key key.pem -out cert.pem -days 36500 -subj "/CN=alice"

# 証明書の確認
openssl x509 -in cert.pem -noout -subject -dates

# 暗号化
openssl smime -encrypt -aes256 -binary -in foo.txt -out encrypted_smime_foo.txt cert.pem

# 復号
openssl smime -decrypt -inkey key.pem -in encrypted_smime_foo.txt -out decrypted_smime_foo.txt
```

## まとめ

それぞれ一長一短あるかと思いますが、OpenSSLを使って手軽にファイルを暗号化することができました。

`enc`のほうは前準備の必要もなく、パスワードで簡単に扱えるのが良いですね。
プログラムなどで暗号化の実装をしているときの検証にも使えたりします。

`cms` / `smime`のほうは、鍵と証明書を管理しないといけないのがやや面倒ですが、処理するときにいちいちパスワードを入力しなくてもよいのが楽です。
また、テキストファイルになるので、やり取りがしやすくなります。

## 参考

- [ファイルをお手軽に暗号化したい！ – openssl cms のススメ | IIJ Engineers Blog](https://eng-blog.iij.ad.jp/archives/9268)
- [コマンドラインでファイルを暗号化する方法](https://zenn.dev/noraworld/articles/file-encryption)
- [OpenSSLで共通鍵暗号を使う場合の鍵の指定 #暗号 - Qiita](https://qiita.com/angel_p_57/items/bc50c5cfbb0276e07707)
- [OpenSSL 1.0.0 のリリースについて](https://www.jnsa.org/result/pki/seminar/2010/2010-030.pdf)