diff --git a/build/dist.js b/build/dist.js index c0062f0..6503ede 100644 --- a/build/dist.js +++ b/build/dist.js @@ -3,15 +3,19 @@ const fs = require('fs') const packageJson = require('../package.json') const srcFile = __dirname + '/../dist/ascon.js' -let contents = fs.readFileSync(srcFile).toString() +const srcFileEs6 = __dirname + '/../dist/ascon.es6.js' +let contents = fs.readFileSync(srcFile).toString().replace('export default JsAscon;', '') contents = '// js-ascon v' + packageJson.version + ' @ ' + packageJson.homepage + '\n' + contents -contents = contents.replace(/export default class JsAscon/, 'class JsAscon') contents += ` if (typeof module !== 'undefined' && module.exports) { module.exports = JsAscon } + if(typeof crypto === 'undefined' && typeof global !== 'undefined'){ global.crypto = require('crypto') } ` -fs.writeFileSync(srcFile, contents) \ No newline at end of file +let contentsCommonJs = contents +fs.writeFileSync(srcFile, contentsCommonJs) +let contentsEs6 = contents.replace(/^class JsAscon/m, 'export default class JsAscon') +fs.writeFileSync(srcFileEs6, contentsEs6) \ No newline at end of file diff --git a/dist/ascon.es6.js b/dist/ascon.es6.js new file mode 100644 index 0000000..1e27974 --- /dev/null +++ b/dist/ascon.es6.js @@ -0,0 +1,627 @@ +// js-ascon v1.0.0 @ https://github.com/brainfoolong/js-ascon +/** + * Javascript / Typescript implementation of Ascon v1.2 + * Heavily inspired by the python implementation of https://github.com/meichlseder/pyascon + * @link https://github.com/brainfoolong/js-ascon + * @author BrainFooLong (Roland Eigelsreiter) + * @version 1.0.0 + */ +export default class JsAscon { + /** + * Encrypt any message to a hex string + * @param {string|Uint8Array} secretKey Your "password", so to say + * @param {any} messageToEncrypt Any type of message + * @param {any} associatedData Any type of associated data + * @param {string} cipherVariant See JsAscon.encrypt() + * @return {string} + */ + static encryptToHex(secretKey, messageToEncrypt, associatedData = null, cipherVariant = 'Ascon-128') { + const key = JsAscon.hash(secretKey, 'Ascon-Xof', cipherVariant === 'Ascon-80pq' ? 20 : 16); + const nonce = JsAscon.getRandomUintArray(16); + const ciphertext = JsAscon.encrypt(key, nonce, associatedData !== null ? JSON.stringify(associatedData) : '', JSON.stringify(messageToEncrypt), cipherVariant); + return JsAscon.byteArrayToHex(ciphertext).substring(2) + JsAscon.byteArrayToHex(nonce).substring(2); + } + /** + * Decrypt any message from a hex string previously generated with encryptToHex + * @param {string|Uint8Array} secretKey Your "password", so to say + * @param {string} hexStr Any type of message + * @param {any} associatedData Any type of associated data + * @param {string} cipherVariant See JsAscon.encrypt() + * @return {any} Null indicate unsuccessfull decrypt + */ + static decryptFromHex(secretKey, hexStr, associatedData = null, cipherVariant = 'Ascon-128') { + const key = JsAscon.hash(secretKey, 'Ascon-Xof', cipherVariant === 'Ascon-80pq' ? 20 : 16); + const hexData = Uint8Array.from(hexStr.match(/.{1,2}/g).map((byte) => parseInt(byte, 16))); + const plaintextMessage = JsAscon.decrypt(key, hexData.slice(-16), associatedData !== null ? JSON.stringify(associatedData) : '', hexData.slice(0, -16), cipherVariant); + return plaintextMessage !== null ? JSON.parse(JsAscon.byteArrayToStr(plaintextMessage)) : null; + } + /** + * Ascon encryption + * @param {string|Uint8Array|number[]} key A string or byte array of a length 16 (for Ascon-128, Ascon-128a; 128-bit security) or + * 20 (for Ascon-80pq; 128-bit security) + * @param {string|Uint8Array|number[]} nonce A string or byte array of a length of 16 bytes (must not repeat for the same key!) + * @param {string|Uint8Array|number[]} associatedData A string or byte array of any length + * @param {string|Uint8Array|number[]} plaintext A string or byte array of any length + * @param {string} variant "Ascon-128", "Ascon-128a", or "Ascon-80pq" (specifies key size, rate and number of + * rounds) + * @return {Uint8Array} + */ + static encrypt(key, nonce, associatedData, plaintext, variant = 'Ascon-128') { + key = JsAscon.anyToByteArray(key); + const keyLength = key.length; + nonce = JsAscon.anyToByteArray(nonce); + const nonceLength = nonce.length; + JsAscon.assertInArray(variant, ['Ascon-128', 'Ascon-128a', 'Ascon-80pq'], 'Encrypt variant'); + if (['Ascon-128', 'Ascon-128a'].indexOf(variant) > -1) { + JsAscon.assert(keyLength === 16 && nonceLength === 16, 'Incorrect key (' + keyLength + ') or nonce(' + nonceLength + ') length'); + } + else { + JsAscon.assert(keyLength === 20 && nonceLength === 16, 'Incorrect key (' + keyLength + ') or nonce(' + nonceLength + ') length'); + } + const data = []; + const keySizeBits = keyLength * 8; + const permutationRoundsA = 12; + const permutationRoundsB = variant === 'Ascon-128a' ? 8 : 6; + const rate = variant === 'Ascon-128a' ? 16 : 8; + JsAscon.initialize(data, keySizeBits, rate, permutationRoundsA, permutationRoundsB, key, nonce); + associatedData = JsAscon.anyToByteArray(associatedData); + JsAscon.processAssociatedData(data, permutationRoundsB, rate, associatedData); + plaintext = JsAscon.anyToByteArray(plaintext); + const ciphertext = JsAscon.processPlaintext(data, permutationRoundsB, rate, plaintext); + const tag = JsAscon.finalize(data, permutationRoundsA, rate, key); + return JsAscon.concatByteArrays(ciphertext, tag); + } + /** + * Ascon decryption + * @param {string|Uint8Array|number[]} key A string or byte array of a length 16 (for Ascon-128, Ascon-128a; 128-bit security) or + * 20 (for Ascon-80pq; 128-bit security) + * @param {string|Uint8Array|number[]} nonce A string or byte array of a length of 16 bytes (must not repeat for the same key!) + * @param {string|Uint8Array|number[]} associatedData A string or byte array of any length + * @param {string|Uint8Array|number[]} ciphertextAndTag A string or byte array of any length + * @param {string} variant "Ascon-128", "Ascon-128a", or "Ascon-80pq" (specifies key size, rate and number of + * rounds) + * @return {Uint8Array|null} Returns plaintext as byte array or NULL when cannot decrypt + */ + static decrypt(key, nonce, associatedData, ciphertextAndTag, variant = 'Ascon-128') { + key = JsAscon.anyToByteArray(key); + const keyLength = key.length; + nonce = JsAscon.anyToByteArray(nonce); + const nonceLength = nonce.length; + JsAscon.assertInArray(variant, ['Ascon-128', 'Ascon-128a', 'Ascon-80pq'], 'Encrypt variant'); + if (['Ascon-128', 'Ascon-128a'].indexOf(variant) > -1) { + JsAscon.assert(keyLength === 16 && nonceLength === 16, 'Incorrect key (' + keyLength + ') or nonce(' + nonceLength + ') length'); + } + else { + JsAscon.assert(keyLength === 20 && nonceLength === 16, 'Incorrect key (' + keyLength + ') or nonce(' + nonceLength + ') length'); + } + const data = []; + const keySizeBits = keyLength * 8; + const permutationRoundsA = 12; + const permutationRoundsB = variant === 'Ascon-128a' ? 8 : 6; + const rate = variant === 'Ascon-128a' ? 16 : 8; + JsAscon.initialize(data, keySizeBits, rate, permutationRoundsA, permutationRoundsB, key, nonce); + associatedData = JsAscon.anyToByteArray(associatedData); + JsAscon.processAssociatedData(data, permutationRoundsB, rate, associatedData); + ciphertextAndTag = JsAscon.anyToByteArray(ciphertextAndTag); + const ciphertext = ciphertextAndTag.slice(0, -16); + const ciphertextTag = ciphertextAndTag.slice(-16); + const plaintext = JsAscon.processCiphertext(data, permutationRoundsB, rate, ciphertext); + const tag = JsAscon.finalize(data, permutationRoundsA, rate, key); + if (JsAscon.byteArrayToHex(tag) === JsAscon.byteArrayToHex(ciphertextTag)) { + return plaintext; + } + return null; + } + /** + * Ascon hash function and extendable-output function + * @param {string|Uint8Array} message A string or byte array + * @param {string} variant "Ascon-Hash", "Ascon-Hasha" (both with 256-bit output for 128-bit security), "Ascon-Xof", + * or "Ascon-Xofa" (both with arbitrary output length, security=min(128, bitlen/2)) + * @param {number} hashLength The requested output bytelength (must be 32 for variant "Ascon-Hash"; can be arbitrary + * for Ascon-Xof, but should be >= 32 for 128-bit security) + * @return {Uint8Array} The byte array representing the hash tag + */ + static hash(message, variant = 'Ascon-Hash', hashLength = 32) { + JsAscon.assertInArray(variant, ['Ascon-Hash', 'Ascon-Hasha', 'Ascon-Xof', 'Ascon-Xofa'], 'Hash variant'); + if (['Ascon-Hash', 'Ascon-Hasha'].indexOf(variant) > -1) { + JsAscon.assert(hashLength === 32, 'Incorrect hash length'); + } + message = JsAscon.anyToByteArray(message); + const messageLength = message.length; + const permutationRoundsA = 12; + const permutationRoundsB = ['Ascon-Hasha', 'Ascon-Xofa'].indexOf(variant) > -1 ? 8 : 12; + const rate = 8; + const data = JsAscon.byteArrayToState(JsAscon.concatByteArrays([0, rate * 8, permutationRoundsA, permutationRoundsA - permutationRoundsB], [0, 0, ['Ascon-Hash', 'Ascon-Hasha'].indexOf(variant) > -1 ? 1 : 0, 0], // tagspec, + new Uint8Array(32))); + JsAscon.debug('initial value', data, true); + JsAscon.permutation(data, permutationRoundsA); + JsAscon.debug('initialization', data, true); + // message processing (absorbing) + const messagePadded = JsAscon.concatByteArrays(message, [0x80], new Uint8Array(rate - (messageLength % rate) - 1)); + const messagePaddedLength = messagePadded.length; + // first s-1 blocks + for (let block = 0; block < messagePaddedLength - rate; block += rate) { + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + JsAscon.permutation(data, permutationRoundsB); + } + // last block + const block = messagePaddedLength - rate; + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + JsAscon.debug('process message', data); + // finalization (squeezing) + let hash = []; + JsAscon.permutation(data, permutationRoundsA); + while (hash.length < hashLength) { + hash = hash.concat(...JsAscon.bigIntToByteArray(data[0])); + JsAscon.permutation(data, permutationRoundsB); + } + JsAscon.debug('finalization', data); + return new Uint8Array(hash); + } + /** + * Ascon message authentication code (MAC) and pseudorandom function (PRF) + * @param {string|number[]|Uint8Array} key A string or byte array of a length of 16 bytes + * @param {string|number[]|Uint8Array} message A string or byte array (<= 16 for "Ascon-PrfShort") + * @param {string} variant "Ascon-Mac", "Ascon-Maca" (both 128-bit output, arbitrarily long input), "Ascon-Prf", + * "Ascon-Prfa" (both arbitrarily long input and output), or "Ascon-PrfShort" (t-bit output for t<=128, m-bit + * input for m<=128) + * @param {number} tagLength The requested output bytelength l/8 (must be <=16 for variants "Ascon-Mac", "Ascon-Maca", + * and "Ascon-PrfShort", arbitrary for "Ascon-Prf", "Ascon-Prfa"; should be >= 16 for 128-bit security) + * @return {Uint8Array} The byte array representing the authentication tag + */ + static mac(key, message, variant = 'Ascon-Mac', tagLength = 16) { + JsAscon.assertInArray(variant, ['Ascon-Mac', 'Ascon-Prf', 'Ascon-Maca', 'Ascon-Prfa', 'Ascon-PrfShort'], 'Mac variant'); + key = JsAscon.anyToByteArray(key); + const keyLength = key.length; + message = JsAscon.anyToByteArray(message); + const messageLength = message.length; + if (['Ascon-Mac', 'Ascon-Maca'].indexOf(variant) > -1) { + JsAscon.assert(keyLength === 16 && tagLength <= 16, 'Incorrect key length'); + } + else if (['Ascon-Prf', 'Ascon-Prfa'].indexOf(variant) > -1) { + JsAscon.assert(keyLength === 16, 'Incorrect key length'); + } + else if (variant === 'Ascon-PrfShort') { + JsAscon.assert(messageLength <= 16, 'Message to long for variant ' + variant); + JsAscon.assert(keyLength === 16 && tagLength <= 16 && messageLength <= 16, 'Incorrect key length'); + } + const permutationRoundsA = 12; + const permutationRoundsB = ['Ascon-Prfa', 'Ascon-Maca'].indexOf(variant) > -1 ? 8 : 12; + const messageBlockSize = ['Ascon-Prfa', 'Ascon-Maca'].indexOf(variant) > -1 ? 40 : 32; + const rate = 16; + if (variant === 'Ascon-PrfShort') { + const data = JsAscon.byteArrayToState(JsAscon.concatByteArrays([keyLength * 8, messageLength * 8, permutationRoundsA + 64, tagLength * 8, 0, 0, 0, 0], key, message, new Uint8Array(16 - messageLength))); + JsAscon.debug('initial value', data); + JsAscon.permutation(data, permutationRoundsA); + JsAscon.debug('process message', data); + data[3] ^= JsAscon.byteArrayToBigInt(key, 0); + data[4] ^= JsAscon.byteArrayToBigInt(key, 8); + return new Uint8Array([...JsAscon.bigIntToByteArray(data[3]), ...JsAscon.bigIntToByteArray(data[4])]); + } + const data = JsAscon.byteArrayToState(JsAscon.concatByteArrays([keyLength * 8, rate * 8, permutationRoundsA + 128, permutationRoundsA - permutationRoundsB], [0, 0, 0, ['Ascon-Mac', 'Ascon-Maca'].indexOf(variant) > -1 ? 128 : 0], // tagspec + key, new Uint8Array(16))); + JsAscon.debug('initial value', data); + JsAscon.permutation(data, permutationRoundsA); + JsAscon.debug('initialization', data); + // message processing (absorbing) + const messagePadded = JsAscon.concatByteArrays(message, [0x80], new Uint8Array(messageBlockSize - (messageLength % messageBlockSize) - 1)); + const messagePaddedLength = messagePadded.length; + const iterations = ['Ascon-Prfa', 'Ascon-Maca'].indexOf(variant) > -1 ? 4 : 3; + // first s-1 blocks + for (let block = 0; block < messagePaddedLength - messageBlockSize; block += messageBlockSize) { + for (let i = 0; i <= iterations; i++) { + data[i] ^= JsAscon.byteArrayToBigInt(messagePadded, block + (i * 8)); + } + JsAscon.permutation(data, permutationRoundsB); + } + // last block + const block = messagePaddedLength - messageBlockSize; + for (let i = 0; i <= iterations; i++) { + data[i] ^= JsAscon.byteArrayToBigInt(messagePadded, block + (i * 8)); + } + data[4] ^= 1n; + JsAscon.debug('process message', data); + // finalization (squeezing) + let tag = []; + JsAscon.permutation(data, permutationRoundsA); + while (tag.length < tagLength) { + tag = tag.concat(...JsAscon.bigIntToByteArray(data[0]), ...JsAscon.bigIntToByteArray(data[1])); + JsAscon.permutation(data, permutationRoundsB); + } + JsAscon.debug('finalization', data); + return new Uint8Array(tag); + } + /** + * Ascon initialization phase - internal helper function + * @param {BigInt[]} data Ascon state, a list of 5 64-bit integers + * @param {number} keySize Key size in bits + * @param {number} rate Block size in bytes (8 for Ascon-128, Ascon-80pq; 16 for Ascon-128a) + * @param {number} permutationRoundsA Number of initialization/finalization rounds for permutation + * @param {number} permutationRoundsB Number of intermediate rounds for permutation + * @param {Uint8Array} key A bytes object of size 16 (for Ascon-128, Ascon-128a; 128-bit security) or 20 (for Ascon-80pq; + * 128-bit security) + * @param {Uint8Array} nonce A bytes object of size 16 + */ + static initialize(data, keySize, rate, permutationRoundsA, permutationRoundsB, key, nonce) { + JsAscon.byteArrayToState(JsAscon.concatByteArrays([keySize, rate * 8, permutationRoundsA, permutationRoundsB], new Uint8Array(20 - key.length), key, nonce), data); + JsAscon.debug('initial value', data); + JsAscon.permutation(data, permutationRoundsA); + const zeroKey = JsAscon.byteArrayToState(JsAscon.concatByteArrays(new Uint8Array(40 - key.length), key)); + for (let i = 0; i <= 4; i++) { + data[i] ^= zeroKey[i]; + } + JsAscon.debug('initialization', data); + } + /** + * Ascon associated data processing phase - internal helper function + * @param {BigInt[]} data data Ascon state, a list of 5 64-bit integers + * @param {number} permutationRoundsB Number of intermediate rounds for permutation + * @param {number} rate Block size in bytes (8 for Ascon-128, Ascon-80pq; 16 for Ascon-128a) + * @param {Uint8Array} associatedData A byte array of any length + */ + static processAssociatedData(data, permutationRoundsB, rate, associatedData) { + if (associatedData.length) { + // message processing (absorbing) + const messagePadded = JsAscon.concatByteArrays(associatedData, [0x80], new Uint8Array(rate - (associatedData.length % rate) - 1)); + const messagePaddedLength = messagePadded.length; + for (let block = 0; block < messagePaddedLength; block += rate) { + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + if (rate === 16) { + data[1] ^= JsAscon.byteArrayToBigInt(messagePadded, block + 8); + } + JsAscon.permutation(data, permutationRoundsB); + } + } + data[4] ^= 1n; + JsAscon.debug('process associated data', data); + } + /** + * Ascon plaintext processing phase (during encryption) - internal helper function + * @param {BigInt[]} data data Ascon state, a list of 5 64-bit integers + * @param {number} permutationRoundsB Number of intermediate rounds for permutation + * @param {number} rate Block size in bytes (8 for Ascon-128, Ascon-80pq; 16 for Ascon-128a) + * @param {Uint8Array} plaintext A byte array of any length + * @return {Uint8Array} Returns the ciphertext as byte array + */ + static processPlaintext(data, permutationRoundsB, rate, plaintext) { + const lastLen = plaintext.length % rate; + const messagePadded = JsAscon.concatByteArrays(plaintext, [0x80], new Uint8Array(rate - lastLen - 1)); + const messagePaddedLength = messagePadded.length; + let ciphertext = new Uint8Array(0); + // first t-1 blocks + for (let block = 0; block < messagePaddedLength - rate; block += rate) { + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + ciphertext = JsAscon.concatByteArrays(ciphertext, JsAscon.bigIntToByteArray(data[0])); + if (rate === 16) { + data[1] ^= JsAscon.byteArrayToBigInt(messagePadded, block + 8); + ciphertext = JsAscon.concatByteArrays(ciphertext, JsAscon.bigIntToByteArray(data[1])); + } + JsAscon.permutation(data, permutationRoundsB); + } + // last block + const block = messagePaddedLength - rate; + if (rate === 8) { + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + ciphertext = JsAscon.concatByteArrays(ciphertext, JsAscon.bigIntToByteArray(data[0]).slice(0, lastLen)); + } + else if (rate === 16) { + data[0] ^= JsAscon.byteArrayToBigInt(messagePadded, block); + data[1] ^= JsAscon.byteArrayToBigInt(messagePadded, block + 8); + ciphertext = JsAscon.concatByteArrays(ciphertext, JsAscon.bigIntToByteArray(data[0]).slice(0, lastLen > 8 ? 8 : lastLen), JsAscon.bigIntToByteArray(data[1]).slice(0, lastLen - 8 < 0 ? 0 : lastLen - 8)); + } + JsAscon.debug('process plaintext', data); + return ciphertext; + } + /** + * Ascon plaintext processing phase (during encryption) - internal helper function + * @param {BigInt[]} data data Ascon state, a list of 5 64-bit integers + * @param {number} permutationRoundsB Number of intermediate rounds for permutation + * @param {number} rate Block size in bytes (8 for Ascon-128, Ascon-80pq; 16 for Ascon-128a) + * @param {Uint8Array} ciphertext A byte array of any length + * @return {Uint8Array} Returns the ciphertext as byte array + */ + static processCiphertext(data, permutationRoundsB, rate, ciphertext) { + const lastLen = ciphertext.length % rate; + const messagePadded = JsAscon.concatByteArrays(ciphertext, new Uint8Array(rate - lastLen)); + const messagePaddedLength = messagePadded.length; + let plaintext = new Uint8Array(0); + // first t-1 blocks + for (let block = 0; block < messagePaddedLength - rate; block += rate) { + let ci = JsAscon.byteArrayToBigInt(messagePadded, block); + plaintext = JsAscon.concatByteArrays(plaintext, JsAscon.bigIntToByteArray(data[0] ^ ci)); + data[0] = ci; + if (rate === 16) { + ci = JsAscon.byteArrayToBigInt(messagePadded, block + 8); + plaintext = JsAscon.concatByteArrays(plaintext, JsAscon.bigIntToByteArray(data[1] ^ ci)); + data[1] = ci; + } + JsAscon.permutation(data, permutationRoundsB); + } + // last block + const block = messagePaddedLength - rate; + if (rate === 8) { + let ci = JsAscon.byteArrayToBigInt(messagePadded, block); + plaintext = JsAscon.concatByteArrays(plaintext, JsAscon.bigIntToByteArray(ci ^ data[0]).slice(0, lastLen)); + const padding = 0x80n << BigInt((rate - lastLen - 1) * 8); + const mask = BigInt('0xFFFFFFFFFFFFFFFF') >> BigInt(lastLen * 8); + data[0] = ci ^ (data[0] & mask) ^ padding; + } + else if (rate === 16) { + const lastLenWord = lastLen % 8; + const padding = 0x80n << BigInt((8 - lastLenWord - 1) * 8); + const mask = BigInt('0xFFFFFFFFFFFFFFFF') >> BigInt(lastLenWord * 8); + let ciA = JsAscon.byteArrayToBigInt(messagePadded, block); + let ciB = JsAscon.byteArrayToBigInt(messagePadded, block + 8); + plaintext = JsAscon.concatByteArrays(plaintext, JsAscon.concatByteArrays(JsAscon.bigIntToByteArray(data[0] ^ ciA), JsAscon.bigIntToByteArray(data[1] ^ ciB)).slice(0, lastLen)); + if (lastLen < 8) { + data[0] = ciA ^ (data[0] & mask) ^ padding; + } + else { + data[0] = ciA; + data[1] = ciB ^ (data[1] & mask) ^ padding; + } + } + JsAscon.debug('process ciphertext', data); + return plaintext; + } + /** + * Ascon finalization phase - internal helper function + * + * @param {BigInt[]} data data Ascon state, a list of 5 64-bit integers + * @param {number} permutationRoundsA Number of initialization/finalization rounds for permutation + * @param {number} rate Block size in bytes (8 for Ascon-128, Ascon-80pq; 16 for Ascon-128a) + * @param {Uint8Array} key A bytes array of size 16 (for Ascon-128, Ascon-128a; 128-bit security) or 20 (for Ascon-80pq; + * 128-bit security) + * @return {Uint8Array} The tag as a byte array + */ + static finalize(data, permutationRoundsA, rate, key) { + let zeroFilledKey = key; + if (key.length > 16) { + const newLen = key.length + 24 - key.length; + zeroFilledKey = new Uint8Array(newLen); + zeroFilledKey.set(key); + } + let index = (rate / 8) | 0; + data[index++] ^= JsAscon.byteArrayToBigInt(key, 0); + data[index++] ^= JsAscon.byteArrayToBigInt(key, 8); + data[index++] ^= JsAscon.byteArrayToBigInt(zeroFilledKey, 16); + JsAscon.permutation(data, permutationRoundsA); + data[3] ^= JsAscon.byteArrayToBigInt(key, -16); + data[4] ^= JsAscon.byteArrayToBigInt(key, -8); + JsAscon.debug('finalization', data); + return JsAscon.concatByteArrays(JsAscon.bigIntToByteArray(data[3]), JsAscon.bigIntToByteArray(data[4])); + } + /** + * Ascon core permutation for the sponge construction - internal helper function + * @param {BigInt[]} data Ascon state, a list of 5 64-bit integers + * @param {number} rounds + */ + static permutation(data, rounds = 1) { + JsAscon.assert(rounds <= 12, 'Permutation rounds must be <= 12'); + JsAscon.debug('permutation input', data, true); + for (let round = 12 - rounds; round < 12; round++) { + // add round constants + data[2] ^= BigInt(0xf0 - round * 0x10 + round); + JsAscon.debug('round constant addition', data, true); + // substitution layer + data[0] ^= data[4]; + data[4] ^= data[3]; + data[2] ^= data[1]; + let t = []; + for (let i = 0; i <= 4; i++) { + t[i] = (data[i] ^ BigInt('0xffffffffffffffff')) & data[(i + 1) % 5]; + } + for (let i = 0; i <= 4; i++) { + data[i] ^= t[(i + 1) % 5]; + } + data[1] ^= data[0]; + data[0] ^= data[4]; + data[3] ^= data[2]; + data[2] ^= BigInt('0xffffffffffffffff'); + JsAscon.debug('substitution layer', data, true); + // linear diffusion layer + data[0] ^= JsAscon.bitRotateRight(data[0], 19) ^ JsAscon.bitRotateRight(data[0], 28); + data[1] ^= JsAscon.bitRotateRight(data[1], 61) ^ JsAscon.bitRotateRight(data[1], 39); + data[2] ^= JsAscon.bitRotateRight(data[2], 1) ^ JsAscon.bitRotateRight(data[2], 6); + data[3] ^= JsAscon.bitRotateRight(data[3], 10) ^ JsAscon.bitRotateRight(data[3], 17); + data[4] ^= JsAscon.bitRotateRight(data[4], 7) ^ JsAscon.bitRotateRight(data[4], 41); + JsAscon.debug('linear diffusion layer', data, true); + } + } + /** + * Concat any amount of byte array to single byte array + * @param {ArrayLike[]} arrays + * @return {Uint8Array} + */ + static concatByteArrays(...arrays) { + let len = 0; + for (let i = 0; i < arrays.length; i++) { + len += arrays[i].length; + } + const arr = new Uint8Array(len); + let offset = 0; + for (let i = 0; i < arrays.length; i++) { + arr.set(arrays[i], offset); + offset += arrays[i].length; + } + return arr; + } + /** + * Convert a byte array to a binary string + * @param {Uint8Array} byteArray + * @return {string} + */ + static byteArrayToStr(byteArray) { + return new TextDecoder().decode(byteArray); + } + /** + * Convert a any value to a byte array + * @param {string|number[]|Uint8Array} val + * @return {Uint8Array} + */ + static anyToByteArray(val) { + if (val instanceof Uint8Array) { + return val; + } + if (Array.isArray(val)) { + return new Uint8Array(val); + } + return new TextEncoder().encode(val); + } + /** + * Convert given bigint into byte array + * @param {BigInt} nr + * @return {Uint8Array} + */ + static bigIntToByteArray(nr) { + let bytes = 8; + let arr = new Uint8Array(bytes); + while (nr > 0) { + arr[--bytes] = Number(nr & 255n); + nr >>= 8n; + } + return arr; + } + /** + * Convert given byte array into internal state array of 5 bigints + * @param {Uint8Array} byteArray + * @param {BigInt[]|null} fillInto If set, fill this given reference as well + * @return {BigInt[]} + */ + static byteArrayToState(byteArray, fillInto = null) { + const arr = [ + JsAscon.byteArrayToBigInt(byteArray, 0), + JsAscon.byteArrayToBigInt(byteArray, 8), + JsAscon.byteArrayToBigInt(byteArray, 16), + JsAscon.byteArrayToBigInt(byteArray, 24), + JsAscon.byteArrayToBigInt(byteArray, 32) + ]; + if (fillInto !== null) { + for (let i = 0; i < arr.length; i++) { + fillInto[i] = arr[i]; + } + } + return arr; + } + /** + * Convert given byte array to bigint + * @param {Uint8Array} byteArray + * @param {number} offset + * @return {BigInt} + */ + static byteArrayToBigInt(byteArray, offset) { + if (offset < 0) { + offset = byteArray.length + offset; + } + if (byteArray.length - 1 < offset) { + return 0n; + } + return new DataView(byteArray.buffer).getBigUint64(offset); + } + /** + * Convert given byte array to visual hex representation with leading 0x + * @param {Uint8Array} byteArray + * @return {string} + */ + static byteArrayToHex(byteArray) { + return '0x' + Array.from(byteArray).map(x => x.toString(16).padStart(2, '0')).join(''); + } + /** + * Bit shift rotate right integer or given number of places + * @param {BigInt} nr + * @param {number} places + */ + static bitRotateRight(nr, places) { + const placesBig = BigInt(places); + const shift1 = BigInt(1); + const shiftRev = BigInt(64 - places); + return (nr >> placesBig) | ((nr & (shift1 << placesBig) - shift1) << (shiftRev)); + } + /** + * Assert that this is true + * If false, it throw and exception + * @param {string} value + * @param {string[]} values + * @param {string} errorMessage + */ + static assertInArray(value, values, errorMessage) { + JsAscon.assert(values.indexOf(value) > -1, errorMessage + ': Value \'' + value + '\' is not in available choices of\n' + JSON.stringify(values)); + } + /** + * Assert that this is true + * If false, it throw and exception + * @param {any} expected + * @param {any} actual + * @param {string} errorMessage + */ + static assertSame(expected, actual, errorMessage) { + JsAscon.assert(expected === actual, errorMessage + ': Value is expected to be\n' + JSON.stringify(expected) + '\nbut actual value is\n' + JSON.stringify(actual)); + } + /** + * Assert that this is true + * If false, it throw and exception + * @param {boolean} result + * @param {string} errorMessage + */ + static assert(result, errorMessage) { + if (!result) { + throw new Error(errorMessage); + } + } + /** + * Generate a uint array with random bytes with given length + * @param {number} length + * @return {Uint8Array} + */ + static getRandomUintArray(length) { + if (typeof crypto === 'undefined') { + new Error('JsAscon requires the "crypto" library to be installed'); + } + if (typeof crypto.getRandomValues === 'function') { + return crypto.getRandomValues(new Uint8Array(length)); + } + // @ts-ignore + if (typeof crypto.randomBytes === 'function') { + // @ts-ignore + return JsAscon.anyToByteArray(crypto.randomBytes(length)); + } + } + /** + * Debug output + * @param {any} msg + * @param {BigInt[]|null} stateData + * @param {boolean} permutation Is a permutation debug + */ + static debug(msg, stateData = null, permutation = false) { + if (!permutation && !JsAscon.debugEnabled) { + return; + } + if (permutation && !JsAscon.debugPermutationEnabled) { + return; + } + if (stateData) { + let outMsg = '[Ascon Debug] ' + msg + ': ['; + for (let i = 0; i < stateData.length; i++) { + outMsg += '"0x' + stateData[i].toString(16).padStart(16, '0') + '", '; + } + console.log(outMsg.substring(0, outMsg.length - 2) + ']'); + } + else { + console.log('[Ascon Debug] ' + msg); + } + } +} +JsAscon.debugEnabled = false; +JsAscon.debugPermutationEnabled = false; + +if (typeof BigInt === 'undefined') { + throw new Error('Cannot use JsAscon library, BigInt datatype is missing'); +} + +if (typeof module !== 'undefined' && module.exports) { + module.exports = JsAscon +} + +if(typeof crypto === 'undefined' && typeof global !== 'undefined'){ + global.crypto = require('crypto') +} diff --git a/dist/ascon.js b/dist/ascon.js index 47adc74..650f93c 100644 --- a/dist/ascon.js +++ b/dist/ascon.js @@ -1,6 +1,4 @@ // js-ascon v1.0.0 @ https://github.com/brainfoolong/js-ascon -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); /** * Javascript / Typescript implementation of Ascon v1.2 * Heavily inspired by the python implementation of https://github.com/meichlseder/pyascon @@ -19,7 +17,7 @@ class JsAscon { */ static encryptToHex(secretKey, messageToEncrypt, associatedData = null, cipherVariant = 'Ascon-128') { const key = JsAscon.hash(secretKey, 'Ascon-Xof', cipherVariant === 'Ascon-80pq' ? 20 : 16); - const nonce = crypto.getRandomValues(new Uint8Array(16)); + const nonce = JsAscon.getRandomUintArray(16); const ciphertext = JsAscon.encrypt(key, nonce, associatedData !== null ? JSON.stringify(associatedData) : '', JSON.stringify(messageToEncrypt), cipherVariant); return JsAscon.byteArrayToHex(ciphertext).substring(2) + JsAscon.byteArrayToHex(nonce).substring(2); } @@ -462,10 +460,12 @@ class JsAscon { * @return {Uint8Array} */ static anyToByteArray(val) { - if (val instanceof Uint8Array) + if (val instanceof Uint8Array) { return val; - if (Array.isArray(val)) + } + if (Array.isArray(val)) { return new Uint8Array(val); + } return new TextEncoder().encode(val); } /** @@ -513,8 +513,9 @@ class JsAscon { if (offset < 0) { offset = byteArray.length + offset; } - if (byteArray.length - 1 < offset) + if (byteArray.length - 1 < offset) { return 0n; + } return new DataView(byteArray.buffer).getBigUint64(offset); } /** @@ -567,6 +568,24 @@ class JsAscon { throw new Error(errorMessage); } } + /** + * Generate a uint array with random bytes with given length + * @param {number} length + * @return {Uint8Array} + */ + static getRandomUintArray(length) { + if (typeof crypto === 'undefined') { + new Error('JsAscon requires the "crypto" library to be installed'); + } + if (typeof crypto.getRandomValues === 'function') { + return crypto.getRandomValues(new Uint8Array(length)); + } + // @ts-ignore + if (typeof crypto.randomBytes === 'function') { + // @ts-ignore + return JsAscon.anyToByteArray(crypto.randomBytes(length)); + } + } /** * Debug output * @param {any} msg @@ -594,7 +613,7 @@ class JsAscon { } JsAscon.debugEnabled = false; JsAscon.debugPermutationEnabled = false; -exports.default = JsAscon; + if (typeof BigInt === 'undefined') { throw new Error('Cannot use JsAscon library, BigInt datatype is missing'); } @@ -602,6 +621,7 @@ if (typeof BigInt === 'undefined') { if (typeof module !== 'undefined' && module.exports) { module.exports = JsAscon } + if(typeof crypto === 'undefined' && typeof global !== 'undefined'){ global.crypto = require('crypto') } diff --git a/playwright.config.ts b/playwright.config.ts deleted file mode 100644 index afffbfd..0000000 --- a/playwright.config.ts +++ /dev/null @@ -1,108 +0,0 @@ -import type { PlaywrightTestConfig } from '@playwright/test' -import { devices } from '@playwright/test' - -/** - * Read environment variables from file. - * https://github.com/motdotla/dotenv - */ -// require('dotenv').config(); - -/** - * See https://playwright.dev/docs/test-configuration. - */ -const config: PlaywrightTestConfig = { - testDir: './tests', - /* Maximum time one test can run for. */ - timeout: 30 * 1000, - expect: { - /** - * Maximum time expect() should wait for the condition to be met. - * For example in `await expect(locator).toHaveText();` - */ - timeout: 5000 - }, - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - ignoreHTTPSErrors: true, - /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ - actionTimeout: 0, - /* Base URL to use in actions like `await page.goto('/')`. */ - // baseURL: 'http://localhost:3000', - - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, - - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { - ...devices['Desktop Chrome'], - }, - }, - - { - name: 'firefox', - use: { - ...devices['Desktop Firefox'], - }, - }, - - { - name: 'webkit', - use: { - ...devices['Desktop Safari'], - }, - }, - - /* Test against mobile viewports. */ - { - name: 'Mobile Chrome', - use: { - ...devices['Pixel 5'], - }, - }, - { - name: 'Mobile Safari', - use: { - ...devices['iPhone 12'], - }, - }, - - /* Test against branded browsers. */ - { - name: 'Microsoft Edge', - use: { - channel: 'msedge', - }, - }, - { - name: 'Google Chrome', - use: { - channel: 'chrome', - }, - }, - ], - - /* Folder for test artifacts such as screenshots, videos, traces, etc. */ - // outputDir: 'test-results/', - - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // port: 3000, - // }, -} - -export default config diff --git a/src/ascon.ts b/src/ascon.ts index fef64f6..2281e28 100644 --- a/src/ascon.ts +++ b/src/ascon.ts @@ -24,7 +24,7 @@ export default class JsAscon { cipherVariant: string = 'Ascon-128' ): string { const key = JsAscon.hash(secretKey, 'Ascon-Xof', cipherVariant === 'Ascon-80pq' ? 20 : 16) - const nonce = crypto.getRandomValues(new Uint8Array(16)) + const nonce = JsAscon.getRandomUintArray(16) const ciphertext = JsAscon.encrypt( key, nonce, @@ -599,8 +599,12 @@ export default class JsAscon { * @return {Uint8Array} */ public static anyToByteArray (val: any): Uint8Array { - if (val instanceof Uint8Array) return val - if (Array.isArray(val)) return new Uint8Array(val) + if (val instanceof Uint8Array) { + return val + } + if (Array.isArray(val)) { + return new Uint8Array(val) + } return new TextEncoder().encode(val) } @@ -651,7 +655,9 @@ export default class JsAscon { if (offset < 0) { offset = byteArray.length + offset } - if (byteArray.length - 1 < offset) return 0n + if (byteArray.length - 1 < offset) { + return 0n + } return new DataView(byteArray.buffer).getBigUint64(offset) } @@ -717,6 +723,25 @@ export default class JsAscon { } } + /** + * Generate a uint array with random bytes with given length + * @param {number} length + * @return {Uint8Array} + */ + public static getRandomUintArray (length: number): Uint8Array { + if (typeof crypto === 'undefined') { + new Error('JsAscon requires the "crypto" library to be installed') + } + if (typeof crypto.getRandomValues === 'function') { + return crypto.getRandomValues(new Uint8Array(length)) + } + // @ts-ignore + if (typeof crypto.randomBytes === 'function') { + // @ts-ignore + return JsAscon.anyToByteArray(crypto.randomBytes(length)) + } + } + /** * Debug output * @param {any} msg diff --git a/tests/browser-tests.html b/tests/browser-tests.html new file mode 100644 index 0000000..2a5f024 --- /dev/null +++ b/tests/browser-tests.html @@ -0,0 +1,23 @@ + + + + + Js Ascon Browser Tests + + + + + + + + + \ No newline at end of file diff --git a/tests/performance.js b/tests/performance.js index 0ea45f2..6591fb2 100644 --- a/tests/performance.js +++ b/tests/performance.js @@ -1,48 +1,52 @@ -const JsAscon = require('../dist/ascon') +if (typeof window === 'undefined') { + global.JsAscon = require('../dist/ascon') +} +(function () { -JsAscon.debugEnabled = false -JsAscon.debugPermutationEnabled = false + JsAscon.debugEnabled = false + JsAscon.debugPermutationEnabled = false -let cycles = [ - { - 'nr': 10, - 'messageSize': 32, - 'assocSize': 128, - }, - { - 'nr': 10, - 'messageSize': 128, - 'assocSize': 512, - }, - { - 'nr': 10, - 'messageSize': 128 * 8, - 'assocSize': 512 * 4, - }, - { - 'nr': 10, - 'messageSize': 512 * 8, - 'assocSize': 0, - } -] + let cycles = [ + { + 'nr': 10, + 'messageSize': 32, + 'assocSize': 128, + }, + { + 'nr': 10, + 'messageSize': 128, + 'assocSize': 512, + }, + { + 'nr': 10, + 'messageSize': 128 * 8, + 'assocSize': 512 * 4, + }, + { + 'nr': 10, + 'messageSize': 512 * 8, + 'assocSize': 0, + } + ] -for (let i in cycles) { - const cycle = cycles[i] - let totalTime = 0 - let runs = cycle['nr'] - let message, associatedData - for (i = 1; i <= runs; i++) { - const key = crypto.getRandomValues(new Uint8Array(16)) - message = crypto.getRandomValues(new Uint8Array(cycle['messageSize'])) - associatedData = cycle['assocSize'] ? crypto.getRandomValues(new Uint8Array(cycle['assocSize'])) : null + for (let i in cycles) { + const cycle = cycles[i] + let totalTime = 0 + let runs = cycle['nr'] + let message, associatedData + for (i = 1; i <= runs; i++) { + const key = JsAscon.getRandomUintArray(16) + message = JsAscon.getRandomUintArray(cycle['messageSize']) + associatedData = cycle['assocSize'] ? JsAscon.getRandomUintArray(cycle['assocSize']) : null + const start = new Date().getTime() + const encrypted = JsAscon.encryptToHex(key, message, associatedData) + const decrypted = JsAscon.decryptFromHex(key, encrypted, associatedData) + totalTime += new Date().getTime() - start + JsAscon.assertSame(JSON.stringify(message), JSON.stringify(decrypted), 'Encryption/Decryption to hex failed') + } - const start = performance.now() - const encrypted = JsAscon.encryptToHex(key, message, associatedData) - const decrypted = JsAscon.decryptFromHex(key, encrypted, associatedData) - totalTime += performance.now() - start - JsAscon.assertSame(JSON.stringify(message), JSON.stringify(decrypted), 'Encryption/Decryption to hex failed') + console.log('### ' + runs + ' cycles with ' + (message ? message.length : 0) + ' byte message data and ' + (associatedData ? associatedData.length : 0) + ' byte associated data ###') + console.log('Total Time: ' + (totalTime / 1000).toFixed(3) + ' seconds') } - - console.log('### ' + runs + ' cycles with ' + (message ? message.length : 0) + ' byte message data and ' + (associatedData ? associatedData.length : 0) + ' byte associated data ###') - console.log('Total Time: ' + (totalTime / 1000).toFixed(3) + ' seconds') -} + console.log('performance.js successfully done') +})() \ No newline at end of file diff --git a/tests/test-encrypt-decrypt.js b/tests/test-encrypt-decrypt.js index 8dbbd06..05a712e 100644 --- a/tests/test-encrypt-decrypt.js +++ b/tests/test-encrypt-decrypt.js @@ -1,117 +1,123 @@ -const JsAscon = require('../dist/ascon') +if (typeof window === 'undefined') { + global.JsAscon = require('../dist/ascon') +} +(function () { -JsAscon.debugEnabled = false -JsAscon.debugPermutationEnabled = false + JsAscon.debugEnabled = false + JsAscon.debugPermutationEnabled = false -let expected, actual + let expected, actual -const key20 = [ - 0x90, - 0x80, - 0x70, - 0x60, - 0x50, - 0x40, - 0x30, - 0x20, - 0x10, - 0xAA, - 0x90, - 0x90, - 0x90, - 0x90, - 0xCC, - 0xEF, - 0xAA, - 0x90, - 0x90, - 0x90, -] -const key16 = [0x90, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0x90, 0xCC, 0xEF] -const nonce = [0x50, 0x10, 0x30, 0x70, 0x90, 0x60, 0x40, 0x30, 0xEF, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0xCC] -const plaintextSimple = 'ascon' -const plaintextMore = 'ascon-asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -const accociatedMore = 'BLAB-asconASDFNASKIQAL-_;:;#+asconKIQAL-_;:;#+asconASDFNASKIQAL+' -const variants = [ - { - 'variant': 'Ascon-128', - 'key': key16, - 'plaintext': plaintextSimple, - 'associatedData': 'ASCON', - 'expectedCiphertextHex': '0x265e8b5755', - 'expectedTagHex': '0x0d95cfeeb4cfe5a4f9ff29380137f12d', - }, - { - 'variant': 'Ascon-128a', - 'key': key16, - 'plaintext': plaintextSimple, - 'associatedData': 'ASCON', - 'expectedCiphertextHex': '0x24443ec02c', - 'expectedTagHex': '0xd411bf0897d558a95d09cccccc06d273', - }, - { - 'variant': 'Ascon-80pq', - 'key': key20, - 'plaintext': plaintextSimple, - 'associatedData': 'ASCON', - 'expectedCiphertextHex': '0x112e6c44be', - 'expectedTagHex': '0x83bea05f00a5f8b9f08efd404144b87b', - }, - { - 'variant': 'Ascon-128', - 'key': key16, - 'plaintext': plaintextMore, - 'associatedData': accociatedMore, - 'expectedCiphertextHex': '0x2287b412d5c2658c38fb1616e2a3c6ff85952bbaefe021757e535ccfd4a0806cf9c5d61a368739fe661ac16d4c943a84c16196b343fdc8aaf76cc2e5ad067843dc28bae8fcf7972bfa36aaf6e734ba4ac89b3c559bdb5ba49bfb8df56d6beafd0104d9d4d495', - 'expectedTagHex': '0x7c1e88242bea67a90f369fb0889b74c9', - }, - { - 'variant': 'Ascon-128a', - 'key': key16, - 'plaintext': plaintextMore, - 'associatedData': accociatedMore, - 'expectedCiphertextHex': '0x72e5fde15539b1dbf9f7aea29e58598267971ae9b0446db26a0fdd7f5821cb492ca4c5ec9c40d5fd6536cc4a1d4b4cb616423d4c6d33c8a06364e7137726447d1bdee5d2071cacea601c1ab199b57748e35766248cbb26f0287abb70280b8510de508e22cc6f', - 'expectedTagHex': '0x45eafc378d5f1d2d3b4af25ba3ef70ac', - }, - { - 'variant': 'Ascon-80pq', - 'key': key20, - 'plaintext': plaintextMore, - 'associatedData': accociatedMore, - 'expectedCiphertextHex': '0xe483db31c108a269c2cacd33534544c0ba524a6f46016473260b9d4aa81dd0a61f994f46bb3966f2aea436990f024fbaf1477c0cbd6664b53ecdd4acf91f683054762c952dbfa42235763a8cb97ee94a25d8f0d53e200ba6a291715e3713c02ee63196aa5917', - 'expectedTagHex': '0x6d06c25335b03f3eef29537c3a133afd', - }, -] -for (let i in variants) { - const row = variants[i] - const variant = row['variant'] - const plaintext = row['plaintext'] - const plaintextHex = JsAscon.byteArrayToHex(JsAscon.anyToByteArray(plaintext)) - const associatedData = row['associatedData'] + const key20 = [ + 0x90, + 0x80, + 0x70, + 0x60, + 0x50, + 0x40, + 0x30, + 0x20, + 0x10, + 0xAA, + 0x90, + 0x90, + 0x90, + 0x90, + 0xCC, + 0xEF, + 0xAA, + 0x90, + 0x90, + 0x90, + ] + const key16 = [0x90, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0x90, 0xCC, 0xEF] + const nonce = [0x50, 0x10, 0x30, 0x70, 0x90, 0x60, 0x40, 0x30, 0xEF, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0xCC] + const plaintextSimple = 'ascon' + const plaintextMore = 'ascon-asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + const accociatedMore = 'BLAB-asconASDFNASKIQAL-_;:;#+asconKIQAL-_;:;#+asconASDFNASKIQAL+' + const variants = [ + { + 'variant': 'Ascon-128', + 'key': key16, + 'plaintext': plaintextSimple, + 'associatedData': 'ASCON', + 'expectedCiphertextHex': '0x265e8b5755', + 'expectedTagHex': '0x0d95cfeeb4cfe5a4f9ff29380137f12d', + }, + { + 'variant': 'Ascon-128a', + 'key': key16, + 'plaintext': plaintextSimple, + 'associatedData': 'ASCON', + 'expectedCiphertextHex': '0x24443ec02c', + 'expectedTagHex': '0xd411bf0897d558a95d09cccccc06d273', + }, + { + 'variant': 'Ascon-80pq', + 'key': key20, + 'plaintext': plaintextSimple, + 'associatedData': 'ASCON', + 'expectedCiphertextHex': '0x112e6c44be', + 'expectedTagHex': '0x83bea05f00a5f8b9f08efd404144b87b', + }, + { + 'variant': 'Ascon-128', + 'key': key16, + 'plaintext': plaintextMore, + 'associatedData': accociatedMore, + 'expectedCiphertextHex': '0x2287b412d5c2658c38fb1616e2a3c6ff85952bbaefe021757e535ccfd4a0806cf9c5d61a368739fe661ac16d4c943a84c16196b343fdc8aaf76cc2e5ad067843dc28bae8fcf7972bfa36aaf6e734ba4ac89b3c559bdb5ba49bfb8df56d6beafd0104d9d4d495', + 'expectedTagHex': '0x7c1e88242bea67a90f369fb0889b74c9', + }, + { + 'variant': 'Ascon-128a', + 'key': key16, + 'plaintext': plaintextMore, + 'associatedData': accociatedMore, + 'expectedCiphertextHex': '0x72e5fde15539b1dbf9f7aea29e58598267971ae9b0446db26a0fdd7f5821cb492ca4c5ec9c40d5fd6536cc4a1d4b4cb616423d4c6d33c8a06364e7137726447d1bdee5d2071cacea601c1ab199b57748e35766248cbb26f0287abb70280b8510de508e22cc6f', + 'expectedTagHex': '0x45eafc378d5f1d2d3b4af25ba3ef70ac', + }, + { + 'variant': 'Ascon-80pq', + 'key': key20, + 'plaintext': plaintextMore, + 'associatedData': accociatedMore, + 'expectedCiphertextHex': '0xe483db31c108a269c2cacd33534544c0ba524a6f46016473260b9d4aa81dd0a61f994f46bb3966f2aea436990f024fbaf1477c0cbd6664b53ecdd4acf91f683054762c952dbfa42235763a8cb97ee94a25d8f0d53e200ba6a291715e3713c02ee63196aa5917', + 'expectedTagHex': '0x6d06c25335b03f3eef29537c3a133afd', + }, + ] + for (let i in variants) { + const row = variants[i] + const variant = row['variant'] + const plaintext = row['plaintext'] + const plaintextHex = JsAscon.byteArrayToHex(JsAscon.anyToByteArray(plaintext)) + const associatedData = row['associatedData'] - const ciphertextAndTag = JsAscon.encrypt(row['key'], nonce, associatedData, plaintext, variant) - const ciphertext = ciphertextAndTag.slice(0, -16) - const tag = ciphertextAndTag.slice(-16) - // check ciphertext - expected = row['expectedCiphertextHex'] - actual = JsAscon.byteArrayToHex(ciphertext) - JsAscon.assertSame(expected, actual, - 'Encrypted ciphertext of word "' + plaintext + '" in variant "' + variant + '"') - // check encrypted tag - expected = row['expectedTagHex'] - actual = JsAscon.byteArrayToHex(tag) - JsAscon.assertSame(expected, actual, 'Encrypted tag of word "' + plaintext + '" in variant "' + variant + '"') - // check decryption - const plaintextReceived = JsAscon.decrypt(row['key'], nonce, associatedData, ciphertextAndTag, variant) - actual = JsAscon.byteArrayToHex(plaintextReceived ?? []) - JsAscon.assertSame(plaintextHex, JsAscon.byteArrayToHex(plaintextReceived ?? []), - 'Decryption from ciphertext failed in variant "' + variant + '"') -} + const ciphertextAndTag = JsAscon.encrypt(row['key'], nonce, associatedData, plaintext, variant) + const ciphertext = ciphertextAndTag.slice(0, -16) + const tag = ciphertextAndTag.slice(-16) + // check ciphertext + expected = row['expectedCiphertextHex'] + actual = JsAscon.byteArrayToHex(ciphertext) + JsAscon.assertSame(expected, actual, + 'Encrypted ciphertext of word "' + plaintext + '" in variant "' + variant + '"') + // check encrypted tag + expected = row['expectedTagHex'] + actual = JsAscon.byteArrayToHex(tag) + JsAscon.assertSame(expected, actual, 'Encrypted tag of word "' + plaintext + '" in variant "' + variant + '"') + // check decryption + const plaintextReceived = JsAscon.decrypt(row['key'], nonce, associatedData, ciphertextAndTag, variant) + actual = JsAscon.byteArrayToHex(plaintextReceived ?? []) + JsAscon.assertSame(plaintextHex, JsAscon.byteArrayToHex(plaintextReceived ?? []), + 'Decryption from ciphertext failed in variant "' + variant + '"') + } + + // test convenient methods + let key = 'mypassword' + let message = ['this can be any data type 😎 文', 123] + let associatedData = 'Some data 😋 文 This data is not contained in the encrypt output. You must pass the same data to encrypt and decrypt in order to be able to decrypt the message.' + let encrypted = JsAscon.encryptToHex(key, message, associatedData) + let decrypted = JsAscon.decryptFromHex(key, encrypted, associatedData) + JsAscon.assertSame(JSON.stringify(message), JSON.stringify(decrypted), 'Encryption/Decryption to hex failed') -// test convenient methods -let key = 'mypassword' -let message = ['this can be any data type 😎 文', 123] -let associatedData = 'Some data 😋 文 This data is not contained in the encrypt output. You must pass the same data to encrypt and decrypt in order to be able to decrypt the message.' -let encrypted = JsAscon.encryptToHex(key, message, associatedData) -let decrypted = JsAscon.decryptFromHex(key, encrypted, associatedData) -JsAscon.assertSame(JSON.stringify(message), JSON.stringify(decrypted), 'Encryption/Decryption to hex failed') \ No newline at end of file + console.log('test-encrypt-decrypt.js successfully done') +})() \ No newline at end of file diff --git a/tests/test-es6-module.js b/tests/test-es6-module.js new file mode 100644 index 0000000..509dce5 --- /dev/null +++ b/tests/test-es6-module.js @@ -0,0 +1,16 @@ +(function () { + + import JsAscon from '../dist/ascon.es6' + + JsAscon.debugEnabled = false + JsAscon.debugPermutationEnabled = false + + let key = 'mypassword' + let message = ['this can be any data type 😎 文', 123] + let associatedData = 'Some data 😋 文 This data is not contained in the encrypt output. You must pass the same data to encrypt and decrypt in order to be able to decrypt the message.' + let encrypted = JsAscon.encryptToHex(key, message, associatedData) + let decrypted = JsAscon.decryptFromHex(key, encrypted, associatedData) + JsAscon.assertSame(JSON.stringify(message), JSON.stringify(decrypted), 'Encryption/Decryption to hex failed') + + console.log('test-es6-module.js successfully done') +})() \ No newline at end of file diff --git a/tests/test-hash.js b/tests/test-hash.js index e6b7d1c..27df06a 100644 --- a/tests/test-hash.js +++ b/tests/test-hash.js @@ -1,28 +1,34 @@ -const JsAscon = require('../dist/ascon') +if (typeof window === 'undefined') { + global.JsAscon = require('../dist/ascon') +} -JsAscon.debugEnabled = false -JsAscon.debugPermutationEnabled = false +(function () { + JsAscon.debugEnabled = false + JsAscon.debugPermutationEnabled = false -let word, expected, actual + let word, expected, actual -word = 'ascon' -expected = '0x02c895cb92d79f195ed9e3e2af89ae307059104aaa819b9a987a76cf7cf51e6e' -actual = JsAscon.byteArrayToHex(JsAscon.hash(word)) -JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hash"') + word = 'ascon' + expected = '0x02c895cb92d79f195ed9e3e2af89ae307059104aaa819b9a987a76cf7cf51e6e' + actual = JsAscon.byteArrayToHex(JsAscon.hash(word)) + JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hash"') -word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -expected = '0x9223f5c59a29c05a60121936c90968ecb3103c3c69a876f4d5de87cd4d3fec84' -actual = JsAscon.byteArrayToHex(JsAscon.hash(word)) -JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hash"') + word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + expected = '0x9223f5c59a29c05a60121936c90968ecb3103c3c69a876f4d5de87cd4d3fec84' + actual = JsAscon.byteArrayToHex(JsAscon.hash(word)) + JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hash"') -expected = '0x54a4c99e9a43141b4ade74044c74e6fa9bc7ddcb2334c0fc2308c8c834c7feec' -actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Xof')) -JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Xof"') + expected = '0x54a4c99e9a43141b4ade74044c74e6fa9bc7ddcb2334c0fc2308c8c834c7feec' + actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Xof')) + JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Xof"') -expected = '0x1349fdcb638579236bfc8f56ac260b6359706276d7bed25b9dd751645a523b2f' -actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Xofa')) -JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Xofa"') + expected = '0x1349fdcb638579236bfc8f56ac260b6359706276d7bed25b9dd751645a523b2f' + actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Xofa')) + JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Xofa"') -expected = '0xb03447d661a92286a403507c0bb647c6c10dad98a4366b60a0631cd5cb7ed930' -actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Hasha')) -JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hasha"') \ No newline at end of file + expected = '0xb03447d661a92286a403507c0bb647c6c10dad98a4366b60a0631cd5cb7ed930' + actual = JsAscon.byteArrayToHex(JsAscon.hash(word, 'Ascon-Hasha')) + JsAscon.assertSame(expected, actual, 'Hash of word "' + word + '" in variant "Ascon-Hasha"') + + console.log('test-hash.js successfully done') +})() \ No newline at end of file diff --git a/tests/test-mac.js b/tests/test-mac.js index 320184d..a10836e 100644 --- a/tests/test-mac.js +++ b/tests/test-mac.js @@ -1,37 +1,43 @@ -const JsAscon = require('../dist/ascon') - -JsAscon.debugEnabled = false -JsAscon.debugPermutationEnabled = false - -let key = [0x90, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0x90, 0xCC, 0xEF] -let word, expected, actual - -word = 'ascon' -expected = '0x5432a3ff217b31c6a7105a175438b1f9' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word)) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Mac"') - -word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -expected = '0x558cb5e4ae72cc04da650971dc7b2f43' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word)) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Mac"') - -word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -expected = '0x68dfa25dbca5a16559f963b34351b95b' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Maca')) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Maca"') - -word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -expected = '0xc674ed5d593aeed416664d592c917050' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Prf')) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Prf"') - -word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' -expected = '0x4d7087c67b452a80b373df49f2c134c5' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Prfa')) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Prfa"') - -word = 'ascon' -expected = '0xbc38d3219d01a84e0afecd930c40ac9d' -actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-PrfShort')) -JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-PrfShort"') \ No newline at end of file +if (typeof window === 'undefined') { + global.JsAscon = require('../dist/ascon') +} + +(function () { + JsAscon.debugEnabled = false + JsAscon.debugPermutationEnabled = false + + let key = [0x90, 0x80, 0x70, 0x60, 0x50, 0x40, 0x30, 0x20, 0x10, 0xAA, 0x90, 0x90, 0x90, 0x90, 0xCC, 0xEF] + let word, expected, actual + + word = 'ascon' + expected = '0x5432a3ff217b31c6a7105a175438b1f9' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word)) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Mac"') + + word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + expected = '0x558cb5e4ae72cc04da650971dc7b2f43' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word)) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Mac"') + + word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + expected = '0x68dfa25dbca5a16559f963b34351b95b' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Maca')) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Maca"') + + word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + expected = '0xc674ed5d593aeed416664d592c917050' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Prf')) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Prf"') + + word = 'asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+asconASDFNASKIQAL-_;:;#+' + expected = '0x4d7087c67b452a80b373df49f2c134c5' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-Prfa')) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-Prfa"') + + word = 'ascon' + expected = '0xbc38d3219d01a84e0afecd930c40ac9d' + actual = JsAscon.byteArrayToHex(JsAscon.mac(key, word, 'Ascon-PrfShort')) + JsAscon.assertSame(expected, actual, 'Mac of word "' + word + '" in variant "Ascon-PrfShort"') + + console.log('test-mac.js successfully done') +})() \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 53d4a88..34a1e93 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,9 +3,9 @@ "outDir": "./dist/", "sourceMap": false, "noImplicitAny": false, - "target": "ES2020", + "esModuleInterop": true, "moduleResolution" : "node", - "module": "CommonJS", + "target": "ES2020", "lib": [ "ES2020", "DOM"