目次

「暗号化は現代のセキュリティの基盤」——これはコンピュータサイエンスの世界で広く信じられている命題だ。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 effect1 ビットの変更でハッシュ値の約半分が変化する
# 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 主要なハッシュアルゴリズム

アルゴリズムハッシュ長安全性用途
MD5128 ビット破裂済み(衝突可能)非セキュリティ用途
SHA-1160 ビット危険(衝突攻撃可能)非推奨
SHA-256256 ビット安全仮想通貨、証明書
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-20482048 ビット112 ビット相当遅い
RSA-40964096 ビット128 ビット相当とても遅い
ECDSA P-256256 ビット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 つに大別される:

  1. ハッシュ関数:データの指紋作成(SHA-256)
  2. 共通鍵暗号:高速な暗号化(AES-GCM)
  3. 公開鍵暗号:鍵配送問題の解決(RSA、ECDH)
  4. デジタル署名:真正性の証明(RSA 署名、ECDSA)

現代のセキュアなシステム(HTTPS、Signal、ブロックチェーン)は、これらを適切に組み合わせて使っている。

免責事項 — 掲載情報は執筆時点のものです。料金・機能は変更される場合があります。最新情報は各公式サイトをご確認ください。