16
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

OpenSSLで共通鍵暗号を使う場合の鍵の指定

Posted at

はじめに

背景

今回は、OpenSSLの「共通鍵暗号」の機能、中でも鍵の取り扱いに焦点をあてます。
OpenSSLは、ライブラリとして各種言語から機能を呼び出すこともできますが、それ自身が暗号化等の機能を使えるツールセットにもなっています。

そうすると、「opensslコマンドで暗号化して作ったデータを各種言語で復号したい」といった需要が一部出てきたりするわけですが、鍵等のデータの取り扱いを意識しないと、大抵うまく行きません。

今回は、「暗号化に必要な鍵等のデータ」に焦点をあてていきたいと思います。なお、PBKDF2については取り扱いません

検証環境

ここでは、Ubuntu18/WSL1(Win10)付属のOpenSSL 1.1.1を検証環境としていきます。

ツールによる暗号化

単純な暗号化

opensslコマンドは、サブコマンド enc により、共通鍵暗号による暗号化や復号を行うことができます。なので、単純に例えば 256bit AESで暗号化する場合、次のようなコマンドになります。

単純な暗号化
$ openssl enc -aes256 -in abc.txt -out enc.dat
enter aes-256-cbc encryption password: ******
Verifying - enter aes-256-cbc encryption password: *******
*** WARNING : deprecated key derivation used.
Using -iter or -pbkdf2 would be better.

この場合 -in で指定したのが平文データ、-out で指定したのが暗号文保存先です。
そして、****** で表現してますが ( 実際は表示されません )、パスワードを入力しています。
※最後の2行のメッセージは、今回取り上げないPBKDF2を使えという内容なので、以降無視し、コマンド実行例からも省略します。

ちなみに、このパスワードは -pass pass:文字列 の形式で指定することもできます。今度は逆に復号してみます。

パスワードを指定して復号
$ openssl enc -d -aes256 -pass pass:mypassword -in enc.dat
abcdefghijklmnopqrstuvwxyz
$ cat abc.txt
abcdefghijklmnopqrstuvwxyz

今回は mypassword というパスワードを使っているという想定です。
この実行例のように、復号して元のデータが得られていることが分かります。

なお、今回は取り扱いませんが、上記のような実行例では大抵暗号データがバイナリになりますので、base64により印字可能なデータにする -a オプションというのもあります。

鍵データの正体

さて、前項のようにパスワードを指定して暗号化・復号を行う例を見ますと、「共通鍵暗号は、共通の鍵を使う…。てことは!?」とある程度の人が、「このパスワードこそ鍵なんだ!」と思うこともあるようです。
しかし、「鍵データに使えるデータ長は方式毎に決まっている」 「鍵だけではなくIV(初期化ベクトル)というデータも大抵必要になる」という2点から、「パスワード=鍵」という考えは誤りです。

実際には、opensslが与えられたパスワードから鍵・IVを生成しているというのが正解です。
ただ、パスワードをそのまま使うと同じパスワードを使用することであっさり同じ鍵・IVが生成されてしまうので、saltと呼ばれるデータも併用します。このsaltは、ツールのオプションでも指定できますが、デフォルトではツールがランダムに生成します。

saltの情報が失われると、パスワードから鍵・IVの生成ができないため、ツールの作成した暗号文に埋め込まれます。以下のコマンドでダンプすると、先頭に Salted__ とあり、その次の8バイト分のsaltを確認することができます。
※方式により長さが変わる可能性もありますが、大抵8バイトのようです。

salt付き暗号データの確認
$ xxd enc.dat
00000000: 5361 6c74 6564 5f5f 3bde ed65 e2ff 50b1  Salted__;..e..P.
00000010: 0f41 c5b9 0dda 672d 1e20 3aa1 979b e267  .A....g-. :....g
00000020: edd3 68f0 c25c 7971 b036 ef10 1b4c c18a  ..h..\yq.6...L..

