はじめに

前回はシーザー暗号を実装したのだが、今回は換字暗号を実装してみる。

環境

1
php8.1.9

実行環境

換字暗号について

換字暗号とは

換字暗号についてどのようなものかを知っておく。

換字暗号は、アルファベットの順序を変えたり、特定のルールに基づいて置き換えたりすることによって平文を暗号化する手法。
シーザー暗号とは異なり、単なるシフトではなく、各文字が異なる文字に対応づけられる。
この暗号は、平文の文字と暗号文の文字の対応表を用いて通信の秘匿性を確保するようになっている。

具体例

換字暗号は対応表があり、例えば A->E に、B->Cにといった感じで文字を置き換えることで元の文面の文字を推測しにくい形としている。

例えば以下のようになる。

平文: “HELLO”
換字対応表: H → Q, E → X, L → A, O → P

暗号文: “QXEEP”

このようにして、平文の各文字が対応表に基づいて置き換えられ、新しい文字列が生成されるようになっている。

そのため、シーザー暗号では暗号の鍵はシフト数であったが、換字暗号では暗号の鍵は、対応表となる。

実装

手軽にPHPで実装してみた。 以下のようになる。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
<?php

function createSubstitutionMap() {
    $originalAlphabet = str_split('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
    $shuffledAlphabet = str_shuffle('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz');
   
    return array_combine($originalAlphabet, str_split($shuffledAlphabet));
}

function encryptSubstitutionCipher($text, $substitutionMap) {
    $result = "";

    for ($i = 0; $i < strlen($text); $i++) {
        $char = $text[$i];

        if (ctype_alpha($char)) {
            if (isset($substitutionMap[$char])) {
                $result .= $substitutionMap[$char];
            } else {
                $result .= $text[$i];
            }
        } else {
            $result .= $char;
        }
    }

    return $result;
}

function decryptSubstitutionCipher($text, $substitutionMap) {
    $inverseMap = array_flip($substitutionMap);

    $result = "";

    for ($i = 0; $i < strlen($text); $i++) {
        $char = $text[$i];

        if (ctype_alpha($char)) {
            if (isset($inverseMap[$char])) {
                $result .= $inverseMap[$char];
            } else {
                $result .= $text[$i];
            }
        } else {
            $result .= $char;
        }
    }

    return $result;
}

// テスト
$message = "Hello, World!";
$substitutionMap = createSubstitutionMap();

echo "メッセージ: " . $message . PHP_EOL;

$encryptedMessage = encryptSubstitutionCipher($message, $substitutionMap);
echo "暗号化されたメッセージ: " . $encryptedMessage . PHP_EOL;

$decryptedMessage = decryptSubstitutionCipher($encryptedMessage, $substitutionMap);
echo "復号化されたメッセージ: " . $decryptedMessage . PHP_EOL;

Hello, Worldが平文となっており、それを暗号化したメッセージと復号化したメッセージを出力している。
ではこのコードを実行してみる。すると、以下の出力となる。

実行結果

substitution-cipher-output-1

さてこれで良いのだが、実はこのプログラムは毎回換字の対応表が異なる。
ただ、暗号の鍵となる対応表の情報を出力していないので、この場合の検証はできないことに注意。
プログラムでランダムに対応表を作成している部分、createSubstitutionMap の結果を出力してみると以下のようになっている。(※実行結果で使用した換字の対応表と内容が異なることに注意)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
Array
(
    [A] => J
    [B] => u
    [C] => N
    [D] => F
    [E] => x
    [F] => A
    [G] => C
    [H] => E
    [I] => k
    [J] => q
    [K] => o
    [L] => z
    [M] => V
    [N] => m
    [O] => D
    [P] => w
    [Q] => j
    [R] => W
    [S] => X
    [T] => T
    [U] => R
    [V] => c
    [W] => G
    [X] => U
    [Y] => g
    [Z] => h
    [a] => b
    [b] => r
    [c] => M
    [d] => B
    [e] => P
    [f] => O
    [g] => y
    [h] => e
    [i] => p
    [j] => f
    [k] => t
    [l] => Z
    [m] => s
    [n] => Q
    [o] => a
    [p] => n
    [q] => L
    [r] => d
    [s] => H
    [t] => i
    [u] => v
    [v] => l
    [w] => K
    [x] => S
    [y] => Y
    [z] => I
)

といった感じで、大文字と小文字のアルファベットがそれぞれランダムに割り当てられている。

今回は対応表を実行のたびにランダムで実装したが、もしこの暗号方式を使うのであれば、暗号化側と復号化側で事前に対応表を共有しないと使用できない。

おわりに

換字暗号についてどのようなものかを学んだ。
こちらも比較的わかりやすい部類の暗号方式となっている。
wikipediaの換字式暗号の安全性の欄を見ると、

一般的にアルゴリズムが公開された古典暗号は、安全ではないと考えるべきである。
単純換字は、頻度分析によって解読可能である。同音換字は、文字単位の出現頻度を平均化できるため、単一文字での頻度分析は難しくなるが、2重音字 (digram) や3重音字 (trigram) での頻度分析で解読できる可能性がある。
多表式であっても周期換字で、周期が短い場合には、一致反復率やカシスキー・テスト(頻度分析)などにより周期を特定した上で、頻度分析を行うと解読できる場合がある。
https://ja.wikipedia.org/wiki/換字式暗号より引用

とあり、文字の出現頻度で対応表(変換ルール)が特定される危険性がある。
これも現実では謎解きレベルで使われるものかなと思う。