エニグマ暗号機

エニグマのアルゴリズムを使った暗号ユニットです。

// 参考:
// http://ja.wikipedia.org/wiki/%E3%82%A8%E3%83%8B%E3%82%B0%E3%83%9E_(%E6%9A%97%E5%8F%B7%E6%A9%9F)
// http://www.infonet.co.jp/ueyama/ip/history/enigma.html
unit enigmaunit;

interface

uses
  Classes,SysUtils;

const
  // 利用できる文字種:
  // ・文字種は偶数でなくてはならない。
  //  (リフレクタを使う構造上の制限)
  // ・オリジナルのエニグマで利用できる文字種は'A'..'Z'の26文字だが、
  //  このユニットでは"0x20..0x7e" + 0x09(TAB)の96文字が使える。
  CHAR_MAX = 96;

  // ロータの数:
  // ・ロータを増やす場合にはRotors配列を増やし、ROTOR_MAX定数を増やす必要がある。
  // ・"ロータ数=キーの長さ"となる。
  // ・ロータ数は任意
  ROTOR_MAX = 3;

// エンコード&デコード
function Exec_Enigma(Src, Key: String):String;

// ロータ用テーブル生成
function Genareate_Rotor:String;
// ロータ用テーブル生成
function Genareate_Refrector:String;
// キーの自動生成
function GenarateKey:String;

implementation

{ Char2Index Begin}
function Char2Index(C:Char):Byte;
begin
  if C = #$09 then
    result := $5F
  else
    result := Ord(C) - $20;
end;
{ Char2Index End}

{ Index2Char Begin}
function Index2Char(B:Byte):Char;
begin
  if B = $5F then
    result := #$09
  else
    result := Chr(B + $20);
end;
{ Index2Char End}

