diff --git a/package-lock.json b/package-lock.json index af76ee06..310dbe48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "jimp": "^0.22.12", "jq-web": "^0.5.1", "jquery": "3.7.1", + "js-ascon": "^1.3.0", "js-sha3": "^0.9.3", "jsesc": "^3.0.2", "json5": "^2.2.3", @@ -12349,6 +12350,14 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==", "license": "MIT" }, + "node_modules/js-ascon": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/js-ascon/-/js-ascon-1.3.0.tgz", + "integrity": "sha512-7GdMP11Ut8klrwkx+G2qRqEHhkWxmIoyVH6w+MU/4pRwWO0Dh/n3xo8wKe5IkTAdCCpU22uoHiaoB6JwGpbxcA==", + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/js-sha3": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.9.3.tgz", diff --git a/package.json b/package.json index ec3f0520..dfd6dd7a 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "jimp": "^0.22.12", "jq-web": "^0.5.1", "jquery": "3.7.1", + "js-ascon": "^1.3.0", "js-sha3": "^0.9.3", "jsesc": "^3.0.2", "json5": "^2.2.3", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 434c8bb6..77fb08d1 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -107,6 +107,8 @@ "Rabbit", "SM4 Encrypt", "SM4 Decrypt", + "Ascon Encrypt", + "Ascon Decrypt", "GOST Encrypt", "GOST Decrypt", "GOST Sign", @@ -427,6 +429,8 @@ "BLAKE2b", "BLAKE2s", "BLAKE3", + "Ascon Hash", + "Ascon MAC", "GOST Hash", "Streebog", "SSDEEP", diff --git a/src/core/operations/AsconDecrypt.mjs b/src/core/operations/AsconDecrypt.mjs new file mode 100644 index 00000000..21ab3af2 --- /dev/null +++ b/src/core/operations/AsconDecrypt.mjs @@ -0,0 +1,112 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import JsAscon from "js-ascon"; + +/** + * Ascon Decrypt operation + */ +class AsconDecrypt extends Operation { + + /** + * AsconDecrypt constructor + */ + constructor() { + super(); + + this.name = "Ascon Decrypt"; + this.module = "Ciphers"; + this.description = "Ascon-AEAD128 authenticated decryption as standardised in NIST SP 800-232. Decrypts ciphertext and verifies the authentication tag. Decryption will fail if the ciphertext or associated data has been tampered with.

Key: Must be exactly 16 bytes (128 bits).

Nonce: Must be exactly 16 bytes (128 bits). Must match the nonce used during encryption.

Associated Data: Must match the associated data used during encryption. Any mismatch will cause authentication failure."; + this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Associated Data", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Hex", "Raw"] + }, + { + "name": "Output", + "type": "option", + "value": ["Raw", "Hex"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} if invalid key or nonce length, or authentication fails + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonce = Utils.convertToByteArray(args[1].string, args[1].option), + ad = Utils.convertToByteArray(args[2].string, args[2].option), + inputType = args[3], + outputType = args[4]; + + if (key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`); + } + + if (nonce.length !== 16) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`); + } + + // Convert input to byte array + const inputData = Utils.convertToByteArray(input, inputType); + + const keyUint8 = new Uint8Array(key); + const nonceUint8 = new Uint8Array(nonce); + const adUint8 = new Uint8Array(ad); + const ciphertextUint8 = new Uint8Array(inputData); + + try { + // Decrypt (returns Uint8Array containing plaintext) + const plaintext = JsAscon.decrypt(keyUint8, nonceUint8, adUint8, ciphertextUint8); + + // Return in requested format + if (outputType === "Hex") { + return toHexFast(plaintext); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(plaintext).buffer); + } + } catch (e) { + throw new OperationError("Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with."); + } + } + +} + +export default AsconDecrypt; diff --git a/src/core/operations/AsconEncrypt.mjs b/src/core/operations/AsconEncrypt.mjs new file mode 100644 index 00000000..300ff6c3 --- /dev/null +++ b/src/core/operations/AsconEncrypt.mjs @@ -0,0 +1,108 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import JsAscon from "js-ascon"; + +/** + * Ascon Encrypt operation + */ +class AsconEncrypt extends Operation { + + /** + * AsconEncrypt constructor + */ + constructor() { + super(); + + this.name = "Ascon Encrypt"; + this.module = "Ciphers"; + this.description = "Ascon-AEAD128 authenticated encryption as standardised in NIST SP 800-232. Ascon is a family of lightweight authenticated encryption algorithms designed for constrained devices such as IoT sensors and embedded systems.

Key: Must be exactly 16 bytes (128 bits).

Nonce: Must be exactly 16 bytes (128 bits). Should be unique for each encryption with the same key. Never reuse a nonce with the same key.

Associated Data: Optional additional data that is authenticated but not encrypted. Useful for including metadata like headers or timestamps.

The output includes both the ciphertext and a 128-bit authentication tag."; + this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Nonce", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Associated Data", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + }, + { + "name": "Input", + "type": "option", + "value": ["Raw", "Hex"] + }, + { + "name": "Output", + "type": "option", + "value": ["Hex", "Raw"] + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} if invalid key or nonce length + */ + run(input, args) { + const key = Utils.convertToByteArray(args[0].string, args[0].option), + nonce = Utils.convertToByteArray(args[1].string, args[1].option), + ad = Utils.convertToByteArray(args[2].string, args[2].option), + inputType = args[3], + outputType = args[4]; + + if (key.length !== 16) { + throw new OperationError(`Invalid key length: ${key.length} bytes. + +Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`); + } + + if (nonce.length !== 16) { + throw new OperationError(`Invalid nonce length: ${nonce.length} bytes. + +Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`); + } + + // Convert input to byte array + const inputData = Utils.convertToByteArray(input, inputType); + + const keyUint8 = new Uint8Array(key); + const nonceUint8 = new Uint8Array(nonce); + const adUint8 = new Uint8Array(ad); + const inputUint8 = new Uint8Array(inputData); + + // Encrypt (returns Uint8Array containing ciphertext + tag) + const ciphertext = JsAscon.encrypt(keyUint8, nonceUint8, adUint8, inputUint8); + + // Return in requested format + if (outputType === "Hex") { + return toHexFast(ciphertext); + } else { + return Utils.arrayBufferToStr(Uint8Array.from(ciphertext).buffer); + } + } + +} + +export default AsconEncrypt; diff --git a/src/core/operations/AsconHash.mjs b/src/core/operations/AsconHash.mjs new file mode 100644 index 00000000..6414ea01 --- /dev/null +++ b/src/core/operations/AsconHash.mjs @@ -0,0 +1,49 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import JsAscon from "js-ascon"; + +/** + * Ascon Hash operation + */ +class AsconHash extends Operation { + + /** + * AsconHash constructor + */ + constructor() { + super(); + + this.name = "Ascon Hash"; + this.module = "Crypto"; + this.description = "Ascon-Hash256 produces a fixed 256-bit (32-byte) cryptographic hash as standardised in NIST SP 800-232. Ascon is a family of lightweight authenticated encryption and hashing algorithms designed for constrained devices such as IoT sensors and embedded systems.

The algorithm was selected by NIST in 2023 as the new standard for lightweight cryptography after a multi-year competition."; + this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = []; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + + const inputUint8 = new Uint8Array(input); + + // Compute hash (returns Uint8Array) + const hashResult = JsAscon.hash(inputUint8); + + // Convert to hex string + return toHexFast(hashResult); + } + +} + +export default AsconHash; diff --git a/src/core/operations/AsconMAC.mjs b/src/core/operations/AsconMAC.mjs new file mode 100644 index 00000000..9eb75ea6 --- /dev/null +++ b/src/core/operations/AsconMAC.mjs @@ -0,0 +1,68 @@ +/** + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import { toHexFast } from "../lib/Hex.mjs"; +import AsconMac from "../vendor/ascon.mjs"; + +/** + * Ascon MAC operation + */ +class AsconMAC extends Operation { + + /** + * AsconMAC constructor + */ + constructor() { + super(); + + this.name = "Ascon MAC"; + this.module = "Crypto"; + this.description = "Ascon-Mac produces a 128-bit (16-byte) message authentication code as part of the Ascon family standardised by NIST in SP 800-232. It provides authentication for messages using a secret key, ensuring both data integrity and authenticity.

Ascon is designed for lightweight cryptography on constrained devices such as IoT sensors and embedded systems."; + this.infoURL = "https://wikipedia.org/wiki/Ascon_(cipher)"; + this.inputType = "ArrayBuffer"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "toggleString", + "value": "", + "toggleValues": ["Hex", "UTF8", "Latin1", "Base64"] + } + ]; + } + + /** + * @param {ArrayBuffer} input + * @param {Object[]} args + * @returns {string} + * @throws {OperationError} if invalid key length + */ + run(input, args) { + const keyArray = Utils.convertToByteArray(args[0].string, args[0].option); + + if (keyArray.length !== 16) { + throw new OperationError(`Invalid key length: ${keyArray.length} bytes. + +Ascon-Mac requires a key of exactly 16 bytes (128 bits).`); + } + + // Convert to Uint8Array for vendor Ascon implementation + const keyUint8 = new Uint8Array(keyArray); + const inputUint8 = new Uint8Array(input); + + // Compute MAC (returns Uint8Array) + const macResult = AsconMac.mac(keyUint8, inputUint8); + + // Convert to hex string + return toHexFast(macResult); + } + +} + +export default AsconMAC; diff --git a/src/core/vendor/ascon.mjs b/src/core/vendor/ascon.mjs new file mode 100644 index 00000000..891741d9 --- /dev/null +++ b/src/core/vendor/ascon.mjs @@ -0,0 +1,162 @@ +/** + * Ascon MAC implementation following NIST SP 800-232 + * Vendor file for CyberChef + * + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +/** + * NIST SP 800-232 compliant Ascon-Mac implementation + * Uses little-endian byte ordering as per NIST specification + */ +class AsconMac { + // NIST SP 800-232 constants + static ASCON_MAC_IV = 0x0010200080cc0005n; + static ASCON_PRF_IN_RATE = 32; // 4 * 8 bytes + static ASCON_PRF_OUT_RATE = 16; // 2 * 8 bytes + + /** + * Compute Ascon-Mac tag + * @param {Uint8Array} key - 16-byte key + * @param {Uint8Array} message - Message to authenticate + * @param {number} tagLength - Output tag length (default 16) + * @returns {Uint8Array} - MAC tag + */ + static mac(key, message, tagLength = 16) { + if (key.length !== 16) { + throw new Error(`Invalid key length: ${key.length} bytes. Ascon-Mac requires exactly 16 bytes.`); + } + + // Initialise state + const state = new BigUint64Array(5); + + // Load key as two 64-bit words (little-endian per NIST SP 800-232) + const K0 = AsconMac.loadBytes(key, 0, 8); + const K1 = AsconMac.loadBytes(key, 8, 8); + + // Set initial value per NIST SP 800-232 + state[0] = AsconMac.ASCON_MAC_IV; + state[1] = K0; + state[2] = K1; + state[3] = 0n; + state[4] = 0n; + + // Initial permutation P12 + AsconMac.permutation(state, 12); + + // Absorb message in 8-byte chunks, cycling through state[0..3] + let pos = 0; + let wordIdx = 0; + + while (pos + 8 <= message.length) { + state[wordIdx] ^= AsconMac.loadBytes(message, pos, 8); + wordIdx++; + if (wordIdx === 4) { + wordIdx = 0; + AsconMac.permutation(state, 12); + } + pos += 8; + } + + // Absorb final partial block with padding + const remaining = message.length - pos; + if (remaining > 0) { + state[wordIdx] ^= AsconMac.loadBytes(message, pos, remaining); + } + // PAD(remaining) = 0x01 << (8 * remaining) + state[wordIdx] ^= 0x01n << BigInt(8 * remaining); + + // Domain separation: DSEP() = 0x80 << 56 = 0x8000000000000000 + state[4] ^= 0x8000000000000000n; + + // Finalisation permutation P12 + AsconMac.permutation(state, 12); + + // Squeeze output + const tag = new Uint8Array(tagLength); + let outPos = 0; + wordIdx = 0; + + while (outPos < tagLength) { + const toCopy = Math.min(8, tagLength - outPos); + AsconMac.storeBytes(tag, outPos, state[wordIdx], toCopy); + outPos += toCopy; + wordIdx++; + if (wordIdx === 2 && outPos < tagLength) { + wordIdx = 0; + AsconMac.permutation(state, 12); + } + } + + return tag; + } + + /** + * Load n bytes as little-endian 64-bit integer (NIST SP 800-232 byte order) + * LOADBYTES: bytes[i] goes to position i (byte 0 = LSB) + */ + static loadBytes(arr, offset, n) { + let result = 0n; + for (let i = 0; i < n && offset + i < arr.length; i++) { + result |= BigInt(arr[offset + i]) << BigInt(i * 8); + } + return result; + } + + /** + * Store n bytes from 64-bit integer in little-endian order + * STOREBYTES: position i goes to bytes[i] (LSB = byte 0) + */ + static storeBytes(arr, offset, val, n) { + for (let i = 0; i < n; i++) { + arr[offset + i] = Number((val >> BigInt(i * 8)) & 0xFFn); + } + } + + /** + * Ascon permutation + */ + static permutation(state, rounds) { + for (let r = 12 - rounds; r < 12; r++) { + // Add round constant + state[2] ^= BigInt(0xf0 - r * 0x10 + r); + + // Substitution layer + state[0] ^= state[4]; + state[4] ^= state[3]; + state[2] ^= state[1]; + + const t0 = state[0] ^ (~state[1] & state[2]); + const t1 = state[1] ^ (~state[2] & state[3]); + const t2 = state[2] ^ (~state[3] & state[4]); + const t3 = state[3] ^ (~state[4] & state[0]); + const t4 = state[4] ^ (~state[0] & state[1]); + + state[0] = t0 ^ t4; + state[1] = t1 ^ t0; + state[2] = ~t2; + state[3] = t3 ^ t2; + state[4] = t4; + + // Linear diffusion layer + state[0] ^= AsconMac.rotr64(state[0], 19n) ^ AsconMac.rotr64(state[0], 28n); + state[1] ^= AsconMac.rotr64(state[1], 61n) ^ AsconMac.rotr64(state[1], 39n); + state[2] ^= AsconMac.rotr64(state[2], 1n) ^ AsconMac.rotr64(state[2], 6n); + state[3] ^= AsconMac.rotr64(state[3], 10n) ^ AsconMac.rotr64(state[3], 17n); + state[4] ^= AsconMac.rotr64(state[4], 7n) ^ AsconMac.rotr64(state[4], 41n); + } + } + + /** + * 64-bit rotate right + */ + static rotr64(val, n) { + const mask = 0xFFFFFFFFFFFFFFFFn; + val = val & mask; + return ((val >> n) | (val << (64n - n))) & mask; + } +} + +export default AsconMac; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index f147e9e7..87ade61d 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -16,6 +16,7 @@ import { setLongTestFailure, logTestReport } from "../lib/utils.mjs"; import TestRegister from "../lib/TestRegister.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/AlternatingCaps.mjs"; +import "./tests/Ascon.mjs"; import "./tests/AvroToJSON.mjs"; import "./tests/BaconCipher.mjs"; import "./tests/Base32.mjs"; diff --git a/tests/operations/tests/Ascon.mjs b/tests/operations/tests/Ascon.mjs new file mode 100644 index 00000000..dca3b485 --- /dev/null +++ b/tests/operations/tests/Ascon.mjs @@ -0,0 +1,501 @@ +/** + * Ascon tests. + * + * Test vectors include official NIST ACVP vectors from: + * https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files/Ascon-Hash256-SP800-232 + * https://github.com/ascon/ascon-c (LWC_AEAD_KAT files) + * + * @author Medjedtxm + * @copyright Crown Copyright 2025 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + // ============= Ascon Hash Tests (NIST SP 800-232) ============= + // Official NIST ACVP test vector + { + name: "Ascon Hash: NIST ACVP vector (msg=0x50)", + input: "P", // 0x50 + expectedOutput: "b96da347d720272533a87f5a94a356155f49cdf7c0c10a3e6f346d8a2293e480", + recipeConfig: [ + { + "op": "Ascon Hash", + "args": [] + } + ], + }, + { + name: "Ascon Hash: empty input", + input: "", + expectedOutput: "0b3be5850f2f6b98caf29f8fdea89b64a1fa70aa249b8f839bd53baa304d92b2", + recipeConfig: [ + { + "op": "Ascon Hash", + "args": [] + } + ], + }, + { + name: "Ascon Hash: Hello", + input: "Hello", + expectedOutput: "c1beebe1251d562c4526d6b947cefb932998499424f6cd186e764aa0a36cddb7", + recipeConfig: [ + { + "op": "Ascon Hash", + "args": [] + } + ], + }, + { + name: "Ascon Hash: Hello, World!", + input: "Hello, World!", + expectedOutput: "f40e1ce8d4272e628e9535193f196f4ff2a720b00f6380c5d6f16b975f3a7777", + recipeConfig: [ + { + "op": "Ascon Hash", + "args": [] + } + ], + }, + + // ============= Ascon MAC Tests (NIST LWC_MAC_KAT_128_128.txt) ============= + // Official test vectors from ascon-c: https://github.com/ascon/ascon-c/blob/main/crypto_auth/asconmacv13/LWC_MAC_KAT_128_128.txt + { + name: "Ascon MAC: NIST KAT Count=1 (empty message)", + input: "", + expectedOutput: "eac9d74bbedf8bf1eba2862b26aa6d39", + recipeConfig: [ + { + "op": "Ascon MAC", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"} + ] + } + ], + }, + { + name: "Ascon MAC: NIST KAT Count=2 (Msg=0x10)", + input: "\x10", + expectedOutput: "e5be5b6dfb7b0e3eae00a070791947a8", + recipeConfig: [ + { + "op": "Ascon MAC", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"} + ] + } + ], + }, + { + name: "Ascon MAC: NIST KAT Count=5 (Msg=0x10111213)", + input: "\x10\x11\x12\x13", + expectedOutput: "727f6386405a52ad7ca0669a6a885294", + recipeConfig: [ + { + "op": "Ascon MAC", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"} + ] + } + ], + }, + { + name: "Ascon MAC: invalid key length", + input: "test", + expectedOutput: "Invalid key length: 8 bytes.\n\nAscon-Mac requires a key of exactly 16 bytes (128 bits).", + recipeConfig: [ + { + "op": "Ascon MAC", + "args": [ + {"option": "Hex", "string": "0001020304050607"} + ] + } + ], + }, + + // ============= Ascon Encrypt Tests (NIST SP 800-232) ============= + // Official NIST ascon-c KAT test vector (Count=1) + // https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt + { + name: "Ascon Encrypt: NIST KAT Count=1 (empty PT, empty AD)", + input: "", + expectedOutput: "4f9c278211bec9316bf68f46ee8b2ec6", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + // Official NIST ascon-c KAT test vector (Count=2) + { + name: "Ascon Encrypt: NIST KAT Count=2 (empty PT, AD=0x30)", + input: "", + expectedOutput: "cccb674fe18a09a285d6ab11b35675c0", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "30"}, + "Raw", "Hex" + ] + } + ], + }, + // Official NIST ascon-c KAT test vector (Count=34) - PT=0x20 + { + name: "Ascon Encrypt: NIST KAT Count=34 (PT=0x20, empty AD)", + input: "\x20", + expectedOutput: "e8dd576aba1cd3e6fc704de02aedb79588", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + // Official NIST ascon-c KAT test vector (Count=341) - PT + AD + { + name: "Ascon Encrypt: NIST KAT Count=341 (PT=10 bytes, AD=10 bytes)", + input: "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29", + expectedOutput: "12042996da42b4536e5a0e64692cf6041ff8c367e1423253c84c", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "30313233343536373839"}, + "Raw", "Hex" + ] + } + ], + }, + // Official NIST ascon-c KAT test vector (PT=16 bytes, AD=16 bytes) + { + name: "Ascon Encrypt: NIST KAT (PT=16 bytes, AD=16 bytes)", + input: "\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f", + expectedOutput: "6373ebb28be97c9bac090cf399c13ef13abfc0d209e8f4844c90814d13f32c59", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "Hex", "string": "303132333435363738393a3b3c3d3e3f"}, + "Raw", "Hex" + ] + } + ], + }, + // https://github.com/ascon/ascon-c/blob/main/crypto_aead/asconaead128/LWC_AEAD_KAT_128_128.txt + { + name: "Ascon Encrypt: no key", + input: "test message", + expectedOutput: `Invalid key length: 0 bytes. + +Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`, + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": ""}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: invalid key length", + input: "test message", + expectedOutput: `Invalid key length: 8 bytes. + +Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`, + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "0001020304050607"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: no nonce", + input: "test message", + expectedOutput: `Invalid nonce length: 0 bytes. + +Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`, + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: invalid nonce length", + input: "test message", + expectedOutput: `Invalid nonce length: 12 bytes. + +Ascon-AEAD128 requires a nonce of exactly 16 bytes (128 bits).`, + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: basic encryption", + input: "Hello", + expectedOutput: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: with associated data", + input: "Hello", + expectedOutput: "351880c09f9dee12c20c4ba973066bc10dd26000b6", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "UTF8", "string": "metadata"}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: longer message", + input: "test message", + expectedOutput: "9314a3fef6cc299a07b8c9e0f9e479ca0d1187e87345cf590adc572b", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: empty plaintext", + input: "", + expectedOutput: "4427d64b8e1e1451fc445960f0839bb0", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + { + name: "Ascon Encrypt: zero key and nonce", + input: "Hello", + expectedOutput: "403281e117ebb087e2d9196552b2d123bccb7b5500", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": "00000000000000000000000000000000"}, + {"option": "Hex", "string": ""}, + "Raw", "Hex" + ] + } + ], + }, + + // ============= Ascon Decrypt Tests ============= + { + name: "Ascon Decrypt: no key", + input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0", + expectedOutput: `Invalid key length: 0 bytes. + +Ascon-AEAD128 requires a key of exactly 16 bytes (128 bits).`, + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": ""}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: basic decryption", + input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0", + expectedOutput: "Hello", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: with associated data", + input: "351880c09f9dee12c20c4ba973066bc10dd26000b6", + expectedOutput: "Hello", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "UTF8", "string": "metadata"}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: longer message", + input: "9314a3fef6cc299a07b8c9e0f9e479ca0d1187e87345cf590adc572b", + expectedOutput: "test message", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: authentication failure (tampered ciphertext)", + input: "bf14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0", + expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: authentication failure (wrong key)", + input: "af14bce6b9b6588c3aa63f9ddc5a0cf5f565f358b0", + expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "ff0102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": ""}, + "Hex", "Raw" + ] + } + ], + }, + { + name: "Ascon Decrypt: authentication failure (wrong associated data)", + input: "351880c09f9dee12c20c4ba973066bc10dd26000b6", + expectedOutput: "Unable to decrypt: authentication failed. The ciphertext, key, nonce, or associated data may be incorrect or tampered with.", + recipeConfig: [ + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "UTF8", "string": "wrong data"}, + "Hex", "Raw" + ] + } + ], + }, + + // ============= Round-trip Tests ============= + { + name: "Ascon: encrypt then decrypt round-trip", + input: "This is a test message for Ascon AEAD encryption!", + expectedOutput: "This is a test message for Ascon AEAD encryption!", + recipeConfig: [ + { + "op": "Ascon Encrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "UTF8", "string": "additional data"}, + "Raw", "Hex" + ] + }, + { + "op": "Ascon Decrypt", + "args": [ + {"option": "Hex", "string": "000102030405060708090a0b0c0d0e0f"}, + {"option": "Hex", "string": "101112131415161718191a1b1c1d1e1f"}, + {"option": "UTF8", "string": "additional data"}, + "Hex", "Raw" + ] + } + ], + }, +]);