---
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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'
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user