はじめに
ふとした深夜テンションで、「HSP でエニグマっぽい暗号機つくれないかな」と思って書いたのがこの エニグルマ (Enigruma) です。
- 本物の暗号としては絶対に使ってはいけない
- 「なんとなくエニグマっぽいものを回して遊ぶ」ための玩具
という前提でゆるく読んでもらえれば十分です。
特に、
- HSP 標準の疑似乱数をそのまま使っている
- MD5 をそのまま使っている(ソルトやストレッチなし)
- 実装も「とりあえず動けばヨシ!」寄り
なので、実用暗号用途は厳禁です。 あくまで「仕組みの雰囲気を楽しむサンプル」として扱ってください。
エニグルマのアイデアざっくり
実装のコンセプトは、歴史的なローター暗号機「エニグマ」をだいたい参考にしています。
- 256 文字(バイト)単位での置換暗号
- 複数のローター(rotor)を直列に並べて変換
- 最後に反射板(reflector)で折り返し
- 各ローターはステップ動作で配線が回転し、巨大な周期を持つ
- パスワードから「ハッシュテーブル」を作り、入出力を一度読み替えてからローターに通す
ギア噛み合いのようにローターのステップ周期をずらしているため、 理論上の組み合わせ周期は 10^36 くらいはある……はず、という雑な設計です。
全体構成
モジュール構成はこんな感じです。
rotorモジュール 1 個のローター(配線 & 逆配線 & ステップ動作)reflectorモジュール 反射板の配線enigrumaモジュール 複数ローターの管理、ハッシュによる読み替え、Base64 入出力など
最後に、使用例として
- 暗号化用オブジェクト
enc - 復号用オブジェクト
dec
を同じシード・同じパスワードで初期化し、
encrypt_base64 / decrypt_base64 で実際に文字列を暗号化・復号しています。
コード全文
以下がエニグルマ本体のコードです。 そのまま HSP に貼り付けて動かせます。
// Enigruma暗号モジュール
// エニグマ(Enigma)を参考になんとなくで作成。
#include "hspinet.as"
#module rotor f,fi
#modinit
dim f,256
dim fi,256
for i,0,256
f(i)=i
fi(i)=i
next
// ローターの結線交換
repeat 500+rnd(1000)
wait 0
a=rnd(256):b=rnd(256)
fa=f(a):fb=f(b)
tmp=fi(fa): fi(fa)=fi(fb):fi(fb)=tmp
f(a)=fb:f(b)=fa
loop
return
#modcfunc rotor_crypt int x
return f(x)
#modcfunc rotor_decrypt int x
return fi(x)
#modfunc rotor_step
// 1ステップ回す
repeat 256
wait 0
a=cnt:b=(cnt+1)\256
fa=f(a):fb=f(b)
tmp=fi(fa): fi(fa)=fi(fb):fi(fb)=tmp
f(a)=fb:f(b)=fa
loop
return
#global
#module reflector f
#modinit
dim f,256
dim fi,256
for i,0,256
f(i)=i
next
// 反射板の結線交換
repeat 50000+rnd(10000)
wait 0
a=rnd(256):b=rnd(256)
fa=f(a):fb=f(b)
if(fa==a)^(fb==b){
if(fa==a){
f(a)=fb:f(fb)=a:f(b)=b
}else{
f(b)=fa:f(fa)=b:f(a)=a
}
}else{
f(fa)=b:f(fb)=a
f(a)=fb:f(b)=fa
}
loop
return
#modcfunc reflector_crypt int x
return f(x)
#global
#module enigruma r,hash,latch,gear,ref
#modinit int num
// ローター数を 4〜25 に制限
gear=limit(num,4,25)
// 各ローターのステップ周期をずらすための素数テーブル
prime=1,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
dim latch,gear
repeat gear
newmod r,rotor
latch(cnt)=rnd(prime(cnt))
loop
newmod ref,reflector
dim hash,16
return
#define global sethash(%1,%2,%3=-1) _sethash %1,%2,%3
#modfunc _sethash str inp,int len,local long,local in
in=inp
long=len:if(long==-1):long=strlen(in)
// 本当は salt を混ぜてごちゃごちゃさせるべき
varmd5@ md5,in,long
hl=int("$"+strmid(md5,0,8)),int("$"+strmid(md5,8,8)),int("$"+strmid(md5,16,8)),int("$"+strmid(md5,24,8))
// ハッシュ値 16 個を「重複なし」で作る
repeat 16
hash(cnt)=peek(hl,cnt)
for i,0,cnt
if(hash(i)==hash(cnt)){
hash(cnt)=(hash(cnt)+1)\256
i=-1
_continue
}
next
loop
return
#modcfunc _encrypt_base64 str in, int len,local long
if(len==-1):long=strlen(in):else:long=len
buf=_crypt(thismod,in,long)
b64encode@ b64,buf,long
return b64
#modcfunc _decrypt_base64 str ins,local long,local in
in=ins
long=strlen(in)/4*3
if(instr(in,0,"=")!=-1):long-=strlen(in)-instr(in,0,"=")
b64decode@ b64,in
return _crypt(thismod,b64,long)
#modcfunc _crypt str ins,int len,local long,local in
in=ins
long=len
sdim out,long
for i,0,long
tmp=peek(in,i)
// 入力読み替え(ハッシュテーブルによる 2 つ組入れ替え)
for j,0,16
if(tmp==hash(j)){
if j&1:tmp=hash(j-1):else:tmp=hash(j+1)
_break
}
next
// 入力ギア集(ローター列)を通す
for j,0,gear
tmp=rotor_crypt(r(j),tmp)
next
// 反射板
tmp=reflector_crypt(ref,tmp)
// 出力ギア集(逆向きにローター列を通す)
for j,gear-1,-1,-1
tmp=rotor_decrypt(r(j),tmp)
latch(j)=(latch(j)+1)\prime(j)
if(latch(j)==0):rotor_step(r(j))
next
// 出力読み替え(再度ハッシュテーブルで入れ替え)
for j,0,16
if(tmp==hash(j)){
if j&1:tmp=hash(j-1):else:tmp=hash(j+1)
_break
}
next
poke out,i,tmp
next
return out
#define global ctype encrypt_base64(%1,%2,%3=-1) _encrypt_base64(%1,%2,%3)
#define global ctype decrypt_base64(%1,%2)_decrypt_base64(%1,%2)
#global
// ------------------------
// 使用例
// ------------------------
password="password"
text="エニグルマ/テスト"
randomize
seed=(rnd(256)<<24)+(rnd(256)<<16)+(rnd(256)<<8)+rnd(256)
// 暗号化側の状態
randomize seed
newmod enc,enigruma,25
sethash enc,password
// 復号側の状態(同じ seed / 同じパスワード)
randomize seed
newmod dec,enigruma,25
sethash dec,password
// 注意: dec=enc と書いても同じオブジェクトを指すだけなのでダメ
s=encrypt_base64(enc,text)
mes s
p=decrypt_base64(dec,s)
mes p
実装のポイント解説
1. ローター (rotor モジュール)
f[]が「入力 → 出力」の配線テーブルfi[]が「出力 → 入力」の逆配線テーブル- 初期化時に
500 + rnd(1000)回ランダムな入れ替えを行って、配線をシャッフル rotor_stepでローター全体を「1 ステップ」回すような処理を実装
ここは「1 バイト版のエニグマローター」と思って読めばだいたい雰囲気がつかめます。
2. 反射板 (reflector モジュール)
エニグマの特徴である「反射板」も実装しています。
f[x] = yとf[y] = xになるようにペアを結ぶ- 自己ループ(
f[x] = x)を避けながらランダムに結線 - 50000 + rnd(10000) 回ほどひたすら交換するので、相当カオスな配線になる
ここで折り返すことで、復号処理も「暗号化と同じ手順」で済む構造になっています。
3. ハッシュによる入出力読み替え
sethash でパスワードから 16 バイトのテーブルを作っています。
varmd5@で MD5 ハッシュを取得- そこから 16 バイトを抜き出して、重複しないように調整
-
_crypt内で- 暗号処理の前に 2 つ組で入れ替え(入力読み替え)
- 暗号処理の後にも同じテーブルで再度入れ替え(出力読み替え)
この「ハッシュテーブルがないと復号できない」状態を、 一応の「鍵」らしきものとして使っています。
※本気で安全にしたいなら MD5 ではなく、ソルト入りの KDF などを使うべきですが、 このコードはそこまでやっていません。
4. ローターのステップ制御
enigruma 初期化で使っている prime[] がポイントです。
prime=1,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97
- 各ローターごとに
latch[j]を持つ - 1 文字処理するごとに右側ローターから
latch[j]をインクリメント prime[j]で割った余りが 0 になったときにrotor_stepを呼ぶ
これにより、ローターごとに異なる周期でステップする「ギア噛み合い」が再現されます。 周期の LCM がとんでもなく大きくなるので、表面的にはかなり複雑な変換になります。
サンプル実行
先ほどの使用例では、こんな流れになっています。
- 適当な seed を作る
- 暗号化側
encをrandomize seedから初期化 - 復号側
decも同じ seed から初期化 - 両者に同じ
passwordを渡してsethash encrypt_base64(enc, text)で暗号化(Base64 文字列に)decrypt_base64(dec, s)で復号
同じシード&同じパスワードで初期化しているため、 暗号化と復号でローターの配線・配置が一致し、元通りの文字列を取り出せます。
セキュリティ上の注意点
繰り返しになりますが、このコードは 実用暗号としては使わないでください 🧨
理由をざっと挙げると:
- HSP のデフォルト疑似乱数をそのまま使用
- MD5 にソルトもストレッチもなし
- アルゴリズム自体も暗号設計として検証されていない
- 実装も「とりあえず動けばOK」寄り
あくまで
- 「ローター暗号っぽいものを自作してみたい」
- 「HSP でモジュール書いて遊びたい」
- 「自作暗号の沼を覗き込んでみたい」
といった用途に限定するのがおすすめです。
まとめ
- HSP で作ったエニグマ風暗号モジュール「エニグルマ」の紹介でした
- 複数ローター+反射板+ハッシュ読み替えで雰囲気だけはそれっぽく動きます
- ただし暗号としての強度はまったく保証していません
「自作暗号やってみたいけど、とっかかりが欲しい」というときの ネタや読み物として、好きにいじってみてもらえれば嬉しいです。
コメント