|
|
|
|
@@ -14,13 +14,13 @@ function parse (message: number[], blockLength: number): number[][] {
|
|
|
|
|
// Slice message into blocks.
|
|
|
|
|
let ret: number[][] = []
|
|
|
|
|
while (message.length - cursor >= blockLength) {
|
|
|
|
|
ret.push(...[message.slice(cursor, cursor + blockLength)])
|
|
|
|
|
ret.push(message.slice(cursor, cursor + blockLength))
|
|
|
|
|
cursor = cursor + blockLength
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Append any remaining blocks regardless of block length. These will be padded later.
|
|
|
|
|
if (message.length - cursor > 0) {
|
|
|
|
|
ret.push(...[message.slice(cursor)])
|
|
|
|
|
ret.push(message.slice(cursor))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If no message, return a single block.
|
|
|
|
|
@@ -42,18 +42,18 @@ function parse (message: number[], blockLength: number): number[][] {
|
|
|
|
|
function pad (message: number[], padLength: number): number[] {
|
|
|
|
|
// If there is no message, return a fully padded block.
|
|
|
|
|
if (message.length === 0) {
|
|
|
|
|
return Array(16).fill(0)
|
|
|
|
|
return Array(16)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Return a copy of the message if no padding is required.
|
|
|
|
|
if (message.length === padLength) {
|
|
|
|
|
return [...message]
|
|
|
|
|
return Array.from(message)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Pad a copy of the message to padLength.
|
|
|
|
|
const ret = [...message]
|
|
|
|
|
const ret = Array.from(message)
|
|
|
|
|
const requiredPadding = padLength - message.length - 1
|
|
|
|
|
ret.push(...Array(requiredPadding).fill(0))
|
|
|
|
|
ret.push(...Array(requiredPadding))
|
|
|
|
|
|
|
|
|
|
// Set the final byte of the padded blocked to the length of the original message.
|
|
|
|
|
ret[padLength - 1] = message.length
|
|
|
|
|
@@ -83,12 +83,12 @@ function rho (state: number[], mBlock: number[]): [number[], number[]] {
|
|
|
|
|
const gOfS = g(state)
|
|
|
|
|
|
|
|
|
|
// C = M ⊕ G(S)
|
|
|
|
|
const c = [...Array(16).keys()].map(i => mBlock[i] ^ gOfS[i])
|
|
|
|
|
const cBlock = Array.from(Array(16).keys()).map(i => mBlock[i] ^ gOfS[i])
|
|
|
|
|
|
|
|
|
|
// S' = M ⊕ S
|
|
|
|
|
const nextState = [...Array(16).keys()].map(i => state[i] ^ mBlock[i])
|
|
|
|
|
const nextState = Array.from(Array(16).keys()).map(i => state[i] ^ mBlock[i])
|
|
|
|
|
|
|
|
|
|
return [nextState, c]
|
|
|
|
|
return [nextState, cBlock]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@@ -102,10 +102,10 @@ function inverseRoh (state: number[], cBlock: number[]): [number[], number[]] {
|
|
|
|
|
const gOfS = g(state)
|
|
|
|
|
|
|
|
|
|
// M = C ⊕ G(S)
|
|
|
|
|
const mBlock = [...Array(16).keys()].map(i => cBlock[i] ^ gOfS[i])
|
|
|
|
|
const mBlock = Array.from(Array(16).keys()).map(i => cBlock[i] ^ gOfS[i])
|
|
|
|
|
|
|
|
|
|
// S' = S ⊕ M
|
|
|
|
|
const nextState = [...Array(16).keys()].map(i => state[i] ^ mBlock[i])
|
|
|
|
|
const nextState = Array.from(Array(16).keys()).map(i => state[i] ^ mBlock[i])
|
|
|
|
|
return [nextState, mBlock]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -138,20 +138,11 @@ function increaseCounter (counter: number[]): number[] {
|
|
|
|
|
* @returns A reset counter.
|
|
|
|
|
*/
|
|
|
|
|
function resetCounter (): number[] {
|
|
|
|
|
const counter = Array(COUNTER_LENGTH).fill(0)
|
|
|
|
|
const counter = Array(COUNTER_LENGTH)
|
|
|
|
|
counter[0] = 1
|
|
|
|
|
return counter
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns a zeroed buffer.
|
|
|
|
|
* @param bufferLength The length of the buffer to return.
|
|
|
|
|
* @returns A zeroed buffer.
|
|
|
|
|
*/
|
|
|
|
|
function zeroedBuffer (bufferLength: number): number[] {
|
|
|
|
|
return Array(bufferLength).fill(0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Calculate the domain separation.
|
|
|
|
|
* @param combinedData The parsed and concatenated message and associated data,
|
|
|
|
|
@@ -182,6 +173,7 @@ function calculateDomainSeparation (combinedData: number[][], parsedMessageLengt
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Encrypt a message using the Romulus-M cryptography specification.
|
|
|
|
|
* See https://romulusae.github.io/romulus/docs/Romulusv1.3.pdf for more information.
|
|
|
|
|
* @param message The message to encrypt.
|
|
|
|
|
* @param associatedData The associated data to encrypt.
|
|
|
|
|
* @param nonce A 128 bit nonce.
|
|
|
|
|
@@ -193,7 +185,7 @@ export function cryptoAeadEncrypt (message: number[], associatedData: number[],
|
|
|
|
|
const ciphertext = []
|
|
|
|
|
|
|
|
|
|
// Reset state and counter.
|
|
|
|
|
let state = zeroedBuffer(16)
|
|
|
|
|
let state = Array(16)
|
|
|
|
|
let counter = resetCounter()
|
|
|
|
|
|
|
|
|
|
// Carve message and associated data into blocks.
|
|
|
|
|
@@ -216,10 +208,8 @@ export function cryptoAeadEncrypt (message: number[], associatedData: number[],
|
|
|
|
|
combinedDataBlocks[associatedDataBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount], 16)
|
|
|
|
|
combinedDataBlocks[associatedDataBlockCount + messageBlockCount] = pad(combinedDataBlocks[associatedDataBlockCount + messageBlockCount], 16)
|
|
|
|
|
|
|
|
|
|
// Do the encryption.
|
|
|
|
|
// See https://romulusae.github.io/romulus/docs/Romulusv1.3.pdf for more information.
|
|
|
|
|
// Process the associated data.
|
|
|
|
|
let x = 8
|
|
|
|
|
|
|
|
|
|
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1; i++) {
|
|
|
|
|
[state] = rho(state, combinedDataBlocks[2 * i - 1])
|
|
|
|
|
counter = increaseCounter(counter)
|
|
|
|
|
@@ -231,21 +221,23 @@ export function cryptoAeadEncrypt (message: number[], associatedData: number[],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (associatedDataBlockCount % 2 === messageBlockCount % 2) {
|
|
|
|
|
[state] = rho(state, zeroedBuffer(16))
|
|
|
|
|
[state] = rho(state, Array(16))
|
|
|
|
|
} else {
|
|
|
|
|
[state] = rho(state, combinedDataBlocks[associatedDataBlockCount + messageBlockCount])
|
|
|
|
|
counter = increaseCounter(counter)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const [,authenticationTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16))
|
|
|
|
|
// Generate authentication tag.
|
|
|
|
|
const [,authenticationTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), Array(16))
|
|
|
|
|
|
|
|
|
|
if (message.length === 0) {
|
|
|
|
|
return authenticationTag
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
state = [...authenticationTag]
|
|
|
|
|
state = Array.from(authenticationTag)
|
|
|
|
|
counter = resetCounter()
|
|
|
|
|
|
|
|
|
|
// Encrypt the message.
|
|
|
|
|
const originalFinalMessageBlockLength = messageBlocks[messageBlockCount].length
|
|
|
|
|
messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16)
|
|
|
|
|
|
|
|
|
|
@@ -263,35 +255,44 @@ export function cryptoAeadEncrypt (message: number[], associatedData: number[],
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The authentication tag is stored in the final 16 bytes of the ciphertext.
|
|
|
|
|
// Store the authentication tag in the final 16 bytes of the ciphertext.
|
|
|
|
|
ciphertext.push(...authenticationTag)
|
|
|
|
|
|
|
|
|
|
return ciphertext
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Return interface for decrypting a message.
|
|
|
|
|
*/
|
|
|
|
|
export interface DecryptResult {
|
|
|
|
|
success: boolean
|
|
|
|
|
plaintext: number[]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Decrypt a message using the Romulus-M cryptography specification.
|
|
|
|
|
* See https://romulusae.github.io/romulus/docs/Romulusv1.3.pdf for more information.
|
|
|
|
|
* @param ciphertext The ciphertext to decrypt.
|
|
|
|
|
* @param associatedData The associated data.
|
|
|
|
|
* @param nonce The nonce.
|
|
|
|
|
* @param key The key.
|
|
|
|
|
* @returns The decrypted plaintext.
|
|
|
|
|
*/
|
|
|
|
|
export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[], nonce: number[], key: number[]): number[] {
|
|
|
|
|
export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[], nonce: number[], key: number[]): DecryptResult {
|
|
|
|
|
// Buffer for decrypted message.
|
|
|
|
|
const message = []
|
|
|
|
|
const cleartext = []
|
|
|
|
|
|
|
|
|
|
// The authentication tag is represented by the final 16 bytes of the ciphertext.
|
|
|
|
|
const authenticationTag = ciphertext.slice(-16)
|
|
|
|
|
ciphertext.length -= 16
|
|
|
|
|
|
|
|
|
|
// Reset state and counter.
|
|
|
|
|
let state = zeroedBuffer(16)
|
|
|
|
|
let state = Array(16)
|
|
|
|
|
let counter = resetCounter()
|
|
|
|
|
|
|
|
|
|
if (ciphertext.length !== 0) {
|
|
|
|
|
// Combine the ciphertext
|
|
|
|
|
state = [...authenticationTag]
|
|
|
|
|
// Combine the ciphertext.
|
|
|
|
|
state = Array.from(authenticationTag)
|
|
|
|
|
const ciphertextBlocks = parse(ciphertext, 16)
|
|
|
|
|
const ciphertextBlockCount = ciphertextBlocks.length - 1
|
|
|
|
|
const finalCiphertextBlockLength = ciphertextBlocks[ciphertextBlockCount].length
|
|
|
|
|
@@ -305,9 +306,9 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[
|
|
|
|
|
counter = increaseCounter(counter)
|
|
|
|
|
|
|
|
|
|
if (i < ciphertextBlockCount) {
|
|
|
|
|
message.push(...mBlock)
|
|
|
|
|
cleartext.push(...mBlock)
|
|
|
|
|
} else {
|
|
|
|
|
message.push(...mBlock.slice(0, finalCiphertextBlockLength))
|
|
|
|
|
cleartext.push(...mBlock.slice(0, finalCiphertextBlockLength))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
@@ -315,11 +316,11 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Reset state and counter.
|
|
|
|
|
state = zeroedBuffer(16)
|
|
|
|
|
state = Array(16)
|
|
|
|
|
counter = resetCounter()
|
|
|
|
|
|
|
|
|
|
// Carve the message and associated data into blocks.
|
|
|
|
|
const messageBlocks = parse(message, 16)
|
|
|
|
|
const messageBlocks = parse(cleartext, 16)
|
|
|
|
|
const messageBlockLength = messageBlocks.length - 1
|
|
|
|
|
|
|
|
|
|
const associatedDataBlocks = parse(associatedData, 16)
|
|
|
|
|
@@ -338,6 +339,7 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[
|
|
|
|
|
combinedData[associatedDataBlockCount] = pad(combinedData[associatedDataBlockCount], 16)
|
|
|
|
|
combinedData[associatedDataBlockCount + messageBlockLength] = pad(combinedData[associatedDataBlockCount + messageBlockLength], 16)
|
|
|
|
|
|
|
|
|
|
// Verifiy associated data.
|
|
|
|
|
let x = 8
|
|
|
|
|
for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1; i++) {
|
|
|
|
|
[state] = rho(state, combinedData[2 * i - 1])
|
|
|
|
|
@@ -350,23 +352,32 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (associatedDataBlockCount % 2 === messageBlockLength % 2) {
|
|
|
|
|
[state] = rho(state, zeroedBuffer(16))
|
|
|
|
|
[state] = rho(state, Array(16))
|
|
|
|
|
} else {
|
|
|
|
|
[state] = rho(state, combinedData[associatedDataBlockCount + messageBlockLength])
|
|
|
|
|
counter = increaseCounter(counter)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate authentication tag.
|
|
|
|
|
const [,computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16))
|
|
|
|
|
const [,computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), Array(16))
|
|
|
|
|
|
|
|
|
|
// Validate authentication tag.
|
|
|
|
|
let compare = 0
|
|
|
|
|
for (let i = 0; i < 16; i++) {
|
|
|
|
|
compare |= (authenticationTag[i] ^ computedTag[i])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (compare !== 0) {
|
|
|
|
|
return []
|
|
|
|
|
// Authentication failed.
|
|
|
|
|
return {
|
|
|
|
|
success: false,
|
|
|
|
|
plaintext: []
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
return message
|
|
|
|
|
// Decrypted successfully.
|
|
|
|
|
return {
|
|
|
|
|
success: true,
|
|
|
|
|
plaintext: cleartext
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|