Skip to content

Part I: 並行処理の基礎

概要

本章では、並行処理を学ぶ前提として逐次処理(Sequential Processing)の基本を理解します。パスワードクラッキングを例に、逐次処理の特徴と限界を確認します。


第2章: 逐次処理

逐次処理とは

逐次処理は、タスクを一つずつ順番に実行する最も基本的な処理方式です。

uml diagram

パスワードクラッキングの例

ブルートフォース(総当たり)でパスワードを解読するプログラムを通じて、逐次処理の特徴を学びます。

実装の全体像

#!/usr/bin/env python3

"""パスワードクラッキング(逐次処理版)"""

import time
import math
import hashlib
import typing as T


def get_combinations(*, length: int, min_number: int = 0,
                     max_number: T.Optional[int] = None) -> T.List[str]:
    """全ての可能なパスワード組み合わせを生成"""
    combinations = []
    if not max_number:
        max_number = int(math.pow(10, length) - 1)

    for i in range(min_number, max_number + 1):
        str_num = str(i)
        zeros = "0" * (length - len(str_num))
        combinations.append("".join((zeros, str_num)))
    return combinations


def get_crypto_hash(password: str) -> str:
    """パスワードの暗号ハッシュを計算"""
    return hashlib.sha256(password.encode()).hexdigest()


def check_password(expected_crypto_hash: str,
                   possible_password: str) -> bool:
    """パスワードが一致するか確認"""
    actual_crypto_hash = get_crypto_hash(possible_password)
    return expected_crypto_hash == actual_crypto_hash


def crack_password(crypto_hash: str, length: int) -> None:
    """ブルートフォースでパスワードを解読"""
    print("Processing number combinations sequentially")
    start_time = time.perf_counter()

    combinations = get_combinations(length=length)
    for combination in combinations:
        if check_password(crypto_hash, combination):
            print(f"PASSWORD CRACKED: {combination}")
            break

    process_time = time.perf_counter() - start_time
    print(f"PROCESS TIME: {process_time}")


if __name__ == "__main__":
    # "12345678" のSHA-256ハッシュ
    crypto_hash = \
        "e24df920078c3dd4e7e8d2442f00e5c9ab2a231bb3918d65cc50906e49ecaef4"
    length = 8
    crack_password(crypto_hash, length)

コードの解説

1. パスワード組み合わせの生成

def get_combinations(*, length: int, min_number: int = 0,
                     max_number: T.Optional[int] = None) -> T.List[str]:
    """全ての可能なパスワード組み合わせを生成"""
    combinations = []
    if not max_number:
        # 8桁の場合: 10^8 - 1 = 99,999,999
        max_number = int(math.pow(10, length) - 1)

    for i in range(min_number, max_number + 1):
        str_num = str(i)
        # 先頭をゼロ埋め: 1 → "00000001"
        zeros = "0" * (length - len(str_num))
        combinations.append("".join((zeros, str_num)))
    return combinations

8桁の数字パスワードの場合、00000000 から 99999999 まで 1億通りの組み合わせを生成します。

2. ハッシュ関数

def get_crypto_hash(password: str) -> str:
    """パスワードの暗号ハッシュを計算"""
    return hashlib.sha256(password.encode()).hexdigest()

SHA-256 を使用してパスワードをハッシュ化します。実際のシステムでは、パスワードは平文ではなくハッシュ値として保存されます。

3. パスワードチェック

def check_password(expected_crypto_hash: str,
                   possible_password: str) -> bool:
    actual_crypto_hash = get_crypto_hash(possible_password)
    return expected_crypto_hash == actual_crypto_hash

候補パスワードのハッシュと、保存されているハッシュを比較します。

パフォーマンス測定

start_time = time.perf_counter()
# ... 処理 ...
process_time = time.perf_counter() - start_time
print(f"PROCESS TIME: {process_time}")

time.perf_counter() は高精度のパフォーマンスカウンターで、経過時間の測定に適しています。


逐次処理の特徴

利点

利点 説明
シンプル 実装が簡単で理解しやすい
予測可能 実行順序が決定的
デバッグ容易 問題の特定が容易
競合なし 共有リソースの競合が発生しない

限界

限界 説明
遅い 1億通りを1つずつ処理するため時間がかかる
リソース非効率 マルチコア CPU を活用できない
スケールしない データ量に比例して処理時間が増加

なぜ並行処理が必要か

8桁のパスワードクラッキングでは、1億通りの組み合わせを順番にチェックします。

uml diagram

4コア CPU があっても、逐次処理では1コアしか使用されません。並行処理を使えば、複数コアに作業を分散できます。

uml diagram


次のステップ

Part II では、プロセスとスレッドを使って並行処理を実装します。パスワードクラッキングを並列化し、処理時間を大幅に短縮する方法を学びます。


参考コード