From 1757d7961a7f8da11ed9cc02f60680e16a728132 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:29:14 +0000 Subject: [PATCH 01/10] Add declaration setting to tsconfig --- tsconfig.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index c38d8d8..ddf02ee 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,7 +9,8 @@ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ "strict": true, /* Enable all strict type-checking options. */ - "skipLibCheck": true /* Skip type checking all .d.ts files. */ + "skipLibCheck": true, /* Skip type checking all .d.ts files. */ + "declaration": true }, "exclude": ["tests"] } -- 2.40.1 From b4858a84d95682e0690dd3308ed3eeea26dea447 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:35:30 +0000 Subject: [PATCH 02/10] Add typings location --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 64f0a60..c6280b1 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "An implementation of the Romulus-M cryptography specification.", "main": "index.js", + "typings": "./dist/index.d.ts", "scripts": { "lint": "ts-standard", "test": "jest", -- 2.40.1 From 8fe7072b831f8f6c48628bc7c2a3490dfd8f73b9 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:37:57 +0000 Subject: [PATCH 03/10] Update main location --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c6280b1..c50d3cc 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "romulus-js", "version": "1.0.0", "description": "An implementation of the Romulus-M cryptography specification.", - "main": "index.js", - "typings": "./dist/index.d.ts", + "main": "./dist/index.js", + "types": "./dist/index.d.ts", "scripts": { "lint": "ts-standard", "test": "jest", -- 2.40.1 From d534332ec9c8cbf37da96bc75e7dc0cda27806d5 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:50:42 +0000 Subject: [PATCH 04/10] Add prepare script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c50d3cc..205b98e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "scripts": { "lint": "ts-standard", "test": "jest", - "build": "tsc" + "build": "tsc", + "prepare": "tsc" }, "repository": { "type": "git", -- 2.40.1 From 3347c49fa29663706a8fa3a537f87d3750df6341 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:53:10 +0000 Subject: [PATCH 05/10] Exclude dist from compiler input --- tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index ddf02ee..aabd87d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,5 +12,8 @@ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "declaration": true }, - "exclude": ["tests"] + "exclude": [ + "./tests/**/*", + "./dist/**/*" + ] } -- 2.40.1 From 360f272323e720fb79105afe9ae1f104ba8d19d7 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:56:04 +0000 Subject: [PATCH 06/10] Update typings and build excludes --- package.json | 4 ++-- tsconfig.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 205b98e..e2664f0 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "romulus-js", "version": "1.0.0", "description": "An implementation of the Romulus-M cryptography specification.", - "main": "./dist/index.js", - "types": "./dist/index.d.ts", + "main": "index.js", + "types": "index", "scripts": { "lint": "ts-standard", "test": "jest", diff --git a/tsconfig.json b/tsconfig.json index aabd87d..f48a496 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "declaration": true }, "exclude": [ - "./tests/**/*", - "./dist/**/*" + "tests", + "dist" ] } -- 2.40.1 From d1769651aa17cf64053cbdd517e60df0a65c2fc4 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 00:57:33 +0000 Subject: [PATCH 07/10] Add prepack script --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index e2664f0..42202f9 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "lint": "ts-standard", "test": "jest", "build": "tsc", - "prepare": "tsc" + "prepare": "tsc", + "prepack": "tsc" }, "repository": { "type": "git", -- 2.40.1 From e6568a8b00cd9b96380d8b1fa8df78fb0377a99e Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 01:02:04 +0000 Subject: [PATCH 08/10] Add postinstall --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 42202f9..ca67c34 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "lint": "ts-standard", "test": "jest", "build": "tsc", - "prepare": "tsc", - "prepack": "tsc" + "postinstall": "tsc" }, "repository": { "type": "git", -- 2.40.1 From 338cb017f2df428cd9f77f5211b9e5bb5bc1a161 Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Mon, 31 Jan 2022 01:03:18 +0000 Subject: [PATCH 09/10] Update main and types --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ca67c34..797a9ed 100644 --- a/package.json +++ b/package.json @@ -2,8 +2,8 @@ "name": "romulus-js", "version": "1.0.0", "description": "An implementation of the Romulus-M cryptography specification.", - "main": "index.js", - "types": "index", + "main": "dist/index.js", + "types": "dist/index.d.ts", "scripts": { "lint": "ts-standard", "test": "jest", -- 2.40.1 From 5a042757ddae782bde569cecd543b8670a2f7b3f Mon Sep 17 00:00:00 2001 From: Jack Hadrill Date: Sat, 5 Feb 2022 22:55:31 +0000 Subject: [PATCH 10/10] Improve code quality and add decrypt status return value --- package-lock.json | 30 ++++++++++ package.json | 10 +++- src/decrypt.ts | 29 +++++++++- src/encrypt.ts | 22 ++++++- src/romulus-m.ts | 95 +++++++++++++++++-------------- src/skinny-128-384-plus.ts | 12 ++-- tests/decrypt.test.ts | 33 +++++++++-- tests/encrypt.test.ts | 15 ++--- tests/romulus-m-reference.test.ts | 7 ++- tests/romulus-m.test.ts | 6 +- 10 files changed, 184 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index 643c8f0..909baba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,9 +7,14 @@ "": { "name": "romulus-js", "version": "1.0.0", + "hasInstallScript": true, "license": "ISC", + "dependencies": { + "uuid": "^8.3.2" + }, "devDependencies": { "@types/jest": "^27.4.0", + "@types/uuid": "^8.3.4", "jest": "^27.4.7", "ts-jest": "^27.1.3", "ts-standard": "^11.0.0", @@ -1220,6 +1225,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "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==", + "dev": true + }, "node_modules/@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -6196,6 +6207,14 @@ "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -7411,6 +7430,12 @@ "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "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==", + "dev": true + }, "@types/yargs": { "version": "16.0.4", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", @@ -11064,6 +11089,11 @@ "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": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", diff --git a/package.json b/package.json index 797a9ed..47563bf 100644 --- a/package.json +++ b/package.json @@ -17,16 +17,20 @@ "author": "Butlersaurus", "license": "ISC", "devDependencies": { - "typescript": "^4.5.5", - "ts-standard": "^11.0.0", + "@types/jest": "^27.4.0", + "@types/uuid": "^8.3.4", "jest": "^27.4.7", "ts-jest": "^27.1.3", - "@types/jest": "^27.4.0" + "ts-standard": "^11.0.0", + "typescript": "^4.5.5" }, "jest": { "verbose": true, "transform": { "^.+\\.ts?$": "ts-jest" } + }, + "dependencies": { + "uuid": "^8.3.2" } } diff --git a/src/decrypt.ts b/src/decrypt.ts index 05f2ce2..d50328d 100644 --- a/src/decrypt.ts +++ b/src/decrypt.ts @@ -1,6 +1,29 @@ import { cryptoAeadDecrypt } from './romulus-m' -export function decrypt (ciphertext: Buffer, associatedData: Buffer, nonce: Buffer, key: Buffer): Buffer { - const plaintext = cryptoAeadDecrypt(Array.from(ciphertext), Array.from(associatedData), Array.from(nonce), Array.from(key)) - return Buffer.from(plaintext) +interface DecryptResult { + success: boolean + plaintext: Buffer +} + +/** + * 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: Buffer, associatedData: Buffer, key: Buffer): 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: Buffer.from(result.plaintext) + } } diff --git a/src/encrypt.ts b/src/encrypt.ts index 668e422..5060e57 100644 --- a/src/encrypt.ts +++ b/src/encrypt.ts @@ -1,6 +1,22 @@ import { cryptoAeadEncrypt } from './romulus-m' +import { v4 as uuidv4 } from 'uuid' -export function encrypt (message: Buffer, associatedData: Buffer, nonce: Buffer, key: Buffer): Buffer { - const ciphertext = cryptoAeadEncrypt(Array.from(message), Array.from(associatedData), Array.from(nonce), Array.from(key)) - return Buffer.from(ciphertext) +/** + * Encrypt a message using the Romulus-M encryption algorithm. + * N.B. A nonce is automatically prepended to the ciphertext using this function. + * @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: Buffer, associatedData: Buffer, key: Buffer): Buffer { + // Generate a nonce. + const nonce = Buffer.alloc(16) + uuidv4({}, nonce) + + // Encrypt the data using the associated data, newly generated nonce and encryption key. + const ciphertext = Buffer.from(cryptoAeadEncrypt(Array.from(message), Array.from(associatedData), Array.from(nonce), Array.from(key))) + + // Return the nonce-prepended ciphertext. + return Buffer.concat([nonce, ciphertext]) } diff --git a/src/romulus-m.ts b/src/romulus-m.ts index ed7d45a..0e7ed85 100644 --- a/src/romulus-m.ts +++ b/src/romulus-m.ts @@ -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 + } } } diff --git a/src/skinny-128-384-plus.ts b/src/skinny-128-384-plus.ts index 916321c..7609337 100644 --- a/src/skinny-128-384-plus.ts +++ b/src/skinny-128-384-plus.ts @@ -9,7 +9,7 @@ import { MEMBER_MASK, NB_ROUNDS, TWEAK_LENGTH, PT, LFSR_8_TK2, LFSR_8_TK3, S8, C * @returns The tweakey. */ export function tweakeyEncode (counter: number[], domainSeparation: number, nonce: number[], key: number[]): number[] { - return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8).fill(0), nonce, key) + return counter.concat([domainSeparation ^ MEMBER_MASK], Array(8), nonce, key) } /** @@ -21,10 +21,10 @@ export function tweakeyEncode (counter: number[], domainSeparation: number, nonc export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] { const tk = Array(NB_ROUNDS + 1).fill(Array(TWEAK_LENGTH).fill(0)) - tk[0] = [...Array(TWEAK_LENGTH).keys()].map(i => tweakey[i]) + tk[0] = Array.from(Array(TWEAK_LENGTH).keys()).map(i => tweakey[i]) for (let i = 0; i < NB_ROUNDS - 1; i++) { - tk[i + 1] = [...tk[i]] + tk[i + 1] = Array.from(tk[i]) for (let j = 0; j < TWEAK_LENGTH; j++) { 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(16).keys()].map(i => plaintext[i]) + let s = Array.from(Array(16).keys()).map(i => plaintext[i]) for (let i = 0; i < NB_ROUNDS; i++) { for (let j = 0; j < 16; 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]] for (let j = 0; j < 4; j++) { - const tmp = [...s] + const tmp = Array.from(s) s[j] = tmp[j] ^ tmp[8 + j] ^ tmp[12 + j] s[4 + j] = tmp[j] s[8 + j] = tmp[4 + j] ^ tmp[8 + j] @@ -61,5 +61,5 @@ export function skinnyEncrypt (plaintext: number[], tweakey: number[]): number[] } } - return [...Array(16).keys()].map(i => s[i]) + return Array.from(Array(16).keys()).map(i => s[i]) } diff --git a/tests/decrypt.test.ts b/tests/decrypt.test.ts index d5bad56..5c4f7b8 100644 --- a/tests/decrypt.test.ts +++ b/tests/decrypt.test.ts @@ -1,21 +1,44 @@ import { decrypt } from '../src/decrypt' -test('Test buffers are supported by decrypt function.', () => { +test('Test nonce parsing by public decrypt function.', () => { // Given 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, 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 = Buffer.from('Some associated data.') - 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 - const result = decrypt(ciphertext, associatedData, nonce, key) + const result = decrypt(ciphertext, associatedData, key) // Then const expectedResult = Buffer.from('Hello, World! This is a test message.') - - expect(result).toMatchObject(expectedResult) + expect(result.success).toBe(true) + expect(result.plaintext).toMatchObject(expectedResult) +}) + +test('Test decryption with an invalid key.', () => { + // Given + 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, + 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 = Buffer.from('Some associated data.') + const key = Buffer.from('\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00') + + // When + const result = decrypt(ciphertext, associatedData, key) + + // Then + expect(result.success).toBe(false) + expect(result.plaintext).toMatchObject(Buffer.alloc(0)) }) diff --git a/tests/encrypt.test.ts b/tests/encrypt.test.ts index a6faa31..c57cc4d 100644 --- a/tests/encrypt.test.ts +++ b/tests/encrypt.test.ts @@ -1,20 +1,17 @@ +import { decrypt } from '../src/decrypt' import { encrypt } from '../src/encrypt' -test('Test buffers are supported by encrypt function.', () => { +test('Test nonce generation by public encrypt function.', () => { // Given const message = Buffer.from('Hello, World! This is a test message.') const associatedData = Buffer.from('Some associated data.') - 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 - const result = encrypt(message, associatedData, nonce, key) + const ciphertext = encrypt(message, associatedData, key) + const plaintext = decrypt(ciphertext, associatedData, key) // Then - const expectedResult = Buffer.from([ - 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) + expect(plaintext.success).toBe(true) + expect(plaintext.plaintext).toMatchObject(message) }) diff --git a/tests/romulus-m-reference.test.ts b/tests/romulus-m-reference.test.ts index 7dae2e7..ab5e3c9 100644 --- a/tests/romulus-m-reference.test.ts +++ b/tests/romulus-m-reference.test.ts @@ -1,5 +1,5 @@ import { referenceTests } from './resources/reference-tests' -import { cryptoAeadDecrypt, cryptoAeadEncrypt } from '../src/romulus-m' +import { cryptoAeadDecrypt, cryptoAeadEncrypt, DecryptResult } from '../src/romulus-m' function parseHexString (string: string): number[] { const ret = [] @@ -24,6 +24,9 @@ test.each(referenceTests)('Perform decryption using reference test %#.', (key, n const result = cryptoAeadDecrypt(parseHexString(ciphertext), parseHexString(associatedData), parseHexString(nonce), parseHexString(key)) // Then - const expectedResult = parseHexString(plaintext) + const expectedResult: DecryptResult = { + success: true, + plaintext: parseHexString(plaintext) + } expect(result).toMatchObject(expectedResult) }) diff --git a/tests/romulus-m.test.ts b/tests/romulus-m.test.ts index 7fe9538..957b49e 100644 --- a/tests/romulus-m.test.ts +++ b/tests/romulus-m.test.ts @@ -59,7 +59,8 @@ test('Decrypt a message with no associated data.', () => { // Then const expectedResult = stringToArray('Hello, World! This is a test message.') - expect(result).toMatchObject(expectedResult) + expect(result.success).toBe(true) + expect(result.plaintext).toMatchObject(expectedResult) }) test('Decrypt a message with associated data.', () => { @@ -78,5 +79,6 @@ test('Decrypt a message with associated data.', () => { // Then const expectedResult = stringToArray('Hello, World! This is a test message.') - expect(result).toMatchObject(expectedResult) + expect(result.success).toBe(true) + expect(result.plaintext).toMatchObject(expectedResult) }) -- 2.40.1