Compare commits

..

No commits in common. "master" and "338cb017f2df428cd9f77f5211b9e5bb5bc1a161" have entirely different histories.

10 changed files with 82 additions and 189 deletions

28
package-lock.json generated
View File

@ -7,12 +7,7 @@
"": { "": {
"name": "romulus-js", "name": "romulus-js",
"version": "1.0.0", "version": "1.0.0",
"hasInstallScript": true,
"license": "ISC", "license": "ISC",
"dependencies": {
"@types/uuid": "^8.3.4",
"uuid": "^8.3.2"
},
"devDependencies": { "devDependencies": {
"@types/jest": "^27.4.0", "@types/jest": "^27.4.0",
"jest": "^27.4.7", "jest": "^27.4.7",
@ -1225,11 +1220,6 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true "dev": true
}, },
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"node_modules/@types/yargs": { "node_modules/@types/yargs": {
"version": "16.0.4", "version": "16.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@ -6206,14 +6196,6 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/v8-compile-cache": { "node_modules/v8-compile-cache": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@ -7429,11 +7411,6 @@
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
"dev": true "dev": true
}, },
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"@types/yargs": { "@types/yargs": {
"version": "16.0.4", "version": "16.0.4",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz",
@ -11087,11 +11064,6 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
},
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",

View File

@ -17,20 +17,16 @@
"author": "Butlersaurus", "author": "Butlersaurus",
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/jest": "^27.4.0", "typescript": "^4.5.5",
"ts-standard": "^11.0.0",
"jest": "^27.4.7", "jest": "^27.4.7",
"ts-jest": "^27.1.3", "ts-jest": "^27.1.3",
"ts-standard": "^11.0.0", "@types/jest": "^27.4.0"
"typescript": "^4.5.5"
}, },
"jest": { "jest": {
"verbose": true, "verbose": true,
"transform": { "transform": {
"^.+\\.ts?$": "ts-jest" "^.+\\.ts?$": "ts-jest"
} }
},
"dependencies": {
"uuid": "^8.3.2",
"@types/uuid": "^8.3.4"
} }
} }

View File

@ -1,29 +1,6 @@
import { cryptoAeadDecrypt } from './romulus-m' import { cryptoAeadDecrypt } from './romulus-m'
interface DecryptResult { export function decrypt (ciphertext: Buffer, associatedData: Buffer, nonce: Buffer, key: Buffer): Buffer {
success: boolean const plaintext = cryptoAeadDecrypt(Array.from(ciphertext), Array.from(associatedData), Array.from(nonce), Array.from(key))
plaintext: Uint8Array return Buffer.from(plaintext)
}
/**
* Decrypt a Romulus-M encrypted message.
* N.B. Nonces are handled automatically by this function.
* @param buffer The nonce-prepended data to be decrypted.
* @param associatedData The associated data.
* @param key The encryption key.
* @returns A decrypted DecryptResult object.
*/
export function decrypt (buffer: Uint8Array, associatedData: Uint8Array, key: Uint8Array): DecryptResult {
// Split nonce from ciphertext.
const nonce = Array.from(buffer.slice(0, 16))
const ciphertext = Array.from(buffer.slice(16))
// Decrypt ciphertext using the associated data, nonce and encryption key.
const result = cryptoAeadDecrypt(ciphertext, Array.from(associatedData), nonce, Array.from(key))
// Return the ciphertext and decryption status.
return {
success: result.success,
plaintext: Uint8Array.from(result.plaintext)
}
} }

View File

@ -1,22 +1,6 @@
import { cryptoAeadEncrypt } from './romulus-m' import { cryptoAeadEncrypt } from './romulus-m'
import { v4 as uuidv4 } from 'uuid'
/** export function encrypt (message: Buffer, associatedData: Buffer, nonce: Buffer, key: Buffer): Buffer {
* Encrypt a message using the Romulus-M encryption algorithm. const ciphertext = cryptoAeadEncrypt(Array.from(message), Array.from(associatedData), Array.from(nonce), Array.from(key))
* N.B. A nonce is automatically prepended to the ciphertext using this function. return Buffer.from(ciphertext)
* @param message The plaintext message to encrypt.
* @param associatedData The associated data.
* @param key The encryption key.
* @returns The nonce-prepended ciphertext.
*/
export function encrypt (message: Uint8Array, associatedData: Uint8Array, key: Uint8Array): Uint8Array {
// Generate a nonce.
const nonce = new Uint8Array(16)
uuidv4({}, nonce)
// Encrypt the data using the associated data, newly generated nonce and encryption key.
const ciphertext = Uint8Array.from(cryptoAeadEncrypt(Array.from(message), Array.from(associatedData), Array.from(nonce), Array.from(key)))
// Return the nonce-prepended ciphertext.
return new Uint8Array([...nonce, ...ciphertext])
} }

