とほほの暗号化入門

トップ > とほほの暗号化入門

目次

概要

超サマリ

暗号化・復号化

元のデータを推測できないように変換することを 暗号化(encryption)、暗号化されたデータをもとのデータに戻すことを 復号(decryption) と言います。暗号化は元に戻すことが可能な可逆変換です。

ハッシュ

暗号化と似たものに ハッシュ化(hashing) がありますが、こちらは元のデータに戻すことができない非可逆変換です。

共通鍵暗号と公開鍵暗号

暗号化には、共通鍵で暗号化して共通鍵で復号する 共通鍵暗号系(common-key cryptosystem) と、鍵のペア(秘密鍵と公開鍵)を用いて、秘密鍵で暗号化したものを公開鍵で復号、または、公開鍵で暗号化したものを秘密鍵で復号する 公開鍵暗号系(public-key cryptosystem)があります。詳しくは後述します。

ハッシュ

ハッシュ

あるデータに対して決められたアルゴリズムを適用し、不可逆のデータを作成することを ハッシュ化 と呼びます。また、ハッシュ化されたデータを ハッシュ値 と呼びます。チェックサムのようなものですね。大きなデータであっても、128~512ビット程度の小さなデータにハッシュ化します。ハッシュ値が偶然に同じ値になる(衝突する)可能性はありますが、その確率は128ビット長でも 3.4×1038分の1の確率であり、「実用上十分に小さい」とされています。

ハッシュの利用目的

ハッシュ値は不可逆なので元のデータに戻すことはできませんが、下記などの目的に使用されます。

チェックサム

例えば、ファイルをダウンロードする際、ダウンロードしたファイルをハッシュ化したハッシュ値と、ダウンロードサイトで公開されているハッシュ値を比較することで、ファイルが正常にダウンロードできていること、ファイルが改竄されていないことをチェックすることができます。

送信元認証

例えば、送信側はデータに秘密の情報を付加したものをハッシュ化してデータとともに送信します。受信側も受け取ったデータをあらかじめ共有しておいた秘密の情報を付加してハッシュ化します。計算したハッシュ値と受け取ったハッシュ値が合致すれば、そのデータは秘密の情報を知っている相手からの受信であることが確認できます。

パスワードの安全性向上

ユーザのパスワードをDBに保存する場合、パスワードを暗号化したものではなく、パスワードをハッシュ化したものを保存します。ログイン時に送られてきたパスワードを同じアルゴリズムでハッシュ化し、ハッシュ値がDBに保存していたハッシュ値と合致すれば、正しいパスワードが入力されたと判断します。DBデータが流出してもハッシュ値は非可逆なので安全性が高まります。

データ圧縮

公開鍵暗号 は大量の計算を必要とするため、大きなデータを公開鍵暗号で暗号化するにはCPUリソースを消費してしまいます。暗号化の目的ではなく、署名 の目的で暗号化する際には、大きなデータをハッシュ化し、そのハッシュ値を公開鍵暗号で暗号化することにより、署名のための計算量を減らすことができます。

ハッシュアルゴリズム

ハッシュアルゴリズムには下記などがあります。

MD5(Message Digest 5)

128ビットのハッシュ値を作成します。1992年に発表され、UNIX 系システムのパスワードのハッシュ化などに利用されていましたが、解析手法も確立し、現在では通常のコンピュータでも数秒で解読されてしまうため、セキュリティの目的で使用することはなくなりました。チェックサムの目的であればまだ利用されることがあります。

SHA-1(Secure Hash Algorithm 1)

1993年発表の SHA-0 を改良し、1995年に発表されたたもので、160ビットのハッシュ値を生成します。こちらも2011年に解析理論が確立し、実際のハッシュ値衝突例もでてきたことから、現在ではセキュリティの目的で使用することは推奨されなくなってきました。

SHA-2(Secure Hash Algorithm 2)

SHA-1 を改良して 2001年に発表されたもので、生成するビット数によって SHA-224SHA-256SHA-384SHA-512 などの種類があります。よく使用されるのは SHA-256 と SHA-512 です。SHA-224 は 3DES の鍵長の倍数になるように後から追加されました。

