エニグマ暗号機

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

// 参考:
// 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 - 1] of SmallInt =
    (
      // fast rotor
      (
         13, 83, 67, 43, 54, 70, 81, 14, 36, 26, 71,  8, 55, 55,  8, 63,
        -11, 76, 22, 30, 40, 35, 50, 30, 26, 51, 22,-19,-28, 34, 12, 31,
         -3,  1, -8, 55, -5,  1, -6,-23, 19,-17,-24, 23, -3,-34, 27, -2,
        -25,-22,-35, 26,-40,-50, 31,-35, 18,-20, -6, 27, 10, 19,-56, 29,
        -36,-48,-62,-57, 15, 10,-27,-24, -7,-71, -3,-74, 12, 17,-21,-65,
        -71,-42,-21,-53,-33,-52, -4,  4,-63,  6, -1,-36,-56,-86,-30,-41
      ),
      // midium rotor
      (
         64, 27,  4, 31, 90, 69, 43, 13, 49, 53, 79, 69, 26,  6,  4, 73,
         -4,-10, 30, 42, 20, 42, 28, 63, 44, 30, 30, 60, 38, 50, 40, 45,
          3,-32,  9, 37, -3,-32, 33, 44, -3,-32,-13,-17, 33,  6, -2,-30,
        -34, 36,-29, 14, -6,-28,-50,-53, -3,-18, -4,  0,-19,-50,-47,-41,
         -6, 26,-34,-64,-37,-53,-43,-47,  1,  9, 18,-15,-31, -8,-48,-12,
        -70,-29, -4,-60, -9, -1,-86,-74,-52,  4, -9,-49, -2,-85,  1,-48
      ),
      // slow rotor
      (
         44, 73, 16, 77, 65, 19, 55, 66, -3, 40, 60, 36, 43,  2, 17, 22,
         46, 47,-12, 15, 58, 33,  8,  3,-12, 58, 31, -2,  4, 48,-10,-31,
         18, 26, -1, 40, 49, 31, 34,  2, 55, 46,-29, 20, 42,-31, 43,-24,
        -29,-10,-15,-50,-25,-45, -9, 38,-35,-15,-47,  1,-31, 23,-10,-53,
         30,-61,-44, 21,-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 - 1] of 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 - 1] of Byte;
  F_RT: array [0..ROTOR_MAX - 1, 0..CHAR_MAX - 1] of Byte;
  R_RT: array [0..ROTOR_MAX - 1, 0..CHAR_MAX - 1] of 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 - 1] of 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