2025年9月19日金曜日

JavaScript今さら入門(5)

 


JavaScriptでAES128暗号文を作ってみる

ちょっとgRPCから離れて、JavaScriptで少し複雑なことをやってみる。例題はいつものやつ(rijndael)。
HTMLに埋め込んで自前でやるからサーバー側にプログラムとか置いて許可を得る必要がない。 とはいえ、JavaScriptの知識は圧倒的に不足しているので、Geminiを使ってC言語のソースコードをJavaScriptに変換してもらった。 、、、これって、もう言語を選ぶ意味がなくなるってこと?まぁ、外部ライブラリに依存していないものならそうなのかも。そりゃそうだと言えばそりゃそうだなんだけど、 すごい時代というか、いい時代というか、恐ろしい時代というか、、、

JavaScriptでrijndael





実行結果例

ソースコードはこちら
  1. <!--ここから-->
  2. <div>JavaScriptでrijndael</div>
  3. <input id="plain" placeholder="平文" size="50" type="text" /><br />
  4. <input id="key" placeholder="鍵" size="50" type="text" /><br />
  5. <button id="exec" type="button">実行</button><br />
  6. <div id="out"></div><br />
  7. <script>
  8.   
  9. /* rijndael.js */
  10. // S-Box
  11. const S = [
  12.     [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76],
  13.     [0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0],
  14.     [0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15],
  15.     [0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75],
  16.     [0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84],
  17.     [0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf],
  18.     [0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8],
  19.     [0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2],
  20.     [0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73],
  21.     [0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb],
  22.     [0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79],
  23.     [0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08],
  24.     [0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a],
  25.     [0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e],
  26.     [0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf],
  27.     [0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
  28. ];
  29. // Inverse S-Box
  30. const invS = [
  31.     [0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb],
  32.     [0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb],
  33.     [0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e],
  34.     [0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25],
  35.     [0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92],
  36.     [0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84],
  37.     [0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06],
  38.     [0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b],
  39.     [0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73],
  40.     [0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e],
  41.     [0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b],
  42.     [0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4],
  43.     [0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f],
  44.     [0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef],
  45.     [0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61],
  46.     [0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d]
  47. ];
  48. // Key Expansion用の変数と定数
  49. const w = Array(11).fill(null).map(() => new Uint8Array(16));
  50. const Rcon = new Uint8Array([0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36]);
  51. /**
  52.  * 4バイトの単語をS-Boxを使って置換します。
  53.  * @param {Uint8Array} inWord - 置換する4バイトの単語
  54.  */
  55. function SubWord(inWord) {
  56.     for (let i = 0; i < 4; i++) {
  57.         inWord[i] = S[(inWord[i] >> 4) & 0x0F][inWord[i] & 0x0F];
  58.     }
  59. }
  60. /**
  61.  * 4バイトの単語を左に1バイトシフトします。
  62.  * @param {Uint8Array} inWord - シフトする4バイトの単語
  63.  */
  64. function RotWord(inWord) {
  65.     const tmp = inWord[0];
  66.     inWord[0] = inWord[1];
  67.     inWord[1] = inWord[2];
  68.     inWord[2] = inWord[3];
  69.     inWord[3] = tmp;
  70. }
  71. /**
  72.  * 1ラウンドの鍵を生成します。
  73.  * @param {number} num - ラウンド番号
  74.  */
  75. function KeyExpansion_Single(num) {
  76.     const tempWord = new Uint8Array(4);
  77.     for (let i = 0; i < 4; i++) {
  78.         tempWord[i] = w[num - 1][12 + i];
  79.     }
  80.     RotWord(tempWord);
  81.     SubWord(tempWord);
  82.     tempWord[0] ^= Rcon[num - 1];
  83.     for (let i = 0; i < 4; i++) {
  84.         w[num][i] = tempWord[i] ^ w[num - 1][i];
  85.         w[num][i + 4] = w[num][i] ^ w[num - 1][i + 4];
  86.         w[num][i + 8] = w[num][i + 4] ^ w[num - 1][i + 8];
  87.         w[num][i + 12] = w[num][i + 8] ^ w[num - 1][i + 12];
  88.     }
  89. }
  90. /**
  91.  * 鍵展開を行います。
  92.  * @param {Uint8Array} key - 16バイトの鍵
  93.  */
  94. function KeyExpansion(key) {
  95.     for (let i = 0; i < 16; i++) {
  96.         w[0][i] = key[i];
  97.     }
  98.     for (let i = 1; i < 11; i++) {
  99.         KeyExpansion_Single(i);
  100.     }
  101. }
  102. /**
  103.  * ラウンド鍵を加算します (XOR演算)。
  104.  * @param {Uint8Array} state - 状態配列
  105.  * @param {number} nRound - ラウンド番号
  106.  */
  107. function AddRoundKey(state, nRound) {
  108.     for (let i = 0; i < 16; i++) {
  109.         state[i] ^= w[nRound][i];
  110.     }
  111. }
  112. /**
  113.  * 状態配列の各バイトをS-Boxで置換します。
  114.  * @param {Uint8Array} state - 状態配列
  115.  */
  116. function SubBytes(state) {
  117.     for (let i = 0; i < 16; i++) {
  118.         state[i] = S[state[i] >> 4][state[i] & 0x0F];
  119.     }
  120. }
  121. /**
  122.  * 状態配列の各バイトをInverse S-Boxで置換します。
  123.  * @param {Uint8Array} state - 状態配列
  124.  */
  125. function invSubBytes(state) {
  126.     for (let i = 0; i < 16; i++) {
  127.         state[i] = invS[state[i] >> 4][state[i] & 0x0F];
  128.     }
  129. }
  130. /**
  131.  * 状態配列の行をシフトします。
  132.  * @param {Uint8Array} state - 状態配列
  133.  */
  134. function ShiftRows(state) {
  135.     const temp = new Uint8Array(8);
  136.     for (let i = 0; i < 4; i++) {
  137.         for (let j = 0; j < 4; j++) {
  138.             temp[j] = state[j * 4 + i];
  139.             temp[j + 4] = state[j * 4 + i];
  140.         }
  141.         for (let j = 0; j < 4; j++) {
  142.             state[j * 4 + i] = temp[j + i];
  143.         }
  144.     }
  145. }
  146. /**
  147.  * 状態配列の行を逆シフトします。
  148.  * @param {Uint8Array} state - 状態配列
  149.  */
  150. function invShiftRows(state) {
  151.     const temp = new Uint8Array(8);
  152.     for (let i = 0; i < 4; i++) {
  153.         for (let j = 0; j < 4; j++) {
  154.             temp[j] = state[j * 4 + i];
  155.             temp[j + 4] = state[j * 4 + i];
  156.         }
  157.         for (let j = 0; j < 4; j++) {
  158.             state[j * 4 + i] = temp[j + 4 - i];
  159.         }
  160.     }
  161. }
  162. /**
  163.  * Galois Field GF(2^8)における乗算を行います。
  164.  * @param {number} a - 被乗数
  165.  * @param {number} b - 乗数
  166.  * @returns {number} 乗算結果
  167.  */
  168. function mul(a, b) {
  169.     let x = 0;
  170.     for (let i = 0x08; i > 0; i >>= 1) {
  171.         if (x & 0x80) {
  172.             x <<= 1;
  173.             x ^= 0x1b;
  174.         } else {
  175.             x <<= 1;
  176.         }
  177.         if (b & i) {
  178.             x ^= a;
  179.         }
  180.     }
  181.     return x & 0xFF; // 8ビットに収まるようにマスク
  182. }
  183. /**
  184.  * 1つの列に対してMixColumns変換を適用します。
  185.  * @param {Uint8Array} r - 4バイトの列
  186.  */
  187. function MixColumn_single(r) {
  188.     const t = r.slice(0, 4); // 配列のコピー
  189.     r[0] = mul(t[0], 2) ^ mul(t[1], 3) ^ mul(t[2], 1) ^ mul(t[3], 1);
  190.     r[1] = mul(t[1], 2) ^ mul(t[2], 3) ^ mul(t[3], 1) ^ mul(t[0], 1);
  191.     r[2] = mul(t[2], 2) ^ mul(t[3], 3) ^ mul(t[0], 1) ^ mul(t[1], 1);
  192.     r[3] = mul(t[3], 2) ^ mul(t[0], 3) ^ mul(t[1], 1) ^ mul(t[2], 1);
  193. }
  194. /**
  195.  * 状態配列の列に対してMixColumns変換を適用します。
  196.  * @param {Uint8Array} state - 状態配列
  197.  */
  198. function MixColumns(state) {
  199.     for (let i = 0; i < 4; i++) {
  200.         MixColumn_single(state.subarray(i * 4, i * 4 + 4)); // subarrayでビューを作成
  201.     }
  202. }
  203. /**
  204.  * 1つの列に対してInverse MixColumns変換を適用します。
  205.  * @param {Uint8Array} r - 4バイトの列
  206.  */
  207. function invMixColumn_single(r) {
  208.     const t = r.slice(0, 4); // 配列のコピー
  209.     r[0] = mul(t[0], 14) ^ mul(t[1], 11) ^ mul(t[2], 13) ^ mul(t[3], 9);
  210.     r[1] = mul(t[1], 14) ^ mul(t[2], 11) ^ mul(t[3], 13) ^ mul(t[0], 9);
  211.     r[2] = mul(t[2], 14) ^ mul(t[3], 11) ^ mul(t[0], 13) ^ mul(t[1], 9);
  212.     r[3] = mul(t[3], 14) ^ mul(t[0], 11) ^ mul(t[1], 13) ^ mul(t[2], 9);
  213. }
  214. /**
  215.  * 状態配列の列に対してInverse MixColumns変換を適用します。
  216.  * @param {Uint8Array} state - 状態配列
  217.  */
  218. function invMixColumns(state) {
  219.     for (let i = 0; i < 4; i++) {
  220.         invMixColumn_single(state.subarray(i * 4, i * 4 + 4));
  221.     }
  222. }
  223. /**
  224.  * AES暗号化処理を行います。
  225.  * @param {Uint8Array} data - 暗号化する16バイトのデータ
  226.  * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
  227.  */
  228. function Cipher(data, key) {
  229.     AddRoundKey(data, 0);
  230.     for (let i = 1; i < 10; i++) {
  231.         SubBytes(data);
  232.         ShiftRows(data);
  233.         MixColumns(data);
  234.         AddRoundKey(data, i);
  235.     }
  236.     SubBytes(data);
  237.     ShiftRows(data);
  238.     AddRoundKey(data, 10);
  239. }
  240. /**
  241.  * AES復号化処理を行います。
  242.  * @param {Uint8Array} data - 復号化する16バイトのデータ
  243.  * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
  244.  */
  245. function invCipher(data, key) {
  246.     AddRoundKey(data, 10);
  247.     for (let i = 9; i > 0; i--) {
  248.         invShiftRows(data);
  249.         invSubBytes(data);
  250.         AddRoundKey(data, i);
  251.         invMixColumns(data);
  252.     }
  253.     invShiftRows(data);
  254.     invSubBytes(data);
  255.     AddRoundKey(data, 0);
  256. }
  257. // ユーザーが利用するためのメイン関数
  258. // C言語のAES128Encrypt/Decrypt関数を模倣
  259. const AES128 = {
  260.     /**
  261.      * AES128暗号化を実行します。
  262.      * @param {Uint8Array} plain - 16バイトの平文
  263.      * @param {Uint8Array} key - 16バイトの鍵
  264.      * @returns {Uint8Array} 暗号化された16バイトのデータ
  265.      */
  266.     encrypt(plain, key) {
  267.         const enc = new Uint8Array(16);
  268.         enc.set(plain);
  269.         KeyExpansion(key);
  270.         Cipher(enc, key);
  271.         return enc;
  272.     },
  273.     /**
  274.      * AES128復号化を実行します。
  275.      * @param {Uint8Array} enc - 16バイトの暗号文
  276.      * @param {Uint8Array} key - 16バイトの鍵
  277.      * @returns {Uint8Array} 復号化された16バイトのデータ
  278.      */
  279.     decrypt(enc, key) {
  280.         const plain = new Uint8Array(16);
  281.         plain.set(enc);
  282.         KeyExpansion(key);
  283.         invCipher(plain, key);
  284.         return plain;
  285.     }
  286. };
  287. // C言語のmain関数を模倣したテストコード
  288. function main() {
  289.     const key = new Uint8Array([0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f]);
  290.     const data = new Uint8Array([0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff]);
  291.     // 暗号化
  292.     const encryptedData = AES128.encrypt(data, key);
  293.     console.log("Encrypted Data:", Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
  294.     // 復号化
  295.     const decryptedData = AES128.decrypt(encryptedData, key);
  296.     console.log("Decrypted Data:", Array.from(decryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
  297. }
  298. // main関数を実行
  299. // main();
  300.   
  301. let plain=document.getElementById('plain');
  302. let key=document.getElementById('key');
  303. let out=document.getElementById('out');
  304. let button=document.getElementById('exec');
  305. function button_clicked(){
  306.   const plain_txt=plain.value;
  307.   const key_txt=key.value;
  308.   const plain_pairs=plain_txt.match(/.{2}/g) || [];
  309.   const key_pairs=key_txt.match(/.{2}/g) || [];
  310.   const plain_arr=new Uint8Array(plain_pairs.map(byteString => parseInt(byteString, 16)));
  311.   const key_arr=new Uint8Array(key_pairs.map(byteString => parseInt(byteString, 16)));
  312.   const encryptedData = AES128.encrypt(plain_arr, key_arr);
  313.   out.textContent=Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join('');
  314. }
  315. button.addEventListener("click",button_clicked);
  316. </script>
  317. <!--ここまで-->

AIもこういうのは一発でうまくいくことが多いな、、、

0 件のコメント:

コメントを投稿