Add a Romulus-M decrypt function

This commit is contained in:
Jack Hadrill
2022-01-30 00:04:26 +00:00
parent 1998293b65
commit 19a2481c13
2 changed files with 144 additions and 1 deletions

View File

@@ -91,6 +91,24 @@ function rho (state: number[], mBlock: number[]): [number[], number[]] {
return [nextState, c]
}
/**
* The state update function. Pads a C block.
* @param state The internal state, S.
* @param cBlock A C block.
* @returns [S', M] where M = C ⊕ G(S) and S' = C ⊕ M.
*/
function inverseRoh (state: number[], cBlock: number[]): [number[], number[]] {
// G(S)
const gOfS = g(state)
// M = C ⊕ G(S)
const mBlock = [...Array(16).keys()].map(i => cBlock[i] ^ gOfS[i])
// S' = S ⊕ M
const nextState = [...Array(16).keys()].map(i => state[i] ^ mBlock[i])
return [nextState, mBlock]
}
/**
* Increments the 56 bit LFSR-based counter.
* @param counter The old counter.
@@ -243,3 +261,90 @@ export function cryptoAeadEncrypt (message: number[], associatedData: number[],
return ciphertext
}
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.
const authenticationTag = ciphertext.slice(-16)
ciphertext.length -= 16
let state = zeroedBuffer(16)
if (ciphertext.length !== 0) {
state = [...authenticationTag]
const parsedCiphertext = parse(ciphertext, 16)
const parsedCiphertextLength = parsedCiphertext.length - 1
const finalCiphertextBlockLength = parsedCiphertext[parsedCiphertextLength].length
parsedCiphertext[parsedCiphertextLength] = pad(parsedCiphertext[parsedCiphertextLength], 16)
var counter = resetCounter()
for (let i = 1; i < parsedCiphertextLength + 1; i++) {
state = skinnyEncrypt(state, tweakeyEncode(counter, 4, nonce, key))
let mBlock
[state, mBlock] = inverseRoh(state, parsedCiphertext[i])
counter = increaseCounter(counter)
if (i < parsedCiphertextLength) {
message.push(...mBlock)
} else {
message.push(...mBlock.slice(0, finalCiphertextBlockLength))
}
}
} else {
state = []
}
state = zeroedBuffer(16)
counter = resetCounter()
const parsedAssociatedData = parse(associatedData, 16)
const parsedAssociatedDataLength = parsedAssociatedData.length - 1
const parsedMessage = parse(message, 16)
const parsedMessageLength = parsedMessage.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))
// Insert empty array at position 0.
combinedData.splice(0, 0, [])
// Calculate domain separation for final decryption stage.
const domainSeparation = calculateDomainSeparation(combinedData, parsedMessageLength, parsedAssociatedDataLength)
// Pad combined data.
combinedData[parsedAssociatedDataLength] = pad(combinedData[parsedAssociatedDataLength], 16)
combinedData[parsedAssociatedDataLength + parsedMessageLength] = pad(combinedData[parsedAssociatedDataLength + parsedMessageLength], 16)
let x = 8
for (let i = 1; i < Math.floor((parsedAssociatedDataLength + parsedMessageLength) / 2) + 1; i++) {
[state] = rho(state, combinedData[2 * i - 1])
counter = increaseCounter(counter)
if (i === Math.floor(parsedAssociatedDataLength / 2) + 1) {
x = x ^ 4
}
state = skinnyEncrypt(state, tweakeyEncode(counter, x, combinedData[2 * i], key))
counter = increaseCounter(counter)
}
if (parsedAssociatedDataLength % 2 === parsedMessageLength % 2) {
[state] = rho(state, zeroedBuffer(16))
} else {
[state] = rho(state, combinedData[parsedAssociatedDataLength + parsedMessageLength])
counter = increaseCounter(counter)
}
let computedTag
[state, computedTag] = rho(skinnyEncrypt(state, tweakeyEncode(counter, domainSeparation, nonce, key)), zeroedBuffer(16))
let compare = 0
for (let i = 0; i < 16; i++) {
compare |= (authenticationTag[i] ^ computedTag[i])
}
if (compare !== 0) {
return []
} else {
return message
}
}