アルゴリズムの勉強(3)

暗号

環境

$ node --version
v13.5.0
$ tsc --version
Version 3.7.4

コード

// 線形合同法
//  cryptのコードが線形合同法のrandを使う前提のように見えたので実装
//  TypeScript(というかjavascriptのrandがどうなのかは調べてない)
//  線形合同法を知らない場合はwikipediaなんかで調べればOK
namespace LCG {
  const PARAM_A: number = 1664525;
  const PARAM_C: number = 1013904223;
  const PARAM_M: number = 2147483647;
  let x: number = 1; // default value
  
  export const RAND_MAX: number = 2147483647;

  export function srand(value: number) {
    x = value;
  }
  
  export function rand() {
    x = (x * PARAM_A + PARAM_C) & PARAM_M;
    return x;
  }
};

// 本題はここから

// 文字と文字コードの扱いがC言語と異なるため、暗号化関数と復号関数を別々に用意した
// LCG.srand()の引数が暗号化・復号で共通する「鍵」となっている
// ロジックは暗号化・復号どちらの関数も基本的に同じ
// 文字の扱いと、表示したときのわかりやすさからスペースを挟む・挟まないという違いをつけた

function encrypt(plaintext: string) {
  LCG.srand(12345);

  let cipherletter_array = plaintext.split('').map((c) => {
    let n: number = c.charCodeAt(0);
    let r: number;
    do {
      r = LCG.rand() / ((LCG.RAND_MAX + 1) / 256);
    } while (r >= 256);
    return n ^ r;
  })
  return cipherletter_array.join(' ');
}

function decrypt(ciphertext: string) {
  LCG.srand(12345);

  let plainletter_array = ciphertext.split(' ').map((d) => {
    let n: number = parseInt(d);
    let r: number;
    do {
      r = LCG.rand() / ((LCG.RAND_MAX + 1) / 256);
    } while (r >= 256);
    return String.fromCharCode(n ^ r);
  })
  return plainletter_array.join('');
}

const targettext = "Hello, World!";
console.log("検証テキスト: ", targettext);

const ciphertext = encrypt(targettext);
console.log("暗号化されたテキスト: ", ciphertext);

const plaintext = decrypt(ciphertext);
console.log("復号されたテキスト: ", plaintext);

実行結果

$ tsc crypt.ts && node crypt.js
検証テキスト:  Hello, World!
暗号化されたテキスト:  66 109 122 41 190 21 221 79 94 227 120 24 207
復号されたテキスト:  Hello, World!

所感

線形合同法のコードを久しぶりに書いた。RAND_MAXの値っていくつだっけ?ってちょっと考えてしまったが、式( x = (x * PARAM_A + PARAM_C) & PARAM_M )を見れば一髪でわかるじゃん、ってなった。

本家は encrypt, decryptが一つの関数なのでキレイだけど、文字の扱いの違いをうまく吸収できず二つにわけてしまった。 共通ロジックを抜き出しても良かったが、まぁいいかな。という気持ち。