# opensslで"ABCDEFG"をSHA256でハッシュ化する例
echo -n "ABCDEFG" | openssl dgst --sha256

# ハッシュ化したものをBASE64で文字列化する例
echo -n "ABCDEFG" | openssl dgst --sha256 --binary | base64
SHA-3(Secure Hash Algorithm 3)

SHA-2 よりも強力なハッシュアルゴリズムとしていくつかのアルゴリズムの中から SHA-3 としての選出が行われ、2015年に公開されましたが、SHA-2 が解析されるようになるのは当分先になりそうなことから、まだあまり利用されていません。

CRC(Cyclic Redundancy Check)

巡回冗長検査と訳されます。生成されるハッシュ値のビット数によって CRC-8, CRC-16, CRC-32, CRC-64 などの種類があります。チェックサムの目的で使用されることがほとんどで、セキュリティの目的で使用されることはありません。

パスワードハッシュ化の詳細

ソルト

パスワードのハッシュ化には、ソルト(salt) と呼ばれるランダムデータをパスワードと連結してハッシュ化し、ソルトとハッシュ値をDB保存します。例えば、例えば100万件パスワードハッシュ情報が流出してしまった場合、ソルト無しの場合、よく利用されるパスワードをハッシュ化してDB検索することで簡単にユーザIDとパスワードのペアを見つけることができますが、ソルト有りの場合、ソルトとハッシュ値が流出しても1件1件毎にソルト付きでハッシュ計算しなくてはならず、解析のための計算量が増え、安全性が高まります。

ストレッチング

パスワードをハッシュ化する際には、ソルトに加えて、ハッシュ値をさらに再度ハッシュ化することを数千~数万回繰り返して行う ストレッチング(stretching) を行います。これにより、ユーザ情報が流出した場合でも、1件1件のデータに対して数千~数万回のハッシュ化計算をしなければならなくなり、解析にかかる時間を増やすことが可能となります。

パスワードハッシュアルゴリズム

実際のパスワードのハッシュ化では、昔は MD5 が利用されてきましたが、最近では SHA-512 が、一部のディストリビューションでは bcrypt が使用されています。/etc/shadow ファイルのパスワード部の先頭数文字で、どのアルゴリズムが使用されているかを判断することができます。

$1  ... MD5
$5  ... SHA-256
$6  ... SHA-512 (Ubuntu 21.04 や CentOS 8など)
$2a ... bcrypt 2a(Blowfish) (OpenBSD や SUSE Linuxなど)
bcrypt

ハッシュ関数の代わりに Blowfish というブロック暗号を使用します。いくつかのバージョンがあり、現在では 2a というバージョンがよく利用されています。下記は Python の bcrypt 使用例です。引数の 10 は 2の10乗(1024回)のストレッチングを行うことを、2a は bcrypt のバージョンを表しています。生成したハッシュ値の先頭にはバージョン(2a)、ストレッチ強度(10)、ソルト情報(WuEh...YYe)が付加されます。

import bcrypt

password = b'my-secret-password'

# パスワードのハッシュ値を求める
salt = bcrypt.gensalt(10, b'2a')	# => b'$2a$10$WuEhN51aj9BPVPIQ1pBYYe'
hash = bcrypt.hashpw(password, salt)	# => b'$2a$10$WuEhN51aj9BPVPIQ1pBYYeTJ227o8zr2QtechTilyqmayHMkHMIjq'

# ハッシュ値とパスワードを検証する
check = bcrypt.checkpw(password, hash)	# => True
PBKDF2(Password-Based Key Derivation Function 2)

Django や iOS 9 などが採用しているアルゴリズムです。ハッシュ関数として HMAC-SHA1 や HMAC-SHA256 などを選択することができます。ソルトとストレッチ回数を指定します。下記は Python で PBKDF2 のハッシュ値を求めるサンプルです。ストレッチ回数は、2005年時点の推奨値で4,096回。iOS 9 では 10,000回のストレッチングを行っています。

