Skip to content

Conversation

@gilles-peskine-arm
Copy link
Contributor

@gilles-peskine-arm gilles-peskine-arm commented Jan 3, 2026

This is a complete implementation of Ascon as defined in SP800-232 (except restricted to octet strings).

Contents:

  • Ascon-Hash256
  • Generic XOF interface
  • Ascon-XOF128
  • Ascon-CXOF128
  • Ascon-AEAD128
  • Ascon-AEAD128 with nonce masking
  • Two implementations: a somewhat faster one (at least on my PC) with larger code size (default), and a smaller but slower one (enable MBEDTLS_ASCON_SMALLER).

Integration notes:

  • Built-in implementation (no public interface).
  • Test data mostly generated with https://github.com/GiacomoPope/dilithium-py
  • XOF built-in driver.
  • XOF driver wrapper.
  • XOF test driver — should work but that's not a priority for me.
  • XOF API.
  • psasim builds will be broken until we resolve some technical debt there (we can't easily add new API functions).

Note to potential users: This is a prototype, with everything packaged in a single branch for convenience. I intend to rebase it on top of TF-PSA-Crypto 1.1 once that is released, and then let it go dormant until we officially implement Ascon (check our roadmap).

Note to maintainers: this is not intended for review. When we implement Ascon, we should split this branch (probably into three parts: hash, XOF, AEAD). This branch has generic support for XOF as well, which I presume we'll add for SHAKE well before we work on Ascon.

Legal notice: I worked on this almost entirely in my spare time, the exception being part of the generic XOF interface (which is also relevant for SHAKE, which we will add in 2026Q1). I did, however, use Arm resources. For the avoidance of doubt, I hereby allow Arm to dispose of this work as a work for hire. Note that for recipients of TF-PSA-Crypto, this code is nothing special: it is provided under the usual license of TF-PSA-Crypto.

PR checklist

  • changelog provided | not required because:
  • framework PR TODO (to handle the mechanisms, the new algorithm category for XOF, and the XOF functions in the test driver code)
  • mbedtls development PR not required because: crypto only
  • mbedtls 3.6 PR not required because: new feature
  • tests provided

The test data comes from the Python reference implementation.
The `permute_round` test cases were copied manually from Python runs.
The `permute` test cases were generated by the script below.

```
#!/usr/bin/env python

import struct
import typing

# https://ascon.isec.tugraz.at/implementations.html#cite.0@swPython
# or pip install ascon
import ascon

def p_state_to_hex(state: typing.List[int]) -> bytes:
    assert len(state) == 5
    return struct.pack('<QQQQQ', *state).hex()

def permute(label: str, rounds: int, input_state: typing.List[int]) -> None:
    assert 1 <= rounds <= 12
    output_state = input_state.copy()
    ascon._ascon.ascon_permutation(output_state, rounds)
    print(f'''\
Permute {rounds} rounds {label}
permute:{rounds}:"{p_state_to_hex(input_state)}":"{p_state_to_hex(output_state)}"
''')

def main() -> None:
    permute('#0', 8, [0, 0, 0, 0, 0])
    permute('Mbed-TLS#1', 8, [0x0123456789abcdef, 0x13579bdf02468ace, 0x1234567812345678, 0xfedcba9801234567, 0x0102040801020408])
    permute('Mbed-TLS#2', 8, [0x73276863616e6142, 0x7465626168706c61, 0x676e696c70697274, 0x6465726f7272696d, 0x64656c6b63616873])
    permute('#0', 12, [0, 0, 0, 0, 0])
    permute('Mbed-TLS#1', 12, [0x0123456789abcdef, 0x13579bdf02468ace, 0x1234567812345678, 0xfedcba9801234567, 0x0102040801020408])
    permute('Mbed-TLS#2', 12, [0x6772756d70696c79, 0x626973657875616c, 0x7370696e65742773, 0x6d75726b69657374, 0x6469637461746564])

if __name__ == '__main__':
    main()
```

Signed-off-by: Gilles Peskine <[email protected]>
Add `PSA_ALG_ASCON_HASH256` to the PSA API: algorithm encoding, sizes,
`MBEDTLS_PSA_ACCEL_` and `MBEDTLS_PSA_BUILTIN_` macros.

Signed-off-by: Gilles Peskine <[email protected]>
The `hash256_single` test cases were generated by the script below.

```
import hashlib

import ascon # https://ascon.isec.tugraz.at/implementations.html#cite.0@swPython
             # (NOT the pip package which implements the original proposal
             # without the tweaks made to the final specification!)

def hash256_single(n: int) -> None:
    msg = hashlib.sha512(f'{n}-byte message'.encode('ascii')).digest()
    assert len(msg) >= n
    msg = msg[:n]
    res = ascon.ascon_hash(msg)
    print(f'''
Hash256 simple: {n} bytes
hash256_single:"{msg.hex()}":"{res.hex()}"\
''')

def main() -> None:
    for n in range(24):
        hash256_single(n)
    for n in [31, 32, 63, 64]:
        hash256_single(n)

if __name__ == '__main__':
    main()
```

Signed-off-by: Gilles Peskine <[email protected]>
At the time of writing, Ascon-Hash256 lacks a standard that defines a block
size for HMAC, so we reject HMAC-Ascon-Hash256. Test this, and don't try to
exercise it in automatically generated tests. This affects not only
`PSA_ALG_HMAC(PSA_ALG_ASCON_HASH256)`, but also
`PSA_ALG_DETERMINISTIC_ECDSA(PSA_ALG_ASCON_HASH256)`,
`PSA_ALG_HKDF(PSA_ALG_ASCON_HASH256)`,
`PSA_ALG_HKDF_EXTRACT(PSA_ALG_ASCON_HASH256)`,
`PSA_ALG_HKDF_EXPAND(PSA_ALG_ASCON_HASH256)`, and
`PSA_ALG_PBKDF2_HMAC(PSA_ALG_ASCON_HASH256)` which use HMAC internally.

For key derivation mechanisms based on HMAC, reject the invalid HMAC during
setup, rather than wait until the first attempt at an HMAC operation, to be
more friendly and to reduce the risk of bad consequences for failing midway.

At the time of writing, Ascon-Hash256 lacks a standard that defines an OID,
so we reject RSA PKCS#1v1.5 signature using Ascon-Hash256. Test this, and
don't try to exercise it in automatically generated tests.

Signed-off-by: Gilles Peskine <[email protected]>
Signed-off-by: Gilles Peskine <[email protected]>
Signed-off-by: Gilles Peskine <[email protected]>
If the new config option `MBEDTLS_ASCON_SMALLER` is enabled, use a slower,
byte-by-byte implementation of Ascon-Hash256. This reduces the code size.

Signed-off-by: Gilles Peskine <[email protected]>
Just a low-level implementation for now.

Add test vector from NIST ACVP.

Signed-off-by: Gilles Peskine <[email protected]>
Add test vector from NIST ACVP and more test vectors generated with
`ascon.py` from
https://ascon.isec.tugraz.at/implementations.html#cite.0@swPython

Signed-off-by: Gilles Peskine <[email protected]>
There are no declarations of XOF algorithms yet, and no API or driver
wrappers. Just the XOF functions in the built-in driver, modeled after the
hash functions.

Signed-off-by: Gilles Peskine <[email protected]>
Signed-off-by: Gilles Peskine <[email protected]>
Signed-off-by: Gilles Peskine <[email protected]>
The `aead128_simple` test cases were generated by the script below.

```

import ascon # https://ascon.isec.tugraz.at/implementations.html#cite.0@swPython
             # (NOT the pip package which implements the original proposal
             # without the tweaks made to the final specification!)

KEY = b'A buttered scone'
IV = b'w/ clotted cream'
AD = b'This step has two parts, including absorbing the associated data (when it is non-empty) and applying the domain separation bit to the state.'
PLAINTEXT = b'swordfish xyzzy 1234 iloveyou correct horse battery staple the Spanish inquisition is hiding behind the coal shed'

def aead128_simple(a: int, b: int) -> None:
    assert len(AD) >= a
    ad = AD[:a]
    assert len(PLAINTEXT) >= b
    pt = PLAINTEXT[:b]
    ct_tag = ascon.ascon_encrypt(KEY, IV, ad, pt)
    ct = ct_tag[:-16]
    tag = ct_tag[-16:]
    print(f'''\
AEAD128 py AD={a} plaintext={b}
aead128_simple:"{KEY.hex()}":"{IV.hex()}":"{ad.hex()}":"{pt.hex()}":"{ct.hex()}":"{tag.hex()}"
''')

def main() -> None:
    for a in range(33):
        aead128_simple(a, 0)
    aead128_simple(len(AD), 0)
    for p in range(1, 33):
        aead128_simple(0, p)
        aead128_simple(1, p)
        aead128_simple(16, p)
    aead128_simple(0, len(PLAINTEXT))
    aead128_simple(len(AD), len(PLAINTEXT))

if __name__ == '__main__':
    main()
```

Signed-off-by: Gilles Peskine <[email protected]>
Start refactoring. No behavior change, just moving some functions around.

Signed-off-by: Gilles Peskine <[email protected]>
AEAD tag length validation was only performed in `psa_aead_setup()` and in
built-in drivers, but not in the core for one-shot AEAD. Validate it
systematically in the core.

Signed-off-by: Gilles Peskine <[email protected]>
AEAD key type validation was only performed in built-in drivers. Do it
systematically in the core.

Signed-off-by: Gilles Peskine <[email protected]>
Only `gcm.h` and `ccm.h` need legacy cipher IDs.

No behavior change. This opens the door to a code size optimization in
builds with AEAD that isn't GCM or CCM (i.e. ChaCha20-Poly1305, or the
upcoming Ascon-AEAD128), namely removing at least
`mbedtls_cipher_values_from_psa()` from the build, which is beyond the scope
of this commit.

Signed-off-by: Gilles Peskine <[email protected]>
If the config option `MBEDTLS_ASCON_SMALLER` is enabled, use a slower,
byte-by-byte implementation of Ascon-AEAD128. This reduces the code size.

Signed-off-by: Gilles Peskine <[email protected]>
@gilles-peskine-arm gilles-peskine-arm added size-m Estimated task size: medium (~1w) needs-work needs-ci Needs to pass CI tests priority-scheduled This PR is big - it will require time to be scheduled for review labels Jan 3, 2026
Signed-off-by: Gilles Peskine <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-ci Needs to pass CI tests needs-work priority-scheduled This PR is big - it will require time to be scheduled for review size-m Estimated task size: medium (~1w)

Projects

Status: In Development

Development

Successfully merging this pull request may close these issues.

1 participant