目次
「暗号化は現代のセキュリティの基盤」——これはコンピュータサイエンスの世界で広く信じられている命題だ。HTTPS、パスワード保管、ブロックチェーン、すべて暗号技術の上に成り立っている。本稿では主要な暗号技術の仕組みを実装例とともに体系的に解説する。
暗号化の 3 つの目的
| 目的 | 説明 | 使用技術 |
|---|---|---|
| 機密性 | 第三者に内容を知られない | 共通鍵・公開鍵暗号 |
| 完全性 | 改ざんの検出 | ハッシュ関数 |
| 真正性 | 送信者の特定・なりすまし防止 | デジタル署名 |
1. ハッシュ関数——データの「指紋」を作る
1.1 ハッシュ関数の基本
ハッシュ関数は任意長のデータから固定長の「指紋」(ハッシュ値)を生成する関数だ。
import hashlib
# SHA-256 の使用
data = "Hello, World!"
hash_value = hashlib.sha256(data.encode()).hexdigest()
print(f"データ:{data}")
print(f"SHA-256: {hash_value}")
# 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
1.2 ハッシュ関数の性質
| 性質 | 説明 |
|---|---|
| 一方向性 | ハッシュ値から元データを復元できない |
| 衝突耐性 | 異なるデータが同じハッシュ値になることが極めて稀 |
| avalanche effect | 1 ビットの変更でハッシュ値の約半分が変化する |
# Avalanche effect の確認
data1 = "Hello, World!"
data2 = "Hello, World." # 1 文字だけ違う
hash1 = hashlib.sha256(data1.encode()).hexdigest()
hash2 = hashlib.sha256(data2.encode()).hexdigest()
print(f"data1: {hash1}")
print(f"data2: {hash2}")
# 1 文字の違いでハッシュ値が完全に異なる
# 315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3
# 42b66d07948be0663a1c7e6b56b70706a09a31e599df10e8f910312f460c3ab9
1.3 主要なハッシュアルゴリズム
| アルゴリズム | ハッシュ長 | 安全性 | 用途 |
|---|---|---|---|
| MD5 | 128 ビット | 破裂済み(衝突可能) | 非セキュリティ用途 |
| SHA-1 | 160 ビット | 危険(衝突攻撃可能) | 非推奨 |
| SHA-256 | 256 ビット | 安全 | 仮想通貨、証明書 |
| SHA-3 | 可変(224-512) | 安全 | 新規システム |
1.4 実用例:パスワード保管
import os
import hashlib
def hash_password(password: str, salt: bytes = None) -> tuple[str, bytes]:
"""パスワードをソルト付きでハッシュ化"""
if salt is None:
salt = os.urandom(32) # 32 バイトのランダムソルト
# PBKDF2: ソルト付きパスワードを複数回ハッシュ
# 10 万回のイテレーションで総当たり攻撃を困難に
key = hashlib.pbkdf2_hmac(
'sha256',
password.encode(),
salt,
100000 # イテレーション回数
)
return key.hex(), salt
def verify_password(password: str, stored_hash: str, salt: bytes) -> bool:
"""パスワードの検証"""
computed_hash, _ = hash_password(password, salt)
return computed_hash == stored_hash
# 使用例
password = "my_secure_password"
hashed, salt = hash_password(password)
print(f"ハッシュ値:{hashed}")
print(f"ソルト:{salt.hex()}")
# 検証
print(verify_password("my_secure_password", hashed, salt)) # True
print(verify_password("wrong_password", hashed, salt)) # False
なぜソルトが必要か:
- レインボーテーブル(事前計算されたハッシュ表)攻撃を防止
- 同じパスワードでもユーザーごとに異なるハッシュ値
2. 共通鍵暗号——高速な暗号化
2.1 共通鍵暗号の仕組み
共通鍵暗号は暗号化と復号に同じ鍵を使う。
平文 ─────→ [暗号化 + 鍵] ─────→ 暗号文
暗号文 ───→ [復号 + 鍵] ──────→ 平文
↑
同じ鍵
2.2 AES——現代の標準暗号
AES(Advanced Encryption Standard)は 2001 年に採用された米国標準暗号だ。
| 仕様 | 値 |
|---|---|
| ブロック長 | 128 ビット |
| 鍵長 | 128/192/256 ビット |
| ラウンド数 | 10/12/14 ラウンド(鍵長による) |
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os
def aes_encrypt(plaintext: bytes, key: bytes) -> tuple[bytes, bytes]:
"""AES-256-CBC で暗号化"""
# ランダムな IV(初期化ベクトル)を生成
iv = os.urandom(16)
# 暗号化
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
encryptor = cipher.encryptor()
# パディング(16 バイトの倍数にする)
padding_len = 16 - (len(plaintext) % 16)
padded = plaintext + bytes([padding_len] * padding_len)
ciphertext = encryptor.update(padded) + encryptor.finalize()
return ciphertext, iv
def aes_decrypt(ciphertext: bytes, key: bytes, iv: bytes) -> bytes:
"""AES-256-CBC で復号"""
cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=default_backend())
decryptor = cipher.decryptor()
padded = decryptor.update(ciphertext) + decryptor.finalize()
# パディングを除去
padding_len = padded[-1]
return padded[:-padding_len]
# 使用例
key = os.urandom(32) # 256 ビット鍵
plaintext = b"Secret message!"
ciphertext, iv = aes_encrypt(plaintext, key)
print(f"暗号文(16 進): {ciphertext.hex()}")
decrypted = aes_decrypt(ciphertext, key, iv)
print(f"復号文:{decrypted.decode()}") # Secret message!
2.3 暗号利用モード(Mode of Operation)
| モード | 説明 | 推奨 |
|---|---|---|
| ECB | 単純なブロック暗号化 | ❌ 非推奨(パターンが漏洩) |
| CBC | 前のブロックと XOR | △ 一般的使用 |
| CTR | カウンターモード | ○ 並列処理可能 |
| GCM | 認証付き暗号 | ◎ 推奨(完全性も保証) |
# AES-GCM の使用(推奨)
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
def aes_gcm_encrypt(plaintext: bytes, key: bytes) -> tuple[bytes, bytes]:
"""AES-256-GCM で暗号化(認証付き)"""
nonce = os.urandom(12) # 96 ビット nonce
aesgcm = AESGCM(key)
ciphertext = aesgcm.encrypt(nonce, plaintext, None)
return ciphertext, nonce
def aes_gcm_decrypt(ciphertext: bytes, key: bytes, nonce: bytes) -> bytes:
"""AES-256-GCM で復号(認証検証付き)"""
aesgcm = AESGCM(key)
plaintext = aesgcm.decrypt(nonce, ciphertext, None)
return plaintext
# 使用例
key = os.urandom(32)
plaintext = b"Authenticated message!"
ciphertext, nonce = aes_gcm_encrypt(plaintext, key)
decrypted = aes_gcm_decrypt(ciphertext, key, nonce)
print(f"復号文:{decrypted.decode()}")
3. 公開鍵暗号——鍵配送問題の解決
3.1 公開鍵暗号の仕組み
公開鍵暗号は暗号化と復号で異なる鍵を使う。
送信者 A 受信者 B
│ │
│ 公開鍵(公開) │
│←←←←←←←←←←←←←←←←←←←←←←←│
│ │
│ [公開鍵で暗号化] │
│────────────────────────→│
│ 暗号文 │
│ [秘密鍵で復号]
│ │
3.2 RSA 暗号
RSA は世界初の公開鍵暗号方式だ(1977 年)。
from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
# RSA 鍵ペアの生成
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048, # 2048 ビット
backend=default_backend()
)
public_key = private_key.public_key()
# 暗号化(公開鍵使用)
plaintext = b"Secret message for RSA!"
ciphertext = public_key.encrypt(
plaintext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"暗号文(16 進): {ciphertext.hex()[:64]}...")
# 復号(秘密鍵使用)
decrypted = private_key.decrypt(
ciphertext,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
print(f"復号文:{decrypted.decode()}")
3.3 公開鍵暗号の計算量
| 暗号方式 | 鍵長 | セキュリティレベル | 暗号化速度 |
|---|---|---|---|
| RSA-2048 | 2048 ビット | 112 ビット相当 | 遅い |
| RSA-4096 | 4096 ビット | 128 ビット相当 | とても遅い |
| ECDSA P-256 | 256 ビット | 128 ビット相当 | 高速 |
実用上の注意: 公開鍵暗号は計算コストが高いため、実際のシステムでは以下のように使う:
1. 共通鍵(セッション鍵)をランダム生成
2. 共通鍵でデータを暗号化(AES 高速)
3. 共通鍵を公開鍵で暗号化(RSA で安全に配送)
4. 両方を送信
これはハイブリッド暗号システムと呼ばれ、HTTPS/TLS で使われている。
4. デジタル署名——真正性の証明
4.1 デジタル署名の仕組み
デジタル署名は**「このデータは確かに私が作成した」**ことを証明する技術だ。
送信者 A 受信者 B
│ │
│ [ハッシュ + 秘密鍵] │
│ ↓署名生成 │
│ データ + 署名 │
│─────────────────────────→│
│ [公開鍵で検証]
│ │
│ 真正性確認完了
4.2 RSA 署名の実装
from cryptography.hazmat.primitives.asymmetric import padding as asym_padding
# 署名生成
message = b"Important contract document"
signature = private_key.sign(
message,
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=asym_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print(f"署名(16 進): {signature.hex()[:64]}...")
# 署名検証
try:
public_key.verify(
signature,
message,
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=asym_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
print("署名検証:成功 ✓")
except Exception:
print("署名検証:失敗 ✗")
4.3 デジタル署名の用途
| 用途 | 説明 |
|---|---|
| SSL/TLS 証明書 | Web サイトの真正性を認証局が保証 |
| コード署名 | ソフトウェアが改ざんされていないことを証明 |
| 電子メール(S/MIME) | 送信者の認証とメールの完全性 |
| ブロックチェーン | 取引の正当性を証明 |
5. 鍵交換——安全な通信の開始
5.1 Diffie-Hellman 鍵交換
Diffie-Hellman は公開チャネルで共通秘密を共有する手法だ。
from cryptography.hazmat.primitives.asymmetric import dh
# パラメータ生成
parameters = dh.generate_parameters(generator=2, key_size=2048)
# アリスの鍵ペア生成
alice_private = parameters.generate_private_key()
alice_public = alice_private.public_key()
# ボブの鍵ペア生成
bob_private = parameters.generate_private_key()
bob_public = bob_private.public_key()
# 共有秘密の生成
alice_shared = alice_private.exchange(bob_public)
bob_shared = bob_private.exchange(alice_public)
# 両者が同じ共有秘密を持つ
print(f"Alice: {alice_shared.hex()[:32]}...")
print(f"Bob: {bob_shared.hex()[:32]}...")
print(f"一致:{alice_shared == bob_shared}") # True
仕組みの概要:
1. アリスとボブが公開パラメータ(g, p)を共有
2. アリスは秘密の a を選び、g^a mod p を送信
3. ボブは秘密の b を選び、g^b mod p を送信
4. 両者が (g^b)^a = (g^a)^b = g^(ab) を計算
5. 盗聴者は g^a と g^b から g^(ab) を計算できない(離散対数問題)
5.2 楕円曲線暗号(ECDH)
楕円曲線を使った鍵交換は、より短い鍵長で同等のセキュリティを提供する。
from cryptography.hazmat.primitives.asymmetric import ec
# アリスの鍵ペア(P-256 カーブ)
alice_private = ec.generate_private_key(ec.SECP256R1())
alice_public = alice_private.public_key()
# ボブの鍵ペア
bob_private = ec.generate_private_key(ec.SECP256R1())
bob_public = bob_private.public_key()
# 共有秘密
alice_shared = alice_private.exchange(ec.ECDH(), bob_public)
bob_shared = bob_private.exchange(ec.ECDH(), alice_public)
print(f"共有秘密(16 進): {alice_shared.hex()[:32]}...")
6. 総合的な実装——安全なメッセージング
6.1 エンドツーエンド暗号化
import json
from base64 import b64encode, b64decode
class SecureMessenger:
"""簡易エンドツーエンド暗号化メッセージング"""
def __init__(self):
self.private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
)
self.public_key = self.private_key.public_key()
def get_public_key_pem(self) -> str:
"""公開鍵を PEM 形式で取得"""
return self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
).decode()
def encrypt_message(self, message: str, recipient_public_key) -> dict:
"""メッセージを暗号化"""
from cryptography.hazmat.primitives import serialization
# PEM から公開鍵を読み込み
pub_key = serialization.load_pem_public_key(
recipient_public_key.encode()
)
# ランダムな AES 鍵を生成
aes_key = os.urandom(32)
# メッセージを AES で暗号化
iv = os.urandom(16)
cipher = Cipher(algorithms.AES(aes_key), modes.GCM(iv))
encryptor = cipher.encryptor()
ciphertext = encryptor.update(message.encode()) + encryptor.finalize()
# AES 鍵を RSA で暗号化
encrypted_key = pub_key.encrypt(
aes_key,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# デジタル署名を生成
signature = self.private_key.sign(
message.encode(),
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=asym_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return {
"encrypted_key": b64encode(encrypted_key).decode(),
"iv": b64encode(iv).decode(),
"ciphertext": b64encode(ciphertext).decode(),
"signature": b64encode(signature).decode(),
"sender_public_key": self.get_public_key_pem()
}
def decrypt_message(self, encrypted: dict) -> str:
"""メッセージを復号・検証"""
from cryptography.hazmat.primitives import serialization
# AES 鍵を復号
aes_key = self.private_key.decrypt(
b64decode(encrypted["encrypted_key"]),
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
# メッセージを復号
iv = b64decode(encrypted["iv"])
ciphertext = b64decode(encrypted["ciphertext"])
cipher = Cipher(algorithms.AES(aes_key), modes.GCM(iv))
decryptor = cipher.decryptor()
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
# 署名を検証
sender_public_key = serialization.load_pem_public_key(
encrypted["sender_public_key"].encode()
)
signature = b64decode(encrypted["signature"])
sender_public_key.verify(
signature,
plaintext,
asym_padding.PSS(
mgf=asym_padding.MGF1(hashes.SHA256()),
salt_length=asym_padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
return plaintext.decode()
# 使用例
alice = SecureMessenger()
bob = SecureMessenger()
# アリスがボブに送信
encrypted = alice.encrypt_message("Hello Bob!", bob.get_public_key_pem())
print("暗号化メッセージ送信...")
# ボブが受信・復号
decrypted = bob.decrypt_message(encrypted)
print(f"受信メッセージ:{decrypted}") # Hello Bob!
6.2 実現されているセキュリティ特性
| 特性 | 実装 |
|---|---|
| 機密性 | AES-GCM 暗号化 |
| 完全性 | GCM の認証タグ |
| 真正性 | RSA デジタル署名 |
| 鍵配送 | RSA 鍵カプセル化 |
| 前方秘匿性 | 使い捨てセッション鍵 |
まとめ
暗号技術は以下の 4 つに大別される:
- ハッシュ関数:データの指紋作成(SHA-256)
- 共通鍵暗号:高速な暗号化(AES-GCM)
- 公開鍵暗号:鍵配送問題の解決(RSA、ECDH)
- デジタル署名:真正性の証明(RSA 署名、ECDSA)
現代のセキュアなシステム(HTTPS、Signal、ブロックチェーン)は、これらを適切に組み合わせて使っている。
免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。