// エンコード&デコード
// -----------------------------------------------------------------------------
function Exec_Enigma(Src, Key: String):String;
const
  // ロータ:
  // ・3つのロータを定義している。
  // ・ロータを増やす場合にはRotors配列を増やし、ROTOR_MAX定数を増やす必要がある。
  // ・Genareate_Rotorでロータを再生成可能
  Rotors:array[0..ROTOR_MAX-1,0..CHAR_MAX-1of SmallInt =
    (
      // fast rotor
      (
         1383674354708114362671,  85555,  863,
        -1176223040355030265122,-19,-28341231,
         -3,  1, -855, -5,  1, -6,-2319,-17,-2423, -3,-3427, -2,
        -25,-22,-3526,-40,-5031,-3518,-20, -6271019,-5629,
        -36,-48,-62,-571510,-27,-24, -7,-71, -3,-741217,-21,-65,
        -71,-42,-21,-53,-33,-52, -4,  4,-63,  6, -1,-36,-56,-86,-30,-41
      ),
      // midium rotor
      (
         6427,  431906943134953796926,  6,  473,
         -4,-103042204228634430306038504045,
          3,-32,  937, -3,-323344, -3,-32,-13,-1733,  6, -2,-30,
        -3436,-2914, -6,-28,-50,-53, -3,-18, -4,  0,-19,-50,-47,-41,
         -626,-34,-64,-37,-53,-43,-47,  1,  918,-15,-31, -8,-48,-12,
        -70,-29, -4,-60, -9, -1,-86,-74,-52,  4, -9,-49, -2,-85,  1,-48
      ),
      // slow rotor
      (
         4473167765195566, -340603643,  21722,
         4647,-12155833,  8,  3,-125831, -2,  448,-10,-31,
         1826, -140493134,  25546,-292042,-3143,-24,
        -29,-10,-15,-50,-25,-45, -938,-35,-15,-47,  1,-3123,-10,-53,
         30,-61,-4421,-32, -3, -5,-68,-56,-27,  7,  7,-23,-60,-69,  0,
        -52,-14,-31,  9, -8,-14,-28,  4,-32,-46,-52,-84,-44,-53,-92, -5
      )
    );
  // リフレクタ(反転ロータ):
  // ・Genareate_Refrectorでリフレクタを再生成可能
  Refrector:array[0..CHAR_MAX-1of Byte =
    (
      $5B,$21,$5E,$0E,$49,$4A,$58,$36,$57,$3C,$30,$53,$3E,$46,$03,$16,
      $3A,$34,$23,$31,$1B,$25,$0F,$22,$2D,$24,$38,$14,$28,$4F,$32,$43,
      $50,$01,$17,$12,$19,$15,$2E,$41,$1C,$2C,$45,$5D,$29,$18,$26,$5C,
      $0A,$13,$1E,$48,$11,$39,$07,$56,$1A,$35,$10,$5A,$09,$51,$0C,$4C,
      $54,$27,$4D,$1F,$44,$2A,$0D,$55,$33,$04,$05,$4E,$3F,$42,$4B,$1D,
      $20,$3D,$59,$0B,$40,$47,$37,$08,$06,$52,$3B,$00,$2F,$2B,$02,$5F
    );
var
  i,l:Integer;
  Idx:Byte;
  Dmy:String;
  R_Idx:array [0..ROTOR_MAX-1of Byte;
  F_RT:array [0..ROTOR_MAX-1,0..CHAR_MAX-1of Byte;
  R_RT:array [0..ROTOR_MAX-1,0..CHAR_MAX-1of Byte;
  { Create_Rotor Begin}
  procedure Create_Rotor(Index,Index2:Integer);
  var
    i:Integer;
  begin
    for i:=0 to CHAR_MAX-1 do
      begin
        F_RT[Index,i] := (Rotors[Index,(Index2+i) mod CHAR_MAX] + (Index2 + i)) mod CHAR_MAX;
        R_RT[Index,F_RT[Index,i]] := i;
      end;
  end;
  { Create_Rotor End}
  { Rotate Begin}
  procedure Rotate(i:Integer);
  begin
    Inc(R_Idx[i]);
    if R_Idx[i] = CHAR_MAX then
      begin
        R_Idx[i] := 0;
        if i < ROTOR_MAX then
          Rotate(i+1);
      end;
    Create_Rotor(i,R_Idx[i]);
  end;
  { Rotate End}
begin
  result := '';
  // 入力チェック
  if Src = '' then
    Exit;
  if Length(Key) < ROTOR_MAX then
    Exit;
  // キーからロータ位置を設定
  for i:=0 to ROTOR_MAX-1 do
    begin
      R_Idx[i] := Char2Index(key[i+1]);
      Create_Rotor(i,R_Idx[i]);
    end;
  // 文字列を暗号化/復号
  Dmy := '';
  for i:=1 to Length(Src) do
    begin
      Idx := Char2Index(Src[i]);
      for l:=0 to ROTOR_MAX-1 do
        Idx := F_RT[l,Idx];
      Idx := Refrector[Idx];
      for l:=ROTOR_MAX-1 downto 0 do
        Idx := R_RT[l,Idx];
      Dmy := Dmy + Index2Char(Idx);
      // ロータ回転
      Rotate(0);
    end;
  result := Dmy;
end;

// ロータ用テーブル生成
// -----------------------------------------------------------------------------
function Genareate_Rotor:String;
var
  i,Idx:Byte;
  List:TList;
  Dmy:String;
  Dmy2:String;
begin
  Randomize;
  result := '';
  List := TList.Create;
  try
    for i:=0 to CHAR_MAX-1 do
      List.Add(Pointer(i));
    Dmy := '';
    for i:=0 to CHAR_MAX-1 do
      begin
        Idx := Random(List.Count);
        Dmy2 := IntToStr(Integer(List.Items[Idx]) - i);
        Dmy  := Dmy + StringOfChar(' ',3 - Length(Dmy2)) + Dmy2;
        List.Delete(Idx);
        if i < CHAR_MAX - 1 then
          Dmy := Dmy + ',';
        if (i mod 16) = 15 then
          Dmy := Dmy + #$0D#$0A;
      end;
  finally
    List.Free;
  end;
  result := Dmy;
end;

// リフレクタ用テーブル生成
// -----------------------------------------------------------------------------
function Genareate_Refrector:String;
var
  i,Idx,Idx2:Byte;
  Idx3:Integer;
  List:TList;
  Dmy:String;
  RF:array [0..CHAR_MAX-1of Byte;
begin
  Randomize;
  result := '';
  List := TList.Create;
  try
    for i:=0 to CHAR_MAX-1 do
      begin
        List.Add(Pointer(i));
        RF[i] := $FF;
      end;
    for i:=0 to CHAR_MAX-1 do
      begin
        if RF[i] <> $FF then
          Continue;
        Idx  := Random(List.Count);
        Idx2 := Integer(List.Items[Idx]);
        RF[i]    := Idx2;
        RF[Idx2] := i;
        List.Delete(Idx);
        Idx3 := List.IndexOf(Pointer(i));
        if Idx3 > -1 then
          List.Delete(Idx3);
      end;
    Dmy := '';
    for i:=0 to CHAR_MAX-1 do
      begin
        Dmy := Dmy + Format('$%.2x',[RF[i]]);
        if i < CHAR_MAX - 1 then
          Dmy  := Dmy  + ',';
        if (i mod 16) = 15 then
          Dmy  := Dmy  + #$0D#$0A;
      end;
  finally
    List.Free;
  end;
  result := Dmy;
end;

// キーの自動生成
// -----------------------------------------------------------------------------
// ※自動生成キーにはTAB文字が含まれない(入力の便宜上)。
function GenarateKey:String;
var
  Dmy:String;
  i:Integer;
begin
  Randomize;
  Dmy := '';
  for i:=0 to ROTOR_MAX-1 do
    Dmy := Dmy + Index2Char(Random(CHAR_MAX-1));
  result := Dmy;
end;
end.

 空行/コメント行/自動生成ロジック(ロータ/リフレクタ/キー)を除けば実質120行弱のエニグマユニット です。このユニットで実際に暗号化を行う前に、配列定数Rotors[]とRefrector[]をGenareate_Rotor() /Genareate_Refrector()で生成して該当ソースコードを修正して下さい。

 プラグボードに相当する文字テーブルは"[0x20(0x00)..0x7e(0x5e),0x09(0x5f)]"固定ですが、これも定数化してプラグボードもエミュレートしてもいいかもしれません。

 このユニットはエニグマの動きをエミュレートしていますので、無駄な動きをしています(本当にロータを回転させたりしてます)。ロータテーブルをポインタ(双方向リストの配列とか)で処理するようにするとか、紙エニグマの動きを模倣すればもっとスマートな実装ができると思います。

 日本語をエニグマに通すには
 等、ちょっとした工夫が必要です。