エニグマ暗号機

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

// 参考:
// 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 - 10..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 - 10..CHAR_MAX - 1of Byte;
  R_RT: array [0..ROTOR_MAX - 10..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)]" 固定ですが、これも定数化してプラグボードもエミュレートしてもいいかもしれません。

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

 日本語をエニグマに通すには

等、ちょっとした工夫が必要です。


 BACK