Add Ascon (NIST SP 800-232) operations: Hash, MAC, Encrypt, Decrypt (#2155)

This commit is contained in:
Thomas M
2026-07-04 11:32:23 +01:00
committed by GitHub
parent 4707ddb861
commit 9ab5108664
9 changed files with 1014 additions and 0 deletions
+4
View File
@@ -113,6 +113,8 @@
"SM4 Decrypt",
"RC6 Encrypt",
"RC6 Decrypt",
"Ascon Encrypt",
"Ascon Decrypt",
"GOST Encrypt",
"GOST Decrypt",
"GOST Sign",
@@ -446,6 +448,8 @@
"BLAKE2b",
"BLAKE2s",
"BLAKE3",
"Ascon Hash",
"Ascon MAC",
"GOST Hash",
"Streebog",
"SSDEEP",
+112
View File
@@ -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.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>Nonce:</b> Must be exactly 16 bytes (128 bits). Must match the nonce used during encryption.<br><br><b>Associated Data:</b> 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;
+108
View File
@@ -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.<br><br><b>Key:</b> Must be exactly 16 bytes (128 bits).<br><br><b>Nonce:</b> 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.<br><br><b>Associated Data:</b> Optional additional data that is authenticated but not encrypted. Useful for including metadata like headers or timestamps.<br><br>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;
+49
View File
@@ -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.<br><br>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;
+68
View File
@@ -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.<br><br>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;
+162
View File
@@ -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;