JavaScriptでAES128暗号文を作ってみる
ちょっとgRPCから離れて、JavaScriptで少し複雑なことをやってみる。例題はいつものやつ(rijndael)。
HTMLに埋め込んで自前でやるからサーバー側にプログラムとか置いて許可を得る必要がない。 とはいえ、JavaScriptの知識は圧倒的に不足しているので、Geminiを使ってC言語のソースコードをJavaScriptに変換してもらった。 、、、これって、もう言語を選ぶ意味がなくなるってこと?まぁ、外部ライブラリに依存していないものならそうなのかも。そりゃそうだと言えばそりゃそうだなんだけど、 すごい時代というか、いい時代というか、恐ろしい時代というか、、、
JavaScriptでrijndael
実行結果例
ソースコードはこちら
- <!--ここから-->
- <div>JavaScriptでrijndael</div>
- <input id="plain" placeholder="平文" size="50" type="text" /><br />
- <input id="key" placeholder="鍵" size="50" type="text" /><br />
- <button id="exec" type="button">実行</button><br />
- <div id="out"></div><br />
- <script>
-
- /* rijndael.js */
- // S-Box
- const S = [
- [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76],
- [0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0],
- [0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15],
- [0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75],
- [0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84],
- [0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf],
- [0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8],
- [0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2],
- [0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73],
- [0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb],
- [0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79],
- [0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08],
- [0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a],
- [0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e],
- [0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf],
- [0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]
- ];
- // Inverse S-Box
- const invS = [
- [0x52,0x09,0x6a,0xd5,0x30,0x36,0xa5,0x38,0xbf,0x40,0xa3,0x9e,0x81,0xf3,0xd7,0xfb],
- [0x7c,0xe3,0x39,0x82,0x9b,0x2f,0xff,0x87,0x34,0x8e,0x43,0x44,0xc4,0xde,0xe9,0xcb],
- [0x54,0x7b,0x94,0x32,0xa6,0xc2,0x23,0x3d,0xee,0x4c,0x95,0x0b,0x42,0xfa,0xc3,0x4e],
- [0x08,0x2e,0xa1,0x66,0x28,0xd9,0x24,0xb2,0x76,0x5b,0xa2,0x49,0x6d,0x8b,0xd1,0x25],
- [0x72,0xf8,0xf6,0x64,0x86,0x68,0x98,0x16,0xd4,0xa4,0x5c,0xcc,0x5d,0x65,0xb6,0x92],
- [0x6c,0x70,0x48,0x50,0xfd,0xed,0xb9,0xda,0x5e,0x15,0x46,0x57,0xa7,0x8d,0x9d,0x84],
- [0x90,0xd8,0xab,0x00,0x8c,0xbc,0xd3,0x0a,0xf7,0xe4,0x58,0x05,0xb8,0xb3,0x45,0x06],
- [0xd0,0x2c,0x1e,0x8f,0xca,0x3f,0x0f,0x02,0xc1,0xaf,0xbd,0x03,0x01,0x13,0x8a,0x6b],
- [0x3a,0x91,0x11,0x41,0x4f,0x67,0xdc,0xea,0x97,0xf2,0xcf,0xce,0xf0,0xb4,0xe6,0x73],
- [0x96,0xac,0x74,0x22,0xe7,0xad,0x35,0x85,0xe2,0xf9,0x37,0xe8,0x1c,0x75,0xdf,0x6e],
- [0x47,0xf1,0x1a,0x71,0x1d,0x29,0xc5,0x89,0x6f,0xb7,0x62,0x0e,0xaa,0x18,0xbe,0x1b],
- [0xfc,0x56,0x3e,0x4b,0xc6,0xd2,0x79,0x20,0x9a,0xdb,0xc0,0xfe,0x78,0xcd,0x5a,0xf4],
- [0x1f,0xdd,0xa8,0x33,0x88,0x07,0xc7,0x31,0xb1,0x12,0x10,0x59,0x27,0x80,0xec,0x5f],
- [0x60,0x51,0x7f,0xa9,0x19,0xb5,0x4a,0x0d,0x2d,0xe5,0x7a,0x9f,0x93,0xc9,0x9c,0xef],
- [0xa0,0xe0,0x3b,0x4d,0xae,0x2a,0xf5,0xb0,0xc8,0xeb,0xbb,0x3c,0x83,0x53,0x99,0x61],
- [0x17,0x2b,0x04,0x7e,0xba,0x77,0xd6,0x26,0xe1,0x69,0x14,0x63,0x55,0x21,0x0c,0x7d]
- ];
- // Key Expansion用の変数と定数
- const w = Array(11).fill(null).map(() => new Uint8Array(16));
- const Rcon = new Uint8Array([0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36]);
- /**
- * 4バイトの単語をS-Boxを使って置換します。
- * @param {Uint8Array} inWord - 置換する4バイトの単語
- */
- function SubWord(inWord) {
- for (let i = 0; i < 4; i++) {
- inWord[i] = S[(inWord[i] >> 4) & 0x0F][inWord[i] & 0x0F];
- }
- }
- /**
- * 4バイトの単語を左に1バイトシフトします。
- * @param {Uint8Array} inWord - シフトする4バイトの単語
- */
- function RotWord(inWord) {
- const tmp = inWord[0];
- inWord[0] = inWord[1];
- inWord[1] = inWord[2];
- inWord[2] = inWord[3];
- inWord[3] = tmp;
- }
- /**
- * 1ラウンドの鍵を生成します。
- * @param {number} num - ラウンド番号
- */
- function KeyExpansion_Single(num) {
- const tempWord = new Uint8Array(4);
- for (let i = 0; i < 4; i++) {
- tempWord[i] = w[num - 1][12 + i];
- }
- RotWord(tempWord);
- SubWord(tempWord);
- tempWord[0] ^= Rcon[num - 1];
- for (let i = 0; i < 4; i++) {
- w[num][i] = tempWord[i] ^ w[num - 1][i];
- w[num][i + 4] = w[num][i] ^ w[num - 1][i + 4];
- w[num][i + 8] = w[num][i + 4] ^ w[num - 1][i + 8];
- w[num][i + 12] = w[num][i + 8] ^ w[num - 1][i + 12];
- }
- }
- /**
- * 鍵展開を行います。
- * @param {Uint8Array} key - 16バイトの鍵
- */
- function KeyExpansion(key) {
- for (let i = 0; i < 16; i++) {
- w[0][i] = key[i];
- }
- for (let i = 1; i < 11; i++) {
- KeyExpansion_Single(i);
- }
- }
- /**
- * ラウンド鍵を加算します (XOR演算)。
- * @param {Uint8Array} state - 状態配列
- * @param {number} nRound - ラウンド番号
- */
- function AddRoundKey(state, nRound) {
- for (let i = 0; i < 16; i++) {
- state[i] ^= w[nRound][i];
- }
- }
- /**
- * 状態配列の各バイトをS-Boxで置換します。
- * @param {Uint8Array} state - 状態配列
- */
- function SubBytes(state) {
- for (let i = 0; i < 16; i++) {
- state[i] = S[state[i] >> 4][state[i] & 0x0F];
- }
- }
- /**
- * 状態配列の各バイトをInverse S-Boxで置換します。
- * @param {Uint8Array} state - 状態配列
- */
- function invSubBytes(state) {
- for (let i = 0; i < 16; i++) {
- state[i] = invS[state[i] >> 4][state[i] & 0x0F];
- }
- }
- /**
- * 状態配列の行をシフトします。
- * @param {Uint8Array} state - 状態配列
- */
- function ShiftRows(state) {
- const temp = new Uint8Array(8);
- for (let i = 0; i < 4; i++) {
- for (let j = 0; j < 4; j++) {
- temp[j] = state[j * 4 + i];
- temp[j + 4] = state[j * 4 + i];
- }
- for (let j = 0; j < 4; j++) {
- state[j * 4 + i] = temp[j + i];
- }
- }
- }
- /**
- * 状態配列の行を逆シフトします。
- * @param {Uint8Array} state - 状態配列
- */
- function invShiftRows(state) {
- const temp = new Uint8Array(8);
- for (let i = 0; i < 4; i++) {
- for (let j = 0; j < 4; j++) {
- temp[j] = state[j * 4 + i];
- temp[j + 4] = state[j * 4 + i];
- }
- for (let j = 0; j < 4; j++) {
- state[j * 4 + i] = temp[j + 4 - i];
- }
- }
- }
- /**
- * Galois Field GF(2^8)における乗算を行います。
- * @param {number} a - 被乗数
- * @param {number} b - 乗数
- * @returns {number} 乗算結果
- */
- function mul(a, b) {
- let x = 0;
- for (let i = 0x08; i > 0; i >>= 1) {
- if (x & 0x80) {
- x <<= 1;
- x ^= 0x1b;
- } else {
- x <<= 1;
- }
- if (b & i) {
- x ^= a;
- }
- }
- return x & 0xFF; // 8ビットに収まるようにマスク
- }
- /**
- * 1つの列に対してMixColumns変換を適用します。
- * @param {Uint8Array} r - 4バイトの列
- */
- function MixColumn_single(r) {
- const t = r.slice(0, 4); // 配列のコピー
- r[0] = mul(t[0], 2) ^ mul(t[1], 3) ^ mul(t[2], 1) ^ mul(t[3], 1);
- r[1] = mul(t[1], 2) ^ mul(t[2], 3) ^ mul(t[3], 1) ^ mul(t[0], 1);
- r[2] = mul(t[2], 2) ^ mul(t[3], 3) ^ mul(t[0], 1) ^ mul(t[1], 1);
- r[3] = mul(t[3], 2) ^ mul(t[0], 3) ^ mul(t[1], 1) ^ mul(t[2], 1);
- }
- /**
- * 状態配列の列に対してMixColumns変換を適用します。
- * @param {Uint8Array} state - 状態配列
- */
- function MixColumns(state) {
- for (let i = 0; i < 4; i++) {
- MixColumn_single(state.subarray(i * 4, i * 4 + 4)); // subarrayでビューを作成
- }
- }
- /**
- * 1つの列に対してInverse MixColumns変換を適用します。
- * @param {Uint8Array} r - 4バイトの列
- */
- function invMixColumn_single(r) {
- const t = r.slice(0, 4); // 配列のコピー
- r[0] = mul(t[0], 14) ^ mul(t[1], 11) ^ mul(t[2], 13) ^ mul(t[3], 9);
- r[1] = mul(t[1], 14) ^ mul(t[2], 11) ^ mul(t[3], 13) ^ mul(t[0], 9);
- r[2] = mul(t[2], 14) ^ mul(t[3], 11) ^ mul(t[0], 13) ^ mul(t[1], 9);
- r[3] = mul(t[3], 14) ^ mul(t[0], 11) ^ mul(t[1], 13) ^ mul(t[2], 9);
- }
- /**
- * 状態配列の列に対してInverse MixColumns変換を適用します。
- * @param {Uint8Array} state - 状態配列
- */
- function invMixColumns(state) {
- for (let i = 0; i < 4; i++) {
- invMixColumn_single(state.subarray(i * 4, i * 4 + 4));
- }
- }
- /**
- * AES暗号化処理を行います。
- * @param {Uint8Array} data - 暗号化する16バイトのデータ
- * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
- */
- function Cipher(data, key) {
- AddRoundKey(data, 0);
- for (let i = 1; i < 10; i++) {
- SubBytes(data);
- ShiftRows(data);
- MixColumns(data);
- AddRoundKey(data, i);
- }
- SubBytes(data);
- ShiftRows(data);
- AddRoundKey(data, 10);
- }
- /**
- * AES復号化処理を行います。
- * @param {Uint8Array} data - 復号化する16バイトのデータ
- * @param {Uint8Array} key - 16バイトの鍵 (鍵展開済みであること)
- */
- function invCipher(data, key) {
- AddRoundKey(data, 10);
- for (let i = 9; i > 0; i--) {
- invShiftRows(data);
- invSubBytes(data);
- AddRoundKey(data, i);
- invMixColumns(data);
- }
- invShiftRows(data);
- invSubBytes(data);
- AddRoundKey(data, 0);
- }
- // ユーザーが利用するためのメイン関数
- // C言語のAES128Encrypt/Decrypt関数を模倣
- const AES128 = {
- /**
- * AES128暗号化を実行します。
- * @param {Uint8Array} plain - 16バイトの平文
- * @param {Uint8Array} key - 16バイトの鍵
- * @returns {Uint8Array} 暗号化された16バイトのデータ
- */
- encrypt(plain, key) {
- const enc = new Uint8Array(16);
- enc.set(plain);
- KeyExpansion(key);
- Cipher(enc, key);
- return enc;
- },
- /**
- * AES128復号化を実行します。
- * @param {Uint8Array} enc - 16バイトの暗号文
- * @param {Uint8Array} key - 16バイトの鍵
- * @returns {Uint8Array} 復号化された16バイトのデータ
- */
- decrypt(enc, key) {
- const plain = new Uint8Array(16);
- plain.set(enc);
- KeyExpansion(key);
- invCipher(plain, key);
- return plain;
- }
- };
- // C言語のmain関数を模倣したテストコード
- function main() {
- const key = new Uint8Array([0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f]);
- const data = new Uint8Array([0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xaa,0xbb,0xcc,0xdd,0xee,0xff]);
- // 暗号化
- const encryptedData = AES128.encrypt(data, key);
- console.log("Encrypted Data:", Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
- // 復号化
- const decryptedData = AES128.decrypt(encryptedData, key);
- console.log("Decrypted Data:", Array.from(decryptedData).map(b => b.toString(16).padStart(2, '0')).join(''));
- }
- // main関数を実行
- // main();
-
- let plain=document.getElementById('plain');
- let key=document.getElementById('key');
- let out=document.getElementById('out');
- let button=document.getElementById('exec');
- function button_clicked(){
- const plain_txt=plain.value;
- const key_txt=key.value;
- const plain_pairs=plain_txt.match(/.{2}/g) || [];
- const key_pairs=key_txt.match(/.{2}/g) || [];
- const plain_arr=new Uint8Array(plain_pairs.map(byteString => parseInt(byteString, 16)));
- const key_arr=new Uint8Array(key_pairs.map(byteString => parseInt(byteString, 16)));
- const encryptedData = AES128.encrypt(plain_arr, key_arr);
- out.textContent=Array.from(encryptedData).map(b => b.toString(16).padStart(2, '0')).join('');
- }
- button.addEventListener("click",button_clicked);
- </script>
- <!--ここまで-->
AIもこういうのは一発でうまくいくことが多いな、、、
0 件のコメント:
コメントを投稿