はじめに

ふとした深夜テンションで、「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] = yf[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 がとんでもなく大きくなるので、表面的にはかなり複雑な変換になります。


サンプル実行

先ほどの使用例では、こんな流れになっています。

  1. 適当な seed を作る
  2. 暗号化側 encrandomize seed から初期化
  3. 復号側 dec も同じ seed から初期化
  4. 両者に同じ password を渡して sethash
  5. encrypt_base64(enc, text) で暗号化(Base64 文字列に)
  6. decrypt_base64(dec, s) で復号

同じシード&同じパスワードで初期化しているため、 暗号化と復号でローターの配線・配置が一致し、元通りの文字列を取り出せます。


セキュリティ上の注意点

繰り返しになりますが、このコードは 実用暗号としては使わないでください 🧨

理由をざっと挙げると:

  • HSP のデフォルト疑似乱数をそのまま使用
  • MD5 にソルトもストレッチもなし
  • アルゴリズム自体も暗号設計として検証されていない
  • 実装も「とりあえず動けばOK」寄り

あくまで

  • 「ローター暗号っぽいものを自作してみたい」
  • 「HSP でモジュール書いて遊びたい」
  • 「自作暗号の沼を覗き込んでみたい」

といった用途に限定するのがおすすめです。


まとめ

  • HSP で作ったエニグマ風暗号モジュール「エニグルマ」の紹介でした
  • 複数ローター+反射板+ハッシュ読み替えで雰囲気だけはそれっぽく動きます
  • ただし暗号としての強度はまったく保証していません

「自作暗号やってみたいけど、とっかかりが欲しい」というときの ネタや読み物として、好きにいじってみてもらえれば嬉しいです。