View File

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

View File

@ -9,7 +9,7 @@ import { MEMBER_MASK, NB_ROUNDS, TWEAK_LENGTH, PT, LFSR_8_TK2, LFSR_8_TK3, S8, C
* @returns The tweakey. * @returns The tweakey.
*/ */
export function tweakeyEncode (counter: number[], domainSeparation: number, nonce: number[], key: number[]): number[] { export function tweakeyEncode (counter: number[], domainSeparation: number, nonce: number[], key: number[]): number[] {
return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8), nonce, key) return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8).fill(0), nonce, key)
} }
/** /**
@ -21,10 +21,10 @@ export function tweakeyEncode (counter: number[], domainSeparation: number, nonc
export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] { export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] {
const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0)) const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0))
tk[0] = Array.from(Array(TWEAK_LENGTH).keys()).map(i => tweakey[i]) tk[0] = [...Array(TWEAK_LENGTH).keys()].map(i => tweakey[i])
for (let i = 0; i < NB_ROUNDS - 1; i++) { for (let i = 0; i < NB_ROUNDS - 1; i++) {
tk[i + 1] = Array.from(tk[i]) tk[i + 1] = [...tk[i]]
for (let j = 0; j < TWEAK_LENGTH; j++) { for (let j = 0; j < TWEAK_LENGTH; j++) {
tk[i + 1][j] = tk[i][j - j % 16 + PT[j % 16]] tk[i + 1][j] = tk[i][j - j % 16 + PT[j % 16]]
@ -36,7 +36,7 @@ export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[]
} }
} }
let s = Array.from(Array(16).keys()).map(i => plaintext[i]) let s = [...Array(16).keys()].map(i => plaintext[i])
for (let i = 0; i < NB_ROUNDS; i++) { for (let i = 0; i < NB_ROUNDS; i++) {
for (let j = 0; j < 16; j++) { for (let j = 0; j < 16; j++) {
s[j] = S8[s[j]] s[j] = S8[s[j]]
@ -53,7 +53,7 @@ export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[]
s = [s[0], s[1], s[2], s[3], s[7], s[4], s[5], s[6], s[10], s[11], s[8], s[9], s[13], s[14], s[15], s[12]] s = [s[0], s[1], s[2], s[3], s[7], s[4], s[5], s[6], s[10], s[11], s[8], s[9], s[13], s[14], s[15], s[12]]
for (let j = 0; j < 4; j++) { for (let j = 0; j < 4; j++) {
const tmp = Array.from(s) const tmp = [...s]
s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j] s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j]
s[4 + j] = tmp[j] s[4 + j] = tmp[j]
s[8 + j] = tmp[4 + j] ^ tmp[8 + j] s[8 + j] = tmp[4 + j] ^ tmp[8 + j]
@ -61,5 +61,5 @@ export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[]
} }
} }
return Array.from(Array(16).keys()).map(i => s[i]) return [...Array(16).keys()].map(i => s[i])
} }

View File

@ -1,44 +1,21 @@
import { decrypt } from '../src/decrypt' import { decrypt } from '../src/decrypt'
test('Test nonce parsing by public decrypt function.', () => { test('Test buffers are supported by decrypt function.', () => {
// Given // Given
const ciphertext = Uint8Array.from([ const ciphertext = Buffer.from([
// Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65, 225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40, 25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128 167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
]) ])
const associatedData = new TextEncoder().encode('Some associated data.') const associatedData = Buffer.from('Some associated data.')
const key = Uint8Array.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]) const nonce = Buffer.from('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = Buffer.from('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
// When // When
const result = decrypt(ciphertext, associatedData, key) const result = decrypt(ciphertext, associatedData, nonce, key)
// Then // Then
const expectedResult = new TextEncoder().encode('Hello, World! This is a test message.') const expectedResult = Buffer.from('Hello, World! This is a test message.')
expect(result.success).toBe(true)
expect(result.plaintext).toMatchObject(expectedResult) expect(result).toMatchObject(expectedResult)
})
test('Test decryption with an invalid key.', () => {
// Given
const ciphertext = Uint8Array.from([
// Nonce
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
// Ciphertext
225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
])
const associatedData = new TextEncoder().encode('Some associated data.')
const key = Uint8Array.from([0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
// When
const result = decrypt(ciphertext, associatedData, key)
// Then
expect(result.success).toBe(false)
expect(result.plaintext).toMatchObject(new Uint8Array())
}) })

View File

@ -1,17 +1,20 @@
import { decrypt } from '../src/decrypt'
import { encrypt } from '../src/encrypt' import { encrypt } from '../src/encrypt'
test('Test nonce generation by public encrypt function.', () => { test('Test buffers are supported by encrypt function.', () => {
// Given // Given
const message = new TextEncoder().encode('Hello, World! This is a test message.') const message = Buffer.from('Hello, World! This is a test message.')
const associatedData = new TextEncoder().encode('Some associated data.') const associatedData = Buffer.from('Some associated data.')
const key = Uint8Array.from([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f]) const nonce = Buffer.from('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
const key = Buffer.from('\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f')
// When // When
const ciphertext = encrypt(message, associatedData, key) const result = encrypt(message, associatedData, nonce, key)
const plaintext = decrypt(ciphertext, associatedData, key)
// Then // Then
expect(plaintext.success).toBe(true) const expectedResult = Buffer.from([
expect(plaintext.plaintext).toMatchObject(message) 225, 53, 3, 212, 22, 112, 246, 194, 61, 171, 230, 187, 157, 102, 32, 76, 62, 65,
25, 202, 255, 201, 206, 49, 60, 58, 82, 216, 72, 116, 106, 129, 162, 142, 69, 40,
167, 88, 94, 195, 174, 217, 242, 149, 224, 125, 196, 237, 172, 165, 116, 119, 128
])
expect(result).toMatchObject(expectedResult)
}) })

View File

@ -1,5 +1,5 @@
import { referenceTests } from './resources/reference-tests' import { referenceTests } from './resources/reference-tests'
import { cryptoAeadDecrypt, cryptoAeadEncrypt, DecryptResult } from '../src/romulus-m' import { cryptoAeadDecrypt, cryptoAeadEncrypt } from '../src/romulus-m'
function parseHexString (string: string): number[] { function parseHexString (string: string): number[] {
const ret = [] const ret = []
@ -24,9 +24,6 @@ test.each(referenceTests)('Perform decryption using reference test %#.', (key, n
const result = cryptoAeadDecrypt(parseHexString(ciphertext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key)) const result = cryptoAeadDecrypt(parseHexString(ciphertext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key))
// Then // Then
const expectedResult: DecryptResult = { const expectedResult = parseHexString(plaintext)
success: true,
plaintext: parseHexString(plaintext)
}
expect(result).toMatchObject(expectedResult) expect(result).toMatchObject(expectedResult)
}) })

View File

@ -59,8 +59,7 @@ test('Decrypt a message with no associated data.', () => {
// Then // Then
const expectedResult = stringToArray('Hello, World! This is a test message.') const expectedResult = stringToArray('Hello, World! This is a test message.')
expect(result.success).toBe(true) expect(result).toMatchObject(expectedResult)
expect(result.plaintext).toMatchObject(expectedResult)
}) })
test('Decrypt a message with associated data.', () => { test('Decrypt a message with associated data.', () => {
@ -79,6 +78,5 @@ test('Decrypt a message with associated data.', () => {
// Then // Then
const expectedResult = stringToArray('Hello, World! This is a test message.') const expectedResult = stringToArray('Hello, World! This is a test message.')
expect(result.success).toBe(true) expect(result).toMatchObject(expectedResult)
expect(result.plaintext).toMatchObject(expectedResult)
}) })