import os
import hashlib
password = b'my-secret-password'
salt = os.urandom(16)
hash = hashlib.pbkdf2_hmac('sha256', password, salt, 100000)
print("hash=%s" % hash)
Argon2

2015年に開催されたパスワードハッシュ協議会で優勝したアルゴリズムです。Argon2d と Argon2i の二つのバージョンがあります。PBKDF2 などは使用するメモリが少ないため、ASIC や GPU を用いた攻撃に弱い面がありましたが、Argon2 では使用するメモリ量や実行時間をパラメータとして制御することが可能です。デフォルトで 100MB のメモリを使用します。

from argon2 import PasswordHasher
password = b'my-secret-password'
ph = PasswordHasher(time_cost=2, memory_cost=102400)
hash = ph.hash(password)
print("hash=%s" % hash)

共通鍵暗号

共通鍵暗号系

共通鍵で暗号化し、同じ共通鍵で復号するものを 共通鍵暗号系(common-key cryptosystem) と呼びます。例えば、クライアントとサーバが同じ共通鍵を保持しておき、クライアントがデータを共通鍵で暗号化したものをサーバに送信、サーバがこれを共通鍵で復号することにより暗号化通信を実現します。秘密鍵暗号系 と呼ばれることもあります。

ブロック暗号とストリーム暗号

データを一定サイズのブロックに分割し、ブロック毎に暗号化する方式を ブロック暗号 と呼びます。DES, AES などがあります。これに対してビット、バイトやワード単位で暗号化する方式を ストリーム暗号 と呼びます。RC4 などがあります。ブロック暗号ではブロックサイズ分のデータが溜まるまで暗号化を開始できませんが、ストリーム暗号ではデータを1バイト毎に逐次暗号化してストリーム通信できるのが特徴です。

暗号化パラメータ

暗号化には、下記などの情報が必要となります。これらは復号する側と意識を合わせておく必要があります。特に、異なる言語やライブラリで復号する場合は、アルゴリズムは同じものを選んでいても、その他のパラメータがずれているために復号エラーとなるケースがよくあります。

共通鍵暗号アルゴリズム

共通鍵暗号のアルゴリズムとしては下記などがあります。最近では AES が主流です。

DES(Data Encryption Standard)

1976年に米国の連邦情報処理標準(FIPS)として採用したブロック暗号です。64ビットのブロックを 56ビットの鍵で暗号化します。1980~1990年代は標準的に使用されていましたが、現在では暗号化の目的ではあまり使用されていません。

3DES(Triple DES)

DES を強化する目的で1998年に策定したブロック暗号です。56ビットの鍵を3個(k1, k2, k3)使用し、k1 で暗号化したデータを k2 で復号し、再度 k3 で暗号化します。56×3=168 ビットの鍵を使用しますが、既知の攻撃手法により実質的な暗号強度は 112ビット程度となります。

AES(Advanced Encryption Standard)

米国国立技術研究所(NIST)が1998年に採用したブロック暗号で、現在の主流です。ブロックサイズは 128ビット固定で、鍵長は 128, 192, 256ビットから選択することができます。SSL/TLS や ZIP, 7z の暗号化でも利用されています。

RC4(Rivest Cipher 4)

1987年に開発されたストリーム暗号です。RC4 という名前は RSA 社の商標であるため、ARCFOUR と呼ばれることもあります。RSAWEP, WPA などで利用されていましたが、攻撃手法がいくつか見つかり、現在では推奨されていません。代わりに、ブロック暗号アルゴリズムを用いながらストリーム暗号化が可能な、OFB, CFB, CTR などのモードが利用されるようになってきました。

パディング

ブロック暗号では、64ビットや 128ビットなどのブロック単位で暗号化するため、元データがブロックサイズの整数倍でない場合、パディングを追加して整数倍にする必要があります。パディングの方式には下記などがあります。共通鍵暗号の AES では PKCS#7 が、公開鍵暗号の RSA では OAEP がよく使用されます。

PKCS#7パディング

