commit 1922f2db623d97422efb5e62acc056311ccb7a84 Author: [Quy Anh] «Elliot» Nguyen Date: Wed Jun 24 16:28:42 2026 +0200 --- diff --git a/constants.ts b/constants.ts new file mode 100755 index 0000000..1c6c07b --- /dev/null +++ b/constants.ts @@ -0,0 +1,67 @@ +// https://en.wikipedia.org/wiki/Rijndael_S-box +export const SBox: number[] = [ + 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 +] + +export const RCons = [ + [0x00, 0x00, 0x00, 0x00], [0x01, 0x00, 0x00, 0x00], [0x02, 0x00, 0x00, 0x00], [0x04, 0x00, 0x00, 0x00], + [0x08, 0x00, 0x00, 0x00], [0x10, 0x00, 0x00, 0x00], [0x20, 0x00, 0x00, 0x00], [0x40, 0x00, 0x00, 0x00], + [0x80, 0x00, 0x00, 0x00], [0x1b, 0x00, 0x00, 0x00], [0x36, 0x00, 0x00, 0x00] +] + +export const mixColumnsMatrix: number[][] = [ + [0x02, 0x03, 0x01, 0x01], + [0x01, 0x02, 0x03, 0x01], + [0x01, 0x01, 0x02, 0x03], + [0x03, 0x01, 0x01, 0x02] +] + +export const invSBox: number[] = [ + // 0x00 to 0x0f + 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, + // 0x10 to 0x1f + 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, + // 0x20 to 0x2f + 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, + // 0x30 to 0x3f + 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, + // 0x40 to 0x4f + 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, + // 0x50 to 0x5f + 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, + // 0x60 to 0x6f + 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, + // 0x70 to 0x7f + 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, + // 0x80 to 0x8f + 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, + // 0x90 to 0x9f + 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, + // 0xa0 to 0xaf + 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + // 0xb0 to 0xbf + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, + // 0xc0 to 0xcf + 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, + // 0xd0 to 0xdf + 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, + // 0xe0 to 0xef + 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, + // 0xf0 to 0xff + 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d +] diff --git a/index.ts b/index.ts new file mode 100755 index 0000000..f3edba8 --- /dev/null +++ b/index.ts @@ -0,0 +1,22 @@ +import { decrypt, encrypt } from './utils' + +const plaintext = ` +A network address is an identifier for a node or host on a telecommunications network. Network addresses are designed to be unique identifiers across the network, although some networks allow for local, private addresses, or locally administered addresses that may not be unique.[1] Special network addresses are allocated as broadcast or multicast addresses. These too are not unique. + +In some cases, network hosts may have more than one network address. For example, each network interface controller may be uniquely identified. Further, because protocols are frequently layered, more than one protocol's network address can occur in any particular network interface or node and more than one type of network address may be used in any one network.[2] + +Network addresses can be flat addresses which contain no information about the node's location in the network (such as a MAC address), or may contain structure or hierarchical information for the routing (such as an IP address). +` +const key = 'a10KDJIGkLPmfSRV33HkwVeYgPe8n9V4' + +const encryptedBlocks = encrypt(plaintext, key) + +console.log('Encrypted Blocks:') +encryptedBlocks.forEach(block => { console.table(block) }) + +console.log('\nDecrypted Text:') +// @ts-ignore +const lineLength = process.stdout.columns +console.log('-'.repeat(lineLength)) +console.log(decrypt(encryptedBlocks, key)) +console.log('-'.repeat(lineLength)) \ No newline at end of file diff --git a/temp.ts b/temp.ts new file mode 100755 index 0000000..97f6e9a --- /dev/null +++ b/temp.ts @@ -0,0 +1,29 @@ +import { addRoundKey, expandKeyFake, preprocessKey, preprocessPlaintextHex, shiftRows, subBytes } from './utils' + +const plaintext = '0f1e2d3c4b5a69788796a5b4c3d2e1f0' + +const key = '00000000000000000000000000000000' + +const plaintextBlock = preprocessPlaintextHex(plaintext)[0] +const keyBlock = preprocessKey(key) +const roundKeys = expandKeyFake(keyBlock) + +let state = plaintextBlock +state = addRoundKey(state, roundKeys[0]) +state = subBytes(state) +state = shiftRows(state) + +console.log(`Plaintext: ${plaintext}`) +console.log(`Key: ${key}`) +console.log('State after applying the first two phases (SubBytes and ShiftRows) of the first round inside AES algorithm:') +console.table(state) + +console.log('Equivalent state in hexadecimal:') +for (let rowIndex = 0; rowIndex < state.length; rowIndex++) { + const row = state[rowIndex] + for (let cellIndex = 0; cellIndex < row.length; cellIndex++) { + const cell = row[cellIndex] + state[rowIndex][cellIndex] = cell.toString(16).padStart(2, '0') as unknown as number + } +} +console.table(state) diff --git a/utils/addRoundKey.ts b/utils/addRoundKey.ts new file mode 100644 index 0000000..39e002b --- /dev/null +++ b/utils/addRoundKey.ts @@ -0,0 +1,16 @@ +export const addRoundKey = (state: number[][], roundKey: number[][]): number[][] => { + const result: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ] + + for (let row = 0;row < 4;row++) { + for (let col = 0;col < 4;col++) { + result[row][col] = state[row][col] ^ roundKey[row][col] + } + } + + return result +} diff --git a/utils/decrypt.ts b/utils/decrypt.ts new file mode 100644 index 0000000..2f12819 --- /dev/null +++ b/utils/decrypt.ts @@ -0,0 +1,40 @@ +import { addRoundKey, expandKey, flattenBlocks, invMixColumns, invShiftRows, invSubBytes, preprocessKey } from './' + +export const decrypt = (encryptedBlocks: number[][][], key: string): string => { + /** + * @param {number[][][]} encryptedBlocks - An array containing blocks of cyphertext. Each block is a 4x4 matrix. + * @param {string} key - A string of 128, 192, or 256 bits. + * @returns {string} The original plaintext. + */ + const keyBlock = preprocessKey(key) + const roundKeys = expandKey(keyBlock) + + const keySize = key.length // Key size in bytes (16 for 128-bit, 24 for 192-bit, 32 for 256-bit) + const N_k = keySize / 4 // Number of 32-bit words in the key (4 for 128-bit, 6 for 192-bit, 8 for 256-bit) + const N_r = N_k + 6 + + const decryptedBlocks: number[][][] = [] + + for (let i = 0;i < encryptedBlocks.length;i++) { + let state = encryptedBlocks[i] + state = addRoundKey(state, roundKeys[N_r]) + state = invShiftRows(state) + state = invSubBytes(state) + + for (let j = N_r - 1;j >= 1;j--) { + state = addRoundKey(state, roundKeys[j]) + state = invMixColumns(state) + state = invShiftRows(state) + state = invSubBytes(state) + } + + state = addRoundKey(state, roundKeys[0]) + decryptedBlocks.push(state) + } + + const flatArray = flattenBlocks(decryptedBlocks) + + + const decoder = new TextDecoder() + return decoder.decode(new Uint8Array(flatArray)) +} diff --git a/utils/encrypt.ts b/utils/encrypt.ts new file mode 100644 index 0000000..ddec542 --- /dev/null +++ b/utils/encrypt.ts @@ -0,0 +1,38 @@ +import { preprocessPlaintext, preprocessKey, expandKey, addRoundKey, subBytes } from './' +import { mixColumns } from './mixColumns' +import { shiftRows } from './shiftRows' + +export const encrypt = (plaintext: string, key: string): number[][][] => { + /** + * @param {string} plaintext - The plaintext to encrypt. + * @param {string} key - A string of 128, 192, or 256 bits. + * @returns {string} The encrypted cyphertext. + */ + + const plaintextBlocks = preprocessPlaintext(plaintext) + const keyBlock = preprocessKey(key) + const roundKeys = expandKey(keyBlock) + + const keySize = key.length // Key size in bytes (16 for 128-bit, 24 for 192-bit, 32 for 256-bit) + const N_k = keySize / 4 // Number of 32-bit words in the key (4 for 128-bit, 6 for 192-bit, 8 for 256-bit) + const N_r = N_k + 6 + + const encryptedBlocks: number[][][] = [] + + for (let i = 0;i < plaintextBlocks.length;i++) { + let state = plaintextBlocks[i] + state = addRoundKey(state, roundKeys[0]) + for (let j = 1;j <= N_r - 1;j++) { + state = subBytes(state) + state = shiftRows(state) + state = mixColumns(state) + state = addRoundKey(state, roundKeys[j]) + } + state = subBytes(state) + state = shiftRows(state) + state = addRoundKey(state, roundKeys[N_r]) + encryptedBlocks.push(state) + } + + return encryptedBlocks +} diff --git a/utils/expandKey.ts b/utils/expandKey.ts new file mode 100644 index 0000000..6bfeee4 --- /dev/null +++ b/utils/expandKey.ts @@ -0,0 +1,64 @@ +import { RCons, SBox } from '../constants' + +const subWord = (word: number[]): number[] => { + /** + * @param {number[]} word - An an array containing 4 numbers, each number is a hex value represeting one byte. + * @returns {number[]} An array containing the corresponding values in the S-box. + */ + return word.map(byte => SBox[byte]) +} + +const rotWord = (word: number[]): number[] => { + return word.slice(1).concat(word[0]) +} + +export const expandKey = (key: number[][]): number[][][] => { + /** + * @param {string} key - A string of 128, 192, or 256 bits. + * @returns {string[]} An array of round keys. + */ + const N_k = key[0].length // Number of columns in the key matrix (4 for 128-bit, 6 for 192-bit, 8 for 256-bit) + const N_r = N_k + 6 // Number of rounds (10 for 128-bit, 12 for 192-bit, 14 for 256-bit) + const totalKeys = 4 * (N_r + 1) // Total number of 4-byte words required + + const roundKeys: number[][] = [] + + // Step 1: Copy the initial key as the first round key (N_k columns) + for (let i = 0;i < N_k;i++) { + roundKeys.push([ + key[0][i], + key[1][i], + key[2][i], + key[3][i] + ]) + } + + // Step 2: Expand the key to generate the full round keys + for (let i = N_k;i < totalKeys;i++) { + let temp = roundKeys[i - 1] // Previous word + + if (i % N_k === 0) { + temp = subWord(rotWord(temp)) // RotWord + SubWord + temp = temp.map((t, index) => t ^ RCons[i / N_k][index]) // XOR with Rcon + } else if (N_k > 6 && i % N_k === 4) { + temp = subWord(temp) // Apply SubWord for AES-256 + } + + // XOR with the word N_k positions earlier + roundKeys.push(roundKeys[i - N_k].map((w, index) => w ^ temp[index])) + } + + // Step 3: Convert flat array into 4x4 round key matrices + const expandedKeys: number[][][] = [] + for (let i = 0;i < totalKeys / 4;i++) { + const matrix: number[][] = [ + [roundKeys[i * 4][0], roundKeys[i * 4 + 1][0], roundKeys[i * 4 + 2][0], roundKeys[i * 4 + 3][0]], + [roundKeys[i * 4][1], roundKeys[i * 4 + 1][1], roundKeys[i * 4 + 2][1], roundKeys[i * 4 + 3][1]], + [roundKeys[i * 4][2], roundKeys[i * 4 + 1][2], roundKeys[i * 4 + 2][2], roundKeys[i * 4 + 3][2]], + [roundKeys[i * 4][3], roundKeys[i * 4 + 1][3], roundKeys[i * 4 + 2][3], roundKeys[i * 4 + 3][3]], + ] + expandedKeys.push(matrix) + } + + return expandedKeys +} diff --git a/utils/expandKeyFake.ts b/utils/expandKeyFake.ts new file mode 100644 index 0000000..71df844 --- /dev/null +++ b/utils/expandKeyFake.ts @@ -0,0 +1,22 @@ +export const expandKeyFake = (key: number[][]): number[][][] => { + /** + * @param {string} key - A string of 128, 192, or 256 bits. + * @returns {string[]} An array of round keys. + */ + const N_k = key[0].length // Number of columns in the key matrix (4 for 128-bit, 6 for 192-bit, 8 for 256-bit) + const N_r = N_k + 6 // Number of rounds (10 for 128-bit, 12 for 192-bit, 14 for 256-bit) + const totalKeys = 4 * (N_r + 1) // Total number of 4-byte words required + + const expandedKeys: number[][][] = [] + for (let i = 0;i < totalKeys / 4;i++) { + const matrix: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ] + expandedKeys.push(matrix) + } + + return expandedKeys +} diff --git a/utils/flattenBlocks.ts b/utils/flattenBlocks.ts new file mode 100644 index 0000000..48c5f03 --- /dev/null +++ b/utils/flattenBlocks.ts @@ -0,0 +1,13 @@ +export const flattenBlocks = (blocks: number[][][]): number[] => { + const flatArray: number[] = [] + + for (const block of blocks) { + for (let col = 0; col < 4; col++) { + for (let row = 0; row < 4; row++) { + flatArray.push(block[row][col]) + } + } + } + + return flatArray +} \ No newline at end of file diff --git a/utils/index.ts b/utils/index.ts new file mode 100644 index 0000000..3ca785c --- /dev/null +++ b/utils/index.ts @@ -0,0 +1,14 @@ +export * from './preprocessPlaintext' +export * from './preprocessKey' +export * from './expandKey' +export * from './expandKeyFake.ts' +export * from './addRoundKey' +export * from './subBytes' +export * from './shiftRows.ts' +export * from './invShiftRows' +export * from './invSubBytes' +export * from './invMixColumns' +export * from './decrypt.ts' +export * from './encrypt.ts' +export * from './flattenBlocks.ts' +export * from './preprocessPlaintextHex.ts' diff --git a/utils/invMixColumns.ts b/utils/invMixColumns.ts new file mode 100644 index 0000000..255f011 --- /dev/null +++ b/utils/invMixColumns.ts @@ -0,0 +1,39 @@ +import { galoisMultiplyBy2 } from './mixColumns' + +const galoisMultiplyBy9 = (value: number): number => { + return galoisMultiplyBy2(galoisMultiplyBy2(galoisMultiplyBy2(value))) ^ value +} + +const galoisMultiplyBy11 = (value: number): number => { + return galoisMultiplyBy2(galoisMultiplyBy2(galoisMultiplyBy2(value)) ^ value) ^ value +} + +const galoisMultiplyBy13 = (value: number): number => { + return galoisMultiplyBy2(galoisMultiplyBy2(galoisMultiplyBy2(value) ^ value)) ^ value +} + +const galoisMultiplyBy14 = (value: number): number => { + return galoisMultiplyBy2(galoisMultiplyBy2(galoisMultiplyBy2(value) ^ value) ^ value) +} + +const clampToByte = (value: number): number => { + return value & 0xFF +} + +export const invMixColumns = (state: number[][]): number[][] => { + const result: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ] + + for (let col = 0; col < 4; col++) { + result[0][col] = clampToByte(galoisMultiplyBy14(state[0][col]) ^ galoisMultiplyBy11(state[1][col]) ^ galoisMultiplyBy13(state[2][col]) ^ galoisMultiplyBy9(state[3][col])) + result[1][col] = clampToByte(galoisMultiplyBy9(state[0][col]) ^ galoisMultiplyBy14(state[1][col]) ^ galoisMultiplyBy11(state[2][col]) ^ galoisMultiplyBy13(state[3][col])) + result[2][col] = clampToByte(galoisMultiplyBy13(state[0][col]) ^ galoisMultiplyBy9(state[1][col]) ^ galoisMultiplyBy14(state[2][col]) ^ galoisMultiplyBy11(state[3][col])) + result[3][col] = clampToByte(galoisMultiplyBy11(state[0][col]) ^ galoisMultiplyBy13(state[1][col]) ^ galoisMultiplyBy9(state[2][col]) ^ galoisMultiplyBy14(state[3][col])) + } + + return result +} \ No newline at end of file diff --git a/utils/invShiftRows.ts b/utils/invShiftRows.ts new file mode 100644 index 0000000..25f54c6 --- /dev/null +++ b/utils/invShiftRows.ts @@ -0,0 +1,9 @@ +export const invShiftRows = (state: number[][]): number[][] => { + const result: number[][] = [] + + for (let i = 0;i < state.length;i++) { + result[i] = state[i].slice(-i).concat(state[i].slice(0, -i)) + } + + return result +} diff --git a/utils/invSubBytes.ts b/utils/invSubBytes.ts new file mode 100644 index 0000000..2c08a2f --- /dev/null +++ b/utils/invSubBytes.ts @@ -0,0 +1,15 @@ +import { invSBox } from '../constants' + +export const invSubBytes = (state: number[][]): number[][] => { + const result: number[][] = [] + + for (let i = 0;i < state.length;i++) { + result[i] = [] + for (let j = 0;j < state[i].length;j++) { + const byte = state[i][j] + result[i][j] = invSBox[byte] + } + } + + return result +} diff --git a/utils/mixColumns.ts b/utils/mixColumns.ts new file mode 100644 index 0000000..197e657 --- /dev/null +++ b/utils/mixColumns.ts @@ -0,0 +1,29 @@ +export const galoisMultiplyBy2 = (value: number): number => { + return (value << 1) ^ ((value & 0x80) ? 0x1b : 0x00) +} + +export const galoisMultiplyBy3 = (value: number): number => { + return galoisMultiplyBy2(value) ^ value +} + +const clampToByte = (value: number): number => { + return value & 0xFF +} + +export const mixColumns = (state: number[][]): number[][] => { + const result: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0] + ] + + for (let col = 0; col < 4; col++) { + result[0][col] = clampToByte(galoisMultiplyBy2(state[0][col]) ^ galoisMultiplyBy3(state[1][col]) ^ state[2][col] ^ state[3][col]) + result[1][col] = clampToByte(state[0][col] ^ galoisMultiplyBy2(state[1][col]) ^ galoisMultiplyBy3(state[2][col]) ^ state[3][col]) + result[2][col] = clampToByte(state[0][col] ^ state[1][col] ^ galoisMultiplyBy2(state[2][col]) ^ galoisMultiplyBy3(state[3][col])) + result[3][col] = clampToByte(galoisMultiplyBy3(state[0][col]) ^ state[1][col] ^ state[2][col] ^ galoisMultiplyBy2(state[3][col])) + } + + return result +} \ No newline at end of file diff --git a/utils/preprocessKey.ts b/utils/preprocessKey.ts new file mode 100644 index 0000000..94dc250 --- /dev/null +++ b/utils/preprocessKey.ts @@ -0,0 +1,34 @@ +export const preprocessKey = (key: string): number[][] => { + /** + * @param {string} key - A string of 128, 192, or 256 bits. + * @returns {number[][]} A 4x4/6/8 matrix representing the key. + */ + + const encoder = new TextEncoder() + const byteArray = encoder.encode(key) + + const validKeyLengths = [128, 192, 256] + + if (!validKeyLengths.includes(byteArray.length * 8)) { + throw new Error('Invalid Key!') + } + + const N_k = (byteArray.length * 8) / 32 + + const matrix: number[][] = [ + Array(N_k).fill(0), + Array(N_k).fill(0), + Array(N_k).fill(0), + Array(N_k).fill(0) + ] + + // Fill the matrix in column-major order, i.e., top-to-bottom + left-to-right + // byteArray.length is either 16, 24, or 32. + for (let i = 0;i < byteArray.length;i++) { + const row = i % 4 + const col = Math.floor(i / 4) + matrix[row][col] = byteArray[i] + } + + return matrix +} diff --git a/utils/preprocessPlaintext.ts b/utils/preprocessPlaintext.ts new file mode 100644 index 0000000..dd333a3 --- /dev/null +++ b/utils/preprocessPlaintext.ts @@ -0,0 +1,49 @@ +export type PlaintextBlock = number[][] + +export const preprocessPlaintext = (plaintext: string): PlaintextBlock[] => { + /** + * @param {string} plaintext - The plaintext to encrypt. + * @returns {PlaintextBlock[]} Blocks of 128 bits representing the plaintext. Each block is a 4x4 column-major matrix. + */ + + const encoder = new TextEncoder() + const byteArray = encoder.encode(plaintext) // Array where each item is an 8-bit representation of a character of the string + const blockSize = 16 // 128 bits = 16 bytes + + // Loop through the byteArray and split it into 128-bit blocks + const blocks: Uint8Array[] = [] + for (let i = 0;i < byteArray.length;i += blockSize) { + const block = byteArray.slice(i, i + blockSize) + + if (block.length < blockSize) { + // Pad the block with zeroes + const paddedBlock = new Uint8Array(blockSize) + paddedBlock.set(block) + blocks.push(paddedBlock) + } else { + blocks.push(block) + } + } + + const plaintextBlocks: number[][][] = blocks.map(block => { + // Block is a 128-bit array containing 16 8-bit integers. + + const matrix: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ] + + // Fill the matrix in column-major order, i.e., top-to-bottom + left-to-right + for (let i = 0;i < blockSize;i++) { + const row = i % 4 + const col = Math.floor(i / 4) + matrix[row][col] = block[i] + } + + return matrix + }) + + return plaintextBlocks +} diff --git a/utils/preprocessPlaintextHex.ts b/utils/preprocessPlaintextHex.ts new file mode 100644 index 0000000..d920552 --- /dev/null +++ b/utils/preprocessPlaintextHex.ts @@ -0,0 +1,49 @@ +import { PlaintextBlock} from './' + +export const preprocessPlaintextHex = (hexPlaintext: string): PlaintextBlock[] => { + /** + * @param {string} hexPlaintext - The plaintext to encrypt, as a hexadecimal string. + * @returns {PlaintextBlock[]} Blocks of 128 bits representing the plaintext. Each block is a 4x4 column-major matrix. + */ + const blockSize = 16 // 128 bits = 16 bytes + + // Convert the hex string into a byte array, where each pair of hex digits is a byte + const byteArray = new Uint8Array(hexPlaintext.match(/.{1,2}/g)!.map(byte => parseInt(byte, 16))) + + // Loop through the byteArray and split it into 128-bit blocks + const blocks: Uint8Array[] = [] + for (let i = 0;i < byteArray.length;i += blockSize) { + const block = byteArray.slice(i, i + blockSize) + + if (block.length < blockSize) { + // Pad the block with zeroes + const paddedBlock = new Uint8Array(blockSize) + paddedBlock.set(block) + blocks.push(paddedBlock) + } else { + blocks.push(block) + } + } + + const plaintextBlocks: number[][][] = blocks.map(block => { + // Block is a 128-bit array containing 16 8-bit integers. + + const matrix: number[][] = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ] + + // Fill the matrix in column-major order, i.e., top-to-bottom + left-to-right + for (let i = 0;i < blockSize;i++) { + const row = i % 4 + const col = Math.floor(i / 4) + matrix[row][col] = block[i] + } + + return matrix + }) + + return plaintextBlocks +} diff --git a/utils/shiftRows.ts b/utils/shiftRows.ts new file mode 100644 index 0000000..bd40b10 --- /dev/null +++ b/utils/shiftRows.ts @@ -0,0 +1,9 @@ +export const shiftRows = (state: number[][]): number[][] => { + const result: number[][] = [] + + for (let i = 0;i < state.length;i++) { + result[i] = state[i].slice(i).concat(state[i].slice(0, i)) + } + + return result +} diff --git a/utils/subBytes.ts b/utils/subBytes.ts new file mode 100644 index 0000000..cfac313 --- /dev/null +++ b/utils/subBytes.ts @@ -0,0 +1,13 @@ +import { SBox } from '../constants' + +export const subBytes = (state: number[][]): number[][] => { + const result: number[][] = [] + for (let i = 0;i < state.length;i++) { + result[i] = [] + for (let j = 0;j < state[0].length;j++) { + const byte = state[i][j] + result[i][j] = SBox[byte] + } + } + return result +}