では肝心の鍵・IVは? というと、パスワードを知っているという前提で、enc サブコマンドの中で -P オプションにより得ることができるようになっています。

鍵・IVの確認
$ openssl enc -P -d -aes256 -pass pass:mypassword -in enc.dat
salt=3BDEED65E2FF50B1
key=40F59A2C3AE6C310E4C45DE54A8041BA869865C1DF6B7D1D20D5986248580530
iv =8816B5594C603BFF66CAE73B44CD3D7D

saltに加えて、鍵(key)およびIVが16進ダンプとして出力されます。
※ダンプだとものすごく長いデータに見えますが、この場合の鍵,IVの実データは32バイト,16バイトになります。
これが、暗号化・復号で内部的に使われている鍵・IVということです。

鍵・IVの直接指定

ここまででお気付きかと思いますが、なにもパスワードを使わなくても直接に鍵・IVを指定して処理を行うことも可能です。
※パスワードの方が扱いは楽ですが。

前述の実行例でのパスワード・saltに相当する鍵・IVを使った暗号化は次の通りです。

鍵・IVを指定した暗号化
$ openssl enc -aes256 -K 40F59A2C3AE6C310E4C45DE54A8041BA869865C1DF6B7D1D20D5986248580530 -iv 8816B5594C603BFF66CAE73B44CD3D7D -in abc.txt -out enc2.dat

つまり、-Kおよび-ivオプションで、直接値を指定してしまう、という方法になります。
パスワード指定でできたファイルと比べると、先頭のsalt部分を除いて一致していることが分かります。

暗号データの比較
$ xxd enc.dat
00000000: 5361 6c74 6564 5f5f 3bde ed65 e2ff 50b1  Salted__;..e..P.
00000010: 0f41 c5b9 0dda 672d 1e20 3aa1 979b e267  .A....g-. :....g
00000020: edd3 68f0 c25c 7971 b036 ef10 1b4c c18a  ..h..\yq.6...L..
$ xxd enc2.dat
00000000: 0f41 c5b9 0dda 672d 1e20 3aa1 979b e267  .A....g-. :....g
00000010: edd3 68f0 c25c 7971 b036 ef10 1b4c c18a  ..h..\yq.6...L..

復号する時も、同様に -K,-iv の指定で処理できます。ただ、先頭のsaltがあると却って邪魔になるので、パスワードベースで作成した暗号データの場合には、その分を取り除く必要があります。

鍵・IVを指定した復号
$ openssl enc -d -aes256 -K 40F59A2C3AE6C310E4C45DE54A8041BA869865C1DF6B7D1D20D5986248580530 -iv 8816B5594C603BFF66CAE73B44CD3D7D -in enc2.dat
abcdefghijklmnopqrstuvwxyz
$ tail -c +17 enc.dat | openssl enc -d -aes256 -K 40F59A2C3AE6C310E4C45DE54A8041BA869865C1DF6B7D1D20D5986248580530 -iv 8816B5594C603BFF66CAE73B44CD3D7D
abcdefghijklmnopqrstuvwxyz

ツール以外での処理を見据えて

パスワード→鍵・IVの法則の必要性

ここまでで、パスワードは鍵そのものではなく、処理に実際に使う鍵・IVの2種類のデータがあることを見てきたわけですが、opensslコマンドを使うだけなら別に気にする必要はありません。

しかし、暗号化したデータを各種プログラミング言語から扱いたいと言うような場合、OpenSSLライブラリを使ったとしても、鍵・IVの存在を意識しなければ処理できません。

例えば、RubyのOpenSSLライブラリの場合、以下のように key, iv が分かっている前提で復号処理を行うことになっていますし、

# 復号化器を作成する
dec = OpenSSL::Cipher.new("AES-256-CBC")
dec.decrypt
# 鍵とIVを設定する
dec.key = key
dec.iv = iv