データがブロックサイズの整数倍に1バイト足りない場合は末尾に 0x01 を、2バイト足りない場合は 0x02 0x02 を、3バイト足りない場合は 0x03 0x03 0x03 を付加します。最後の1バイトがパディングサイズを示します。データがブロックサイズの整数倍の場合は1ブロック余分にパディングが付加されます。AES の場合ブロック長は128ビット(16バイト)なので、パディングサイズは 0x01~0x10 のいずれかとなります。

PKCS#5パディング

PKCS#7 のサブセットで、パディングサイズを 0x01~0x08 に限定し、最大8バイトのパディングを行うものです。AES 暗号化で PKCS#5 を使用すると表記されていることがありますが、AES が 128ビット(16バイト)に対して PKCS#5 は8バイトにしか対応しておらず、実態は PKCS#7 でパディングしていることが多いそうです。

ISO7816-4パディング

スマートカード規格の ISO/IEC 7816-4 で規格化されている方式で、1バイトパディングする場合は 0x80 を、N バイトパディングする場合は 0x80 の後ろに 0x00 を N - 1 個付加します。パディングを取り除く際は、末尾のデータから 0x80 が出現するまでのバイトを取り除きます。

ISO10126-2パディング

基本的には PKCS#7 と同様、最後の1バイトがパディングサイズを示しますが、最後の1バイト以外は乱数が格納されます。

OAEP(Optimal Asymmetric Encryption Padding)

最適非対称暗号化パディングと訳されます。RSA 組み合わせて利用されることが多く、この場合は RSA-OAEP と呼ばれます。元データに 0 を付加し、ランダム値をハッシュしたものとの XOR をとり、また、これをハッシュしたものと連結します。(→ 詳細)

暗号利用モード

nビットのブロック暗号では nビットのデータを暗号化するアルゴリズムのみを定義しますが、連続するブロック列を暗号化する際の方式として暗号利用モードを指定します。主な暗号利用モードには下記などがありますが、現時点では CBCCTR の利用が多いようです。最近では認証と暗号化を組み合わせた GCM や EAX も利用されるようになってきました。

ECB(Electronic Codebook)

元データを複数のブロック Di に分割し、それぞれに対して単純に秘密鍵で暗号化を行います。ブロックの内容が同じであれば暗号化の結果も同じになるため、解析されやすいという弱点があります。例えば、画像データを ECBモードで暗号化しても、元画像の輪郭がうっすらと残ってしまうことがあります。(→ 詳細)

Ci = enc(key, Di)
CBC(Cipher Block Chaining)

前ブロックの暗号化の結果を、次ブロックとXORをとったものを暗号化します。ブロックの内容が同じであっても異なる結果となり、解析されにくくなります。最初のブロックは、初期化ベクトル(IV)と呼ばれるデータとXORをとります。ブロックがひとつでも欠損すると XOR に渡す値を失ってしまうため、後続のデータは復号できなくなります。

C0 = enc(key, IV)
Ci = enc(key, Di XOR Ci-1)
CFB(Cipher Feedback Mode)

初期化ベクトル(IV)を暗号化したものをR0、R0 の上位 kビットと元データの先頭 kビットの XOR を結果 C0 とします。R0k ビット上位にシフトして下位 k ビットに C0 を埋め込み、これを暗号化したものを R1 とし、次の元データと XOR する処理を繰り返します。ブロックサイズ n とは異なる k ビット単位で暗号化できること、データが欠損しても直前データがあれば後続データを復号できることから、ストリーム暗号としても用いられます。

R0 = enc(key, IV)
Ri = enc(key, (Ri-1 << k) OR Ci-1)
Ci = (Ri >> (n - k)) XOR Di
OFB(Output Feedback Mode)

初期化ベクトル(IV)を暗号化したものを R0 とし、これを暗号化したものを R1、これを暗号化したものを R2... とします。元データを D0、D1、D2... に分割し、Ri と Di の XOR を暗号化の結果とします。CFB と同様 k ビット毎に処理する方式もありましたが、セキュリティ的に弱いことが判明し、現在では使用されていません。

R0 = enc(key, IV)
R1 = enc(key, Ri-1)
Ci = Ri XOR Di
CTR(CounTeR Mode)

初期化ベクトル(IV)の代わりにノンス(Nonce)と呼ばれる情報を持ち、ノンスとカウンター値を組み合わせたものを Ri とします。Ri を暗号化したものと元データの XOR をとったものを結果 Ci とします。単純なアルゴリズムですが、ストリーム暗号にも利用できること、パディングが不要であること、データ欠損にも対応できること、並列計算も可能なことから利用が増えています。

Ri = Nonce + i
Ci = enc(key, Ri) XOR Di
CCM(Counter with CBC-MAC)

認証付き暗号(AEAD)のひとつ。ストリーム暗号として利用でき、また、暗号化と同時に認証も行うことができるモードです。

EAX(Encrypt-then-Authenticate-then-Translate)

認証付き暗号(AEAD)のひとつ。CTR と OMAC を組み合わせたもので、CCM と同様、認証機能付きストリーム暗号として利用できます。CCM の決定をカバーするものとして採用されました。

GCM(Galois/Counter Mode)

認証付き暗号(AEAD)のひとつ。暗号化として CTR を、認証として Galois Mode を使用します。128ビットのブロック暗号で利用可能です。暗号化も復号も並列処理が可能で、効率や性能がよいことから、パケットの暗号化に適しており、IEEE 802.11ad, IPsec, SSH, TLS 1.2 などに採用されています。

メッセージ認証

MAC(Message Authentication Code)

メッセージ認証コードと訳されます。送信側と受信側で同じ共通鍵を使用することで、メッセージを認証するための仕組みです。公開鍵を用いて署名するのがデジタル署名、共通鍵を用いて署名データ(MAC値、タグとも呼ばれます)を生成するのが MAC です。署名アルゴリズムとしてハッシュを用いる方式(HMAC) と、暗号化を用いる方式(CBC-MAC, OMAC, CMACなど)に大別されます。

MACアルゴリズム

HMAC(Hash-based MAC)

任意のハッシュアルゴリズムを用いて署名データ(MAC値)を生成します。MD5 を用いる場合は HMAC-MD5、SHA256 を用いる場合は HMAC-SHA256 などと表記されます。下記の計算で、hash() は SHA256 などのハッシュ関数、|| はビット列の連結、key は共通鍵を 0x00 でパディングしたもの、ipad はブロック長の 0x36 の繰り返し、opad はブロック長の 0x5c の繰り返し、m が署名対象メッセージを意味します。

HMAC = hash((key XOR opad) || hash((key XOR ipad) || m))
CBC-MAC

ブロック暗号の CBC モードを用いてメッセージ認証を行う方式です。

XCBC

CBC-MAC の改良版です。

OMAC(Open-Key CBC MAC)

XCBC の改良版です。

CMAC(Cipher-based MAC)

OMAC の改良版です。OMAC1 とも呼ばれていました。

公開鍵暗号

公開鍵暗号とは

公開鍵暗号系では、まず、公開鍵と秘密鍵のペアを作成します。秘密鍵は人に知られてはならない秘密の情報ですが、公開鍵は広く人に公開します。この鍵のペアは、公開鍵で暗号化したものは秘密鍵で復号できる、また、秘密鍵で暗号化したものは公開鍵で復号できるという特徴があります。この特質を利用してデータを暗号化するだけではなく、様々な目的で利用されます。

公開鍵によるデータの秘匿

例えば、AさんがBさんに情報を送る場合、AさんはBさんの公開鍵で暗号化してBさんに送ります。Bさんは自分の秘密鍵でこれを復号します。復号できるのは秘密鍵を知っているBさんのみであり、情報の秘匿が守られます。ただ、公開鍵暗号系は共通鍵暗号系にくらべて計算負荷が高いため、実データの暗号化は共通鍵暗号で暗号化し、その鍵情報のみを公開鍵で暗号化して送付したりもします。

公開鍵による本人認証

