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もこういうのは一発でうまくいくことが多いな、、、
UNO R3 開発ボード UNO R3 Arduino互換

↑もってる(*´꒳`*)やっすい互換品の中ではイケてる方かなー
USB-UARTはATMEGA16U2を採用してるものがいいよねー自作のボードに組み込むときはFT231X使ってるけど( ̄▽ ̄;)
(CH340採用のArduino互換品はめっちゃ安くてそれはそれでいいけど、CH340のデバイスドライバはお行儀が悪いので、、、ちょっと、、、)

↑もってる(*´꒳`*)やっすい互換品の中ではイケてる方かなー
USB-UARTはATMEGA16U2を採用してるものがいいよねー自作のボードに組み込むときはFT231X使ってるけど( ̄▽ ̄;)
(CH340採用のArduino互換品はめっちゃ安くてそれはそれでいいけど、CH340のデバイスドライバはお行儀が悪いので、、、ちょっと、、、)



コメントをお書きください