From 4241d4545cc61540024922b37b4e8f97cdcef86d Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Sun, 30 Jan 2022 00:36:58 +0000 Subject: [PATCH] Tidy and improve consistency --- src/romulus-m.ts | 138 +++++++++++++++++++++++++++-------------------- 1 file changed, 80 insertions(+), 58 deletions(-) diff --git a/src/romulus-m.ts b/src/romulus-m.ts index f43b2c6..54f9446 100644 --- a/src/romulus-m.ts +++ b/src/romulus-m.ts @@ -186,106 +186,125 @@ function calculateDomainSeparation (combinedData: number[][], parsedMessageLengt * @param associatedData The associated data to encrypt. * @param nonce A 128 bit nonce. * @param key A 128 bit encryption key. - * @returns The ciphertext. + * @returns The encrypted ciphertext. */ export function cryptoAeadEncrypt (message: number[], associatedData: number[], nonce: number[], key: number[]): number[] { + // Buffer for ciphertext. + const ciphertext = [] + + // Reset state and counter. + let state = zeroedBuffer(16) let counter = resetCounter() // Carve message and associated data into blocks. - const parsedMessage = parse(message, 16) - const parsedMessageLength = parsedMessage.length - 1 + const messageBlocks = parse(message, 16) + const messageBlockCount = messageBlocks.length - 1 - const parsedAssociatedData = parse(associatedData, 16) - const parsedAssociatedDataLength = parsedAssociatedData.length - 1 + const associatedDataBlocks = parse(associatedData, 16) + const associatedDataBlockCount = associatedDataBlocks.length - 1 - // Concatenate the parsed message and the associated data, excluding each array's first element. - const combinedData = parsedAssociatedData.slice(1).concat(parsedMessage.slice(1)) + // Concatenate the message and associated data blocks, excluding each array's first element. + const combinedDataBlocks = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1)) // Insert empty array at position 0. - combinedData.splice(0, 0, []) + combinedDataBlocks.splice(0, 0, []) // Calculate domain separation for final encryption stage. - const domainSeparation = calculateDomainSeparation(combinedData, parsedMessageLength, parsedAssociatedDataLength) + const domainSeparation = calculateDomainSeparation(combinedDataBlocks, messageBlockCount, associatedDataBlockCount) // Pad combined data. - combinedData[parsedAssociatedDataLength] = pad(combinedData[parsedAssociatedDataLength], 16) - combinedData[parsedAssociatedDataLength + parsedMessageLength] = pad(combinedData[parsedAssociatedDataLength + parsedMessageLength], 16) + 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. - let state = zeroedBuffer(16) - let c = zeroedBuffer(16) let x = 8 - for (let i = 1; i < Math.floor((parsedAssociatedDataLength + parsedMessageLength) / 2) + 1; i++) { - [state, c] = rho(state, combinedData[2 * i - 1]) + + for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockCount) / 2) + 1; i++) { + [state] = rho(state, combinedDataBlocks[2 * i - 1]) counter = increaseCounter(counter) - if (i === Math.floor(parsedAssociatedDataLength / 2) + 1) { + if (i === Math.floor(associatedDataBlockCount / 2) + 1) { x = x ^ 4 } - state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key)) + state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedDataBlocks[2 * i], key)) counter = increaseCounter(counter) } - if (parsedAssociatedDataLength % 2 === parsedMessageLength % 2) { - [state, c] = rho(state, zeroedBuffer(16)) + if (associatedDataBlockCount % 2 === messageBlockCount % 2) { + [state] = rho(state, zeroedBuffer(16)) } else { - [state, c] = rho(state, combinedData[parsedAssociatedDataLength + parsedMessageLength]) + [state] = rho(state, combinedDataBlocks[associatedDataBlockCount + messageBlockCount]) counter = increaseCounter(counter) } - [state, c] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) + const [,authenticationTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) + if (message.length === 0) { - return c + return authenticationTag } - state = [...c] - - const ciphertext = [] - const originalLastBlockLength = parsedMessage[parsedMessageLength].length - parsedMessage[parsedMessageLength] = pad(parsedMessage[parsedMessageLength], 16) - + state = [...authenticationTag] counter = resetCounter() - for (let i = 1; i < parsedMessageLength + 1; i++) { + + const originalFinalMessageBlockLength = messageBlocks[messageBlockCount].length + messageBlocks[messageBlockCount] = pad(messageBlocks[messageBlockCount], 16) + + for (let i = 1; i < messageBlockCount + 1; i++) { state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key)) - let x - [state, x] = rho(state, parsedMessage[i]) + + let cBlock + [state, cBlock] = rho(state, messageBlocks[i]) counter = increaseCounter(counter) - if (i < parsedMessageLength) { - ciphertext.push(...x) + + if (i < messageBlockCount) { + ciphertext.push(...cBlock) } else { - ciphertext.push(...x.slice(0, originalLastBlockLength)) + ciphertext.push(...cBlock.slice(0, originalFinalMessageBlockLength)) } } - ciphertext.push(...c) + // The authentication tag is stored in the final 16 bytes of the ciphertext. + ciphertext.push(...authenticationTag) return ciphertext } +/** + * Decrypt a message using the Romulus-M cryptography specification. + * @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[] { // Buffer for decrypted message. const message = [] - // The authentication tag is represented by the last 16 bytes of the ciphertext. + // 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 counter = resetCounter() if (ciphertext.length !== 0) { + // Combine the ciphertext state = [...authenticationTag] - const parsedCiphertext = parse(ciphertext, 16) - const parsedCiphertextLength = parsedCiphertext.length - 1 - const finalCiphertextBlockLength = parsedCiphertext[parsedCiphertextLength].length - parsedCiphertext[parsedCiphertextLength] = pad(parsedCiphertext[parsedCiphertextLength], 16) + const ciphertextBlocks = parse(ciphertext, 16) + const ciphertextBlockCount = ciphertextBlocks.length - 1 + const finalCiphertextBlockLength = ciphertextBlocks[ciphertextBlockCount].length + ciphertextBlocks[ciphertextBlockCount] = pad(ciphertextBlocks[ciphertextBlockCount], 16) - for (let i = 1; i < parsedCiphertextLength + 1; i++) { + for (let i = 1; i < ciphertextBlockCount + 1; i++) { state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key)) + let mBlock - [state, mBlock] = inverseRoh(state, parsedCiphertext[i]) + [state, mBlock] = inverseRoh(state, ciphertextBlocks[i]) counter = increaseCounter(counter) - if (i < parsedCiphertextLength) { + + if (i < ciphertextBlockCount) { message.push(...mBlock) } else { message.push(...mBlock.slice(0, finalCiphertextBlockLength)) @@ -295,47 +314,50 @@ export function cryptoAeadDecrypt (ciphertext: number[], associatedData: number[ state = [] } + // Reset state and counter. state = zeroedBuffer(16) counter = resetCounter() - const parsedAssociatedData = parse(associatedData, 16) - const parsedAssociatedDataLength = parsedAssociatedData.length - 1 + // Carve the message and associated data into blocks. + const messageBlocks = parse(message, 16) + const messageBlockLength = messageBlocks.length - 1 - const parsedMessage = parse(message, 16) - const parsedMessageLength = parsedMessage.length - 1 + const associatedDataBlocks = parse(associatedData, 16) + const associatedDataBlockCount = associatedDataBlocks.length - 1 - // Concatenate the parsed message and the associated data, excluding each array's first element. - const combinedData = parsedAssociatedData.slice(1).concat(parsedMessage.slice(1)) + // Concatenate the message and associated data blocks, excluding each array's first element. + const combinedData = associatedDataBlocks.slice(1).concat(messageBlocks.slice(1)) // Insert empty array at position 0. combinedData.splice(0, 0, []) // Calculate domain separation for final decryption stage. - const domainSeparation = calculateDomainSeparation(combinedData, parsedMessageLength, parsedAssociatedDataLength) + const domainSeparation = calculateDomainSeparation(combinedData, messageBlockLength, associatedDataBlockCount) // Pad combined data. - combinedData[parsedAssociatedDataLength] = pad(combinedData[parsedAssociatedDataLength], 16) - combinedData[parsedAssociatedDataLength + parsedMessageLength] = pad(combinedData[parsedAssociatedDataLength + parsedMessageLength], 16) + combinedData[associatedDataBlockCount] = pad(combinedData[associatedDataBlockCount], 16) + combinedData[associatedDataBlockCount + messageBlockLength] = pad(combinedData[associatedDataBlockCount + messageBlockLength], 16) let x = 8 - for (let i = 1; i < Math.floor((parsedAssociatedDataLength + parsedMessageLength) / 2) + 1; i++) { + for (let i = 1; i < Math.floor((associatedDataBlockCount + messageBlockLength) / 2) + 1; i++) { [state] = rho(state, combinedData[2 * i - 1]) counter = increaseCounter(counter) - if (i === Math.floor(parsedAssociatedDataLength / 2) + 1) { + if (i === Math.floor(associatedDataBlockCount / 2) + 1) { x = x ^ 4 } state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key)) counter = increaseCounter(counter) } - if (parsedAssociatedDataLength % 2 === parsedMessageLength % 2) { + if (associatedDataBlockCount % 2 === messageBlockLength % 2) { [state] = rho(state, zeroedBuffer(16)) } else { - [state] = rho(state, combinedData[parsedAssociatedDataLength + parsedMessageLength]) + [state] = rho(state, combinedData[associatedDataBlockCount + messageBlockLength]) counter = increaseCounter(counter) } - let computedTag - [state, computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) + + // Calculate authentication tag. + const [,computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16)) let compare = 0 for (let i = 0; i < 16; i++) {