例えば、AさんはBさんから、Bさんの秘密鍵で暗号化されたデータを受け取り、これをBさんの公開鍵で復号します。Bさんの公開鍵で復号できるということは、そのデータを作成したのはBさんの秘密鍵を知っているBさん自身だということが確認でき、Bさんの本人確認を行うことができます。

公開鍵によるデジタル署名

デジタル署名は、データに電子印鑑を押すようなものです。たとえば、Aさんは、元データのハッシュ値を秘密鍵で暗号化したものをシグネチャ(署名)として元データに付加して、Bさんに送ります。Bさんは付加されたシグネチャをAさんの公開鍵で復号し、元データのハッシュ値と合致すれば、その文書は確かにAさんが送信してきたものであることが確認できます。

証明書

HTTPS などで利用する証明書は、ドメイン名や管理者名などの情報に対して、認証局(CA: Certification Authority)がデジタル署名を行い、その確からしさを保証するものです。証明書を作成するにはまず、秘密鍵(*.key)を作成し、秘密鍵を用いてドメイン名や管理者名などの情報を含む証明書署名要求(CSR: Certificate Signing Request)(*.csr)を作成します。これをベリサイン(現:シマンテック)、ジオトラストなどの認証局に送って署名してもらい、証明書(*.crt)とします。有名な認証局の公開鍵はルート証明書として OS にプリインストールされており、クライアントは、この公開鍵を用いて HTTPS サーバから送られてきた証明書が正しいものであることを検証します。

公開鍵暗号アルゴリズム

DH鍵共有(Diffie-Hellman)

1976年にスタンフォード大学の Diffie と Hellman が考案した鍵交換の方式で、盗聴の可能性のある通信経路上で二者が安全に鍵を交換するための方式です。現在でも様々なプロトコルで利用されています。方式自体は公開鍵暗号ではありませんが、この方式が公開鍵暗号の概念の元となり、両者は公開鍵暗号の論文を掲載しました。

RSA(Rivest, Shamir, Adleman)

Diffie と Hellman の公開鍵暗号論文に触発され、MIT の Rivest, Shamir, Adleman の3名が開発したアルゴリズムです。大きな素数を掛け合わした値を素因数分解することが難しいことを利用しています。3名は後に RSA 社を設立します。RSA のアルゴリズムは RSA 社が特許を保持していましたが、2000年9月6日に有効期限が切れました。現在でも公開暗号のデファクトとしてよく利用されています。

ElGamal暗号

1984年にエジプトの Taher Elgamal が発表したアルゴリズムです。位数が大きな群の離散対数問題が困難であることを利用しています。同氏はまた、同様の技術を電子署名に応用した、ElGamal署名も発表しています。

文字列化

文字列化とは

暗号化したデータなどのバイナリデータを文字列として表現する際の方式です。下記のものなどがあります。

16進表記

バイナリデータを単純に16進数文字列で表現する方法です。\x01\x02\x03 というデータは 010203 となります。データ量は2倍となります。01 02 03 の様に間にスペースを入れる場合もあります。この場合のデータ量は3倍となります。

BASE64

xxxxxxxx yyyyyyyy zzzzzzzz という3バイトの各ビットを、00xxxxxx 00xxyyyy 00yyyyzz 00zzzzzz という4バイトのデータに変換します。元データが3の倍数でない場合は末尾に = または == を付加します。例えば \x01\x02\x03\x04 というデータは AQIDBA== となります。データ量は約4/3倍となります。

PEM(Privacy Enhanced Mail)

証明書関連では PEM 形式のファイルがよく利用されます。データを BASE64 で文字列化し、適度な長さで改行し、先頭と末尾に -----BEGIN ○○----- と -----END ○○----- を付加します。

-----BEGIN CERTIFICATE-----
MIIGbzCCBFegAwIBAgIICZftEJ0fB/wwDQYJKoZIhvcNAQELBQAwfDELMAkGA1UE
  :
y702dmPTKEdEfwhgLx0LxJr/Aw==
-----END CERTIFICATE-----

Copyright (C) 2021-2022 杜甫々
初版:2021年5月2日 最終更新:2022年1月23日
http://www.tohoho-web.com/ex/encryption.html