PHPのOpenSSL関数の場合も、事情は同じです。
$passphraseとなっていて紛らわしいのですが、これはパスワードではなく ( バイナリデータとしての ) 鍵を意味します。

  openssl_decrypt(
    string $data,
    string $cipher_algo,
    string $passphrase,
    int $options = 0,
    string $iv = "",
    string $tag = "",
    string $aad = ""
): string|false

パスワードからの鍵・IVの生成

さて、では肝心のパスワードからの鍵・IVの生成ですが、これはOpenSSLの内部ルーチンEVP_BytesToKeyに対して、count引数を1とした処理を行っていることが分かっています。

このルーチンは、指定されたハッシュ関数を用いてデータ変換を行い、鍵等に使えるデータを作り出していくものです。以下、処理の概略を示します。

  • パスワードとsalt(バイナリデータ)を連結し、そのハッシュ値 H0 を計算する。
  • H0とパスワードとsaltを連結し、そのハッシュ値 H1 を計算する。
  • H1と…と言うように、必要なデータが揃うまでハッシュ値計算を繰り返す。
    ※計算したハッシュ値のデータ量合計が、鍵・IVのデータ量を賄えれば十分
  • H0, H1, … を連結したデータの内、先頭データを鍵として切り出す。
  • 残りの先頭データをIVとして切り出す。

このハッシュ関数はツールのオプション -md によって指定できます。デフォルトは sha256 であり、ハッシュ値は32バイト、256bit AESの鍵32バイトとIV 16バイトを賄うには、2回のハッシュ計算で十分です。

この計算で鍵・IVが生成されていることは、以下のようなコマンドで確認することができます。

鍵・IVの比較
$ ( echo -n mypassword; xxd -p -r <<< 3BDEED65E2FF50B1 ) | openssl dgst -sha256
(stdin)= 40f59a2c3ae6c310e4c45de54a8041ba869865c1df6b7d1d20d5986248580530
$ (
>   (  echo -n mypassword; xxd -p -r <<< 3BDEED65E2FF50B1 ) | openssl dgst -sha256 -binary
>   echo -n mypassword; xxd -p -r <<< 3BDEED65E2FF50B1
> ) | openssl dgst -sha256
(stdin)= 8816b5594c603bff66cae73b44cd3d7dcbe2c368a284a9264ac689cebcad6bb5
$ openssl enc -P -d -aes256 -pass pass:mypassword -in enc.dat
salt=3BDEED65E2FF50B1
key=40F59A2C3AE6C310E4C45DE54A8041BA869865C1DF6B7D1D20D5986248580530
iv =8816B5594C603BFF66CAE73B44CD3D7D

この例の16進ダンプ出力のように、1回目のハッシュ値計算の値がそのまま key に、2回目のハッシュ値計算の先頭16バイト ( 16進ダンプで32文字分 ) が iv に一致していることが分かります。
なお、salt の値も16進ダンプで得られている状態なので、xxd コマンドでバイナリに変換して使っています。

PHPでの実装例

最後におまけして、PHPでの実装例です。
暗号データの先頭からsaltを読み取り、パスワードを併せて鍵・IVを生成、しかる後にsalt部分を除いた暗号データを、鍵・IVを指定して復号しています。
Teratailでの回答で示したコードの焼き直しです。

PHPでの復号実装例
<?php
  $filename="enc.dat";
  $pass="mypassword";
  $fh=fopen($filename,"r");
  $enc=fread($fh,filesize($filename));
  $salt=substr($enc,8,8);
  $key=openssl_digest($pass.$salt,"sha256",true);
  $iv=substr(openssl_digest($key.$pass.$salt,"sha256",true),0,16);
  echo openssl_decrypt(substr($enc,16),"aes256",$key,OPENSSL_RAW_DATA,$iv);
?>

終わりに

このパスワードから鍵・IVを生成する部分、方式としては弱いものなので、本来はPBKDF2を使うことが推奨です。ただそのやり方にも応用が利くと思いますので(多分)、備忘録としてデフォルトの方式をまとめました。
ちょっとした小ネタですが、お役に立てば幸いです。

16
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?