AI要約
グローバーのアルゴリズム完結編。複雑なハッシュ関数を用いたパスワード解読の実践に挑みます。8桁のハッシュ値から生パスワードを特定します。正解が確率的に浮き上がる量子計算を見てみましょう。
はじめに
前回は3ビットのハッシュ解除を行いました。
グローバーのアルゴリズムの総集編として、複雑なハッシュ関数を通った8桁のハッシュ値から生パスワードを解読してみましょう。
独自のハッシュ関数を作る
バイナリ配列に対する適当なハッシュ関数を作ってみます。パスワードを隠匿するためそれなりに複雑なものがいいですね。入力と出力の長さは同じにします。
逐次XOR
隣同士のビットをドミノ倒しのようにxorで撹拌していきます。これをラウンドとします。
- (q0 xor q1) ➝ q1 を上書き
- (q1 xor q2) ➝ q2 を上書き
- (q2 xor q3) ➝ q3 を上書き
- 中略
- (q6 xor q7) ➝ q7 を上書き
ビットスワップとビット反転
各ラウンドが終わったら特定の2箇所を入れ替え、さらにq0を反転します。
- 1ラウンド終了後:q0 と q1 を入れ替え、q0 を反転
- 2ラウンド終了後:q0 と q2 を入れ替え、q0 を反転
- 3ラウンド終了後:q0 と q3 を入れ替え、q0 を反転
- 中略
- 7ラウンド終了後:q0 と q7 を入れ替え、q0 を反転
この一連の操作によって予測不可能で、かつ、決定論的なハッシュ化が行われます。
Pythonで試してみます。
#生パスワード
password = [0,0,0,0,0,0,0,0,0]
#ハッシュ化の関数
def hash(p):
for i in range(1, 8):
#逐次xor
for j in range(1, 8):
p[j] = (p[j - 1] + p[j]) % 2 # {p[j - 1] xor p[j]} -> p[j]
#ビットスワップ
tmp = p[0]
p[0] = p[i]
p[i] = tmp
#ビット反転
p[0] = 1 - p[0]
return p
print(hash(password))[0, 0, 1, 1, 0, 1, 1, 1, 0]パスワード「00000000」をハッシュ化すると「001101110」になりました。ハッシュ化のアルゴリズムを知っていたとしても、逆方向に解読するのは非常に困難です。しらみつぶしに探索すると28通りもチェックしなければなりません。
量子計算で生パスワードを解読
いよいよ、量子ゲート計算で解読してみましょう。
今回、ハッシュ値が「00001111」だとします。生パスワードを解読できるでしょうか?
from qiskit import QuantumCircuit, transpile
from qiskit_aer import AerSimulator
#初期化 入力8 + 補助1
qc = QuantumCircuit(9)
#同時計算のため入力ビットを重ね合わせにする
qc.h([0, 1, 2, 3, 4, 5, 6, 7])
#重ね合わせのままハッシュ化する
for i in range(1, 8):
for j in range(1, 8):
qc.cx(j - 1, j) # {q(j - 1) xor q(j)} -> q(j)
qc.swap(0, i)
qc.x(0)
#鏡の準備 お作法
qc.x(8)
qc.h(8)
#鏡を特殊な向きに傾けて攻撃を反射させる 暗号文(この場合は'00001111')の存在確率だけを反転させる
qc.x([0, 1, 2, 3])
qc.mcx([0, 1, 2, 3, 4, 5, 6, 7], 8) # mcxはccxの拡張バージョン
qc.x([0, 1, 2, 3])
#重ね合わせのままハッシュ化の逆操作を行って平文に戻す ➝ これが出力として観測される
for i in range(7, 0, -1):
qc.x(0)
qc.swap(0, i)
for j in range(7, 0, -1):
qc.cx(j - 1, j)
#ディフュージョン 適当な1ビット(この場合は7番ビット)を標的にして増幅のおまじないをかける
qc.h([0, 1, 2, 3, 4, 5, 6, 7])
qc.x([0, 1, 2, 3, 4, 5, 6, 7])
qc.h(7)
qc.mcx([0, 1, 2, 3, 4, 5, 6], 7)
qc.h(7)
qc.x([0, 1, 2, 3, 4, 5, 6, 7])
qc.h([0, 1, 2, 3, 4, 5, 6, 7])
#補助ビットの重ね合わせを解除して解の重複を避ける
qc.h(8)
#計算
qc.measure_all()
backend = AerSimulator(method='matrix_product_state')
t_qc = transpile(qc, backend) #トランスパイルが必要
result = backend.run(t_qc, shots=1000).result().get_counts()
#解の確認
for r in result:
print(f'{r[::-1]} | {result[r]}/1000')
001011001 | 1/1000
110110101 | 2/1000
000111101 | 3/1000
001101001 | 5/1000
011010011 | 5/1000
100000101 | 5/1000
010110111 | 2/1000
110011011 | 43/1000
100001101 | 2/1000
001100011 | 5/1000
110100111 | 3/1000
111010111 | 5/1000
011111011 | 8/1000
(以下略)最大で28通りの解が観測されますが、その中にひときわ観測回数の多い解がありました。
「11001101」
これが生のパスワードでしょうか??
確認してみます。
#生パスワード
password = [1,1,0,0,1,1,0,1]
print(hash(password))[0, 0, 0, 0, 1, 1, 1, 1]ちゃんと 00001111 になりました。大成功!ハッシュ値から生パスワードを解読することができました!
おわりに
以上、数式を使わずにグローバーのアルゴリズムを体験しました。遊戯王カードのたとえはもうちょっといい感じならなかったかなぁ。。重ね合わせてエクシーズ召喚とか…?
ちなみに暗号の解読は「平文」と「鍵」を見つける必要がありますが、なんかまあ似たようなノリでいけるとGemini先生がおっしゃっています。気が向いたらそれも記事にしますよってに。
OK、わかったぞ~♪という方はリツイートとかリアクションをお願いしますね _(-ω-`_)⌒)_ あとコーヒーを奢ってくれ

