Skip to content

About

Source code related to the Eurocrypt 2017 paper "How Fast Can Higher-Order Masking Be in Software?"

Resources

Stars

3 stars

Watchers

4 watching

Forks

Repository files navigation

Software Higher-order Masking for Block Ciphers

This repository provides some material related to the article

How Fast Can Higher-Order Masking Be in Software?

published at Eurocrypt 2017. The repository includes the source codes of the different masking schemes optimised in ARMv7 assembly as depicted in the paper.

Authors

  • Dahmun Goudarzi
  • Matthieu Rivain

Contributors:

  • Ryad Benadjila

Copyright and License

Copyright © 2018-2026, CryptoExperts License GNU General Public License v3 (GPLv3)

Updated in 2026 for ARMv7M compatibility, various fixes, masked AES key schedules, masked AES-192/AES-256 and Rijndael-256-256, and a masked C API.

Disclaimer

  • This code does not aim to be strong against side-channel attacks but to benchmark higher-order masking. Strength against side-channel attacks might require a mix of masking and other countermeasures. In particular, no measures were taken to avoid transition leakage (which is hardware dependent). It is likely that a n-share implementation suffers some (n/2)-order flaw because of transition hardware effects.

History

  • The code has been completely refactored for portability and open sourcing since the publication at Eurocrypt 2017. The target now differs from the one on which the timing were benchmarked in the original paper, which may introduce some differences. In essence the code and its optimisations are unchanged and should still lead to comparable performances.

  • In 2026, the code has been adapted to be compatible with ARMv7M (Thumb-2), compilation targets have been added, as well as emulation with qemu for tests. An exhaustive testing script has also been added for regression tests.

  • In 2026, the AES part has been extended (regular and bitslice versions): masked AES-128 key schedule (the original code only took precomputed masked round keys), masked AES-192 and AES-256 (encryption and key schedule), masked Rijndael-256-256 (256-bit block, 256-bit key) with its masked key schedule, and a C API working either fully in the masked world (masked keys, inputs and outputs) or with clear inputs/outputs, including public keys and multi-key (_x2/_x4/_x8) variants. Two randomness issues have also been fixed: the bitslice AES plaintext masking used a non-random value for half of its masks, and the C_RAND get_random lost the random value when its destination register was caller-saved (which was the case in the ISW, CPRR and refresh gadgets).

Content

ARMv7 assembly source files:

All the ARMv7 assembly source files can be found in the files called src.

  • 1_fieldmult.S: different field multiplications.
  • 2_secmult_cprr.S: CPRR evaluation.
  • 2_secmult_isw.S: ISW multiplication.
  • 2_secmult_iswand.S: ISW-AND multiplication.
  • 2_secmult_refresh.S: ISW-based refresh.
  • 3_secsbox_bsaes.S: bitslice AES s-box.
  • 3_secsbox_bspresent.S: bitslice PRESENT s-box.
  • 3_secsbox_fog.S: FoG PRESENT s-box.
  • 3_secsbox_khl.S: KHL AES s-box.
  • 3_secsbox_rp.S: RP AES s-box.
  • 4_seccipher_bsaes.S: bitslice AES encryption (legacy, and masked cores for AES-128/192/256), representation changes.
  • 4_seccipher_bspresent.S: bitslice PRESENT encryption.
  • 4_seccipher_regaes.S: regular AES encryption (legacy, and masked cores for AES-128/192/256), representation changes.
  • 4_seccipher_regpresent.S: regular PRESENT encryption.
  • 4_seccipher_regaes_keysched.S: masked AES-128 and AES-192/AES-256 key schedules, regular version.
  • 4_seccipher_bsaes_keysched.S: masked AES-128 and AES-192/AES-256 key schedules, bitslice version.
  • 4_seccipher_regrijndael256.S: masked Rijndael-256-256 encryption, regular version.
  • 4_seccipher_regrijndael256_keysched.S: masked Rijndael-256-256 key schedule, regular version.
  • 4_seccipher_bsrijndael256.S: masked Rijndael-256-256 encryption, bitslice version.
  • 4_seccipher_bsrijndael256_keysched.S: masked Rijndael-256-256 key schedule, bitslice version.
  • 4_seccipher_sharing.S: masked encoding, decoding and (ISW) refresh of 16-byte, 24-byte and 32-byte blocks.
  • random.S: random generation.

Masked AES and Rijndael-256-256 C API:

  • aes_masked.h / aes_masked.c: C API on top of the assembly masked AES-128/192/256, and the sharing utilities (see below).
  • rijndael256_masked.h / rijndael256_masked.c: C API on top of the assembly masked Rijndael-256-256 (see below).

Library mode (use from another project):

  • lib/masked.mk: Makefile fragment giving the library sources and flags.
  • lib/param.h: parameters of the library, set at compilation time (replaces the generated param.h).
  • lib/masked_asm.S: single assembly unit of the library (regular or bitslice).
  • Makefile.lib / test/test_masked_lib.c: tests of the library mode.

Header files:

  • param.h: Header files containing different sets of parameters.
  • mode.h: Header files containing different modes for specific functions.

Main:

  • main.c: Main file containing function to set up shares/unmasked shares according to mode chosen and testing the correctness.

Parameters

NOTE: Beware that all the possible set of parameters might not be compatible with each other, sometimes because of inherent incompatibility, and sometimes because the selection is not implemented (yet). When this is the case, usually a #error will be triggered at compilation time.

param.h


  • FIELDSIZE: sets the bit-size of the elements. Range from 4 to 10. On specific s-boxes such as AES and PRESENT needs to be set to 8 and 4 resp. in order to work.
  • NBSHARES: sets the number of shares used to protect the implementation. Ranges from 2 to 10.
  • MULT_MODE: sets the field multiplication algorithm. The possible values are listed in mode.h.
  • CODE_MODE: sets the parallelisation level for the implementation. The possibles values are listed in mode.h. The code was developed for a 32-bit architecture. Hence consider that FIELDSIZE * CODE_MODE should not exceed 32.
  • CIPH_MODE: sets some implementation tricks for regular AES encryption with KHL and RP schemes. The possible values are listed in mode.h. If not set to ANY, it should be used with specific sets of parameters (see below).
  • RAND_MODE: sets the random number generator. Can either be C rand function for functionality testing (C_RAND), a table look-up to emulate a fast TRNG for performance testing (TRNG), or an external random function provided at link time (EXT_RAND).
  • TEST_MODE: sets the function that will be tested. The possible values are listed in mode.h.
  • AES_IMPL: sets the backend of the masked AES-128/192/256 and Rijndael-256-256 APIs (AES_IMPL_REG, AES_IMPL_BS or AES_IMPL_NONE). Automatically set by configure.py for the AES and Rijndael-256-256 cipher tests, and by lib/param.h (from MASKED_REGULAR) in library mode.
  • NB_TESTS: sets the number of test that are going to be performed.

mode.h


Field Multiplication

  • BINMULT1: binary multiplication (l2r).
  • BINMULT2: binary multiplication (l2r) with look-up table used for the modular reduction.
  • EXPLOG1: exp-log multiplication.
  • EXPLOG2: exp-log multiplication with look-up table used for the modular reduction.
  • KARA: Karatsuba multiplication.
  • HALFTAB: half table multiplication.
  • FULLTAB: full table multiplication.
  • FULLTABSHIFT: tweaked full table multiplication for 8 elements in parallel (for KHL cipher mode only).
  • EXPLOG2SHIFT: tweaked exp-log multiplication for 4 elements in parallel (for RP cipher mode only).
  • HALFTABSHIFT: tweaked half table multiplication for 4 elements in parallel (for in RP cipher mode only).

Parallelisation level

  • NORMAL: no parallelisation.
  • PARA2: parallelisation level 2: 2 elements/register.
  • PARA4: parallelisation level 4: 4 elements/register (usually used for FIELDSIZE=8).
  • PARA8: parallelisation level 8: 8 elements/register (only use for FIELDSIZE=4).
  • BITSLICE: parallelisation level full: 32 elements/register.

Cipher choice

  • ANY: default choice for testing.
  • KHL: used when testing KHL encryption.
  • RP: used when testing RP encryption.

Refresh choice

  • RF1: regular refresh.
  • RF4: optimised refresh (partial unrolling of loops).

Random choice

  • TRNG: "randomness" from table look-up to emulate a fast TRNG. For performance testing only!
  • C_RAND: randomness from C rand function (two calls combined for 32 random bits). For functionality testing.
  • EXT_RAND: randomness from an external function provided at link time, uint32_t masked_random32(void) (32 random bits per call), whose name can be overridden with -DMASKED_RANDOM_FUNC=name. It is called from the assembly as a usual C function (AAPCS, stack aligned on 8 bytes, caller-saved registers preserved around the call) and must not call the masked library. This is the way to plug a real TRNG or CSPRNG. The tests provide a xorshift128 generator (testing only) and check that it is actually used.

Test choice

  • TEST_FIELDMULT: to test field multiplications.
  • TEST_SECMULT_CPRR: to test CPRR evaluations.
  • TEST_SECMULT_ISW: to test ISW multiplications.
  • TEST_SECSBOX_RPAES: to test RP AES s-box.
  • TEST_SECSBOX_KHLAES: to test KHL AES s-box.
  • TEST_SECSBOX_FOGPRESENT: to test FoG PRESENT s-box.
  • TEST_SECSBOX_BSAES: to test bitsliced AES s-box.
  • TEST_SECSBOX_BSPRESENT: to test bitsliced PRESENT s-box.
  • TEST_SECCIPHER_REGAES: to test regular AES encryption, as well as the masked AES-128/192/256 key schedules and C API (regular backend).
  • TEST_SECCIPHER_REGPRESENT: to test regular PRESENT encryption.
  • TEST_SECCIPHER_BSAES: to test bitslice AES encryption, as well as the masked AES-128/192/256 key schedules and C API (bitslice backend).
  • TEST_SECCIPHER_BSPRESENT: to test bitslice PRESENT encryption.
  • TEST_SECCIPHER_REGRIJNDAEL256: to test the masked Rijndael-256-256 encryption, key schedule and C API (regular backend).
  • TEST_SECCIPHER_BSRIJNDAEL256: to test the masked Rijndael-256-256 encryption, key schedule and C API (bitslice backend).

Masked AES and Rijndael-256-256 API

On top of the regular (regaes, RP s-box) and bitslice (bsaes) masked AES-128 encryptions, the repository provides a masked AES-128 key schedule, masked AES-192 and AES-256, a masked Rijndael-256-256 (256-bit block, 256-bit key) and a C API (src/aes_masked.h for AES and the sharing utilities, src/rijndael256_masked.h for Rijndael-256-256), the backend being selected with AES_IMPL. Masked values are passed explicitly as uint8_t x[NBSHARES][16] arrays (the value being x[0] ^ ... ^ x[NBSHARES-1], each share in the usual FIPS-197 byte order):

Masked key schedules

The key schedules work on masked keys and produce masked round keys, directly in the representation of the backend (rows for the regular version, bitslice planes for the bitslice one): all the linear operations (RotWord, word XORs, Rcon added to one share) are applied share by share, and SubWord goes through the masked s-box of the backend (one RP s-box call per SubWord, or one bitslice s-box call per round for up to 4 keys at once). Rijndael-256-256 (Nk = Nb = 8) uses two sequential SubWord per round key. AES-192 and AES-256 use a generic key schedule (Nk = 6 or 8): the masked words are computed in byte format (w[i] = w[i-Nk] ^ t, share by share), the SubWord of up to 4 keys going through one s-box call, and the round keys are then converted in place to the backend representation. The AES-192/AES-256 encryptions use the AES-128 masked cores with 12 / 14 rounds.

API

  • Core API (masked inputs and outputs only, nothing is ever recombined):
    • aes128_masked_core_setkey_enc(ctx, key[NBSHARES][16]): masked key schedule of a masked key,
    • aes128_masked_core_enc(ctx, in[NBSHARES][16], out[NBSHARES][16]): masked encryption of a masked block. Since keys and blocks share the same format, a masked ciphertext can directly be used as a masked key.
  • Standard API (clear inputs and outputs, masking and unmasking done internally):
    • aes128_masked_setkey_enc(ctx, key[16]), aes128_masked_enc(ctx, pt[16], ct[16]).
  • Public keys: when the key is public, the key schedule can be computed in the clear and trivially encoded as (rk, 0, ..., 0) with aes128_masked_setkey_enc_public(ctx, key[16]), or from already expanded clear round keys (rk[176], FIPS-197 byte order) with aes128_masked_ctx_encode_public(ctx, rk).
  • Sharing utilities: masked_encode_16, masked_encode_public_16 (trivial sharing of public data), masked_decode_16, masked_refresh_16 (ISW refresh), and their _24 / _32 variants for 24-byte / 32-byte values (AES-192 / AES-256 keys, Rijndael-256-256 keys and blocks).
  • AES-192 / AES-256: the same API with the aes192_ / aes256_ prefixes and 24-byte / 32-byte keys (16-byte blocks).
  • Rijndael-256-256: the same API with the rijndael256_ prefix and 32-byte blocks (uint8_t x[NBSHARES][32]).
  • Multi-key API (_x2 / _x4 / _x8 suffixes, e.g. aes128_masked_core_setkey_enc_x4(ctx1, ..., ctx4, key1, ..., key4)): key schedules and encryptions of 2, 4 or 8 independent keys, one context per key. With the bitslice backend, the key schedules of up to 4 keys share the same s-box calls (the SubWord of one key only fill 4 of the 16 lanes of the bitslice s-box), _x8 being processed as two batches of 4 keys; the other operations are loops (the masked s-boxes already use the full register width for one block).

All the operations on shares (key schedule, representation changes, encoding, decoding, refresh) are implemented in assembly, the C layer only dispatches (and handles the public key schedule). The internal buffers of the masked encryption and key schedule (including the s-boxes scratch tables) are zeroized at the end of each call. The legacy regaes_encrypt and bsaes_encrypt entry points (clear block in and out) are kept, and now rely on the masked core encryptions regaes_encrypt_core and bsaes_encrypt_core.

NOTE: all the randomness comes from get_random (see RAND_MODE). With the TRNG emulation, the "random" values are constant, which breaks the masking (e.g. with an odd number of shares, an encoding leaves the secret in one share): use C_RAND for functional tests, and provide a real TRNG (or a CSPRNG) as the external random function (EXT_RAND) for real-world use.

Tests

The masked AES APIs are tested along with the AES cipher tests (TEST_SECCIPHER_REGAES and TEST_SECCIPHER_BSAES, C tests in test/test_aes_masked.c), and the masked Rijndael-256-256 APIs with the Rijndael-256-256 cipher tests (TEST_SECCIPHER_REGRIJNDAEL256 and TEST_SECCIPHER_BSRIJNDAEL256, C tests in test/test_rijndael256_masked.c): all the APIs (core, standard, public keys, _x2/_x4/_x8) against known answer tests (FIPS-197 and NIST SP 800-38A for AES-128/192/256, the "Extended Rijndael" ECB KAT files by B. Gladman for Rijndael-256-256) and against a C reference on random inputs, the masked round keys against the clear key schedules, and (with C_RAND or EXT_RAND) the quality of the sharings and the use of fresh randomness by the masked gadgets (with EXT_RAND, that the external random function is actually called).

Using the masked library from another project

The masked AES-128/192/256 and Rijndael-256-256 (assembly and C API) can be used as a library by another project, directly from a clone of this repository and without running configure.py: lib/masked.mk gives the sources (MASKED_LIB_SRC) and the compilation flags (MASKED_LIB_CFLAGS), and lib/param.h replaces the generated param.h, with the parameters set at compilation time:

Make variable / define Effect
MASKED_NBSHARES=n number of shares, 2 to 10 (default 2)
MASKED_REGULAR=1 / -DMASKED_REGULAR regular implementation (RP s-box) instead of the bitslice one (default)
MASKED_TRNG=1 / -DMASKED_TRNG TRNG emulation (performance measurements only) instead of the C rand()
MASKED_EXT_RAND=1 / -DMASKED_EXT_RAND external random function uint32_t masked_random32(void), provided at link time (see EXT_RAND)
MASKED_RANDOM_FUNC=name / -DMASKED_RANDOM_FUNC=name same, with another name for the function

For instance:

MASKED_NBSHARES = 3
include path/to/EC17-software-masking/lib/masked.mk
SRC    += $(MASKED_LIB_SRC)
CFLAGS += $(MASKED_LIB_CFLAGS)

with #include "aes_masked.h" (and/or "rijndael256_masked.h") in the C code. The CPU must be ARMv7E-M (e.g. -mcpu=cortex-m4 -mthumb) or ARMv7-A (e.g. -march=armv7-a), lib/param.h stops the compilation otherwise. The same NOTE on the randomness applies: plug a real TRNG (or a CSPRNG) as the external random function (MASKED_EXT_RAND / MASKED_RANDOM_FUNC) for real-world use.

The library mode is tested by Makefile.lib (the masked AES and Rijndael-256-256 C tests of test/, built against lib/masked.mk by test/test_masked_lib.c), e.g. PLATFORM=cortexm4 make -f Makefile.lib clean platform_test MASKED_NBSHARES=3 MASKED_REGULAR=1, and test_all_configs.py runs it for all the parameters (regular / bitslice, 2 to 10 shares, TRNG / C_RAND / EXT_RAND) on both targets.

How to use

Targets

The assembly code targets regular 32-bit ARMv7A platforms as well as ARMV v7M embedded platforms. In the generated Makefile, we use the two specific cross-compilation targets Cortex-A for ARMv7A, and Cortex-M4 for ARMv7M Thumb-2 (you can however easily adapt this Makefile to change the target with compatible CPUs through gcc -march and -mcpu options). A last target is the "local" one, i.e. compilation on the current platform. For cross-compilation, arm-linux-gnueabi-gcc is used for Cortex-A (all development and tests have been performed with version 15.2.0) and arm-none-eabi-gcc for Cortex-M (all development and tests have been performed with version 14.2.1).

  • The local target:

The "local" target compiles the code for the current platform, i.e. where the current gcc is installed. This can be for instance a Raspberry Pi or any ARM based compatible platform where a GNU compatible gcc compilation toolchain is installed. The produced file is an ELF executable file that can be executed on the same platform, or using qemu user mode emulation (see below the Cortex-A target).

Simply compile the project with make clean && make. This will produce the hom_ec17 ELF executable, that you should be able to execute with ./hom_ec17.

  • The Cortex-A target:

This is a cross-compilation platform for the arm-linux-gnueabi-gcc toolchain. The produced file is an ELF executable that can be executed with the qemu user mode emulation: on recent Linux kernels, the bin-fmt translation layer allows to transparently handle cross-platforms executions.

Simply compile the project with make clean && PLATFORM=cortexa make, this will produce a hom_ec17 ELF executable for ARMv7A Cortex-A. Then, you can launch the emulation with PLATFORM=cortexa make platform_test (this will simply execute ./hom_ec17 and trigger the bin-fmt machinery in the kernel).

  • The Cortex-M4 target:

This is a cross-compilation platform for the arm-none-eabi-gcc toolchain targeting MCUs for embedded devices. The produced file is an ELF executable that in fact represents an embedded firmware. In order to perform tests, we use the so-called semi-hosting feature of the toolchain and qemu so that printing on the console is made easy. Emulation makes use of qemu system that emulates a whole system with this semi-hosting activated, the emulated board being the classical mps2-an386. A minimal linker script linker.ld as well as a minimal startup file startup.c are provided to setup the platform in a minimal working state: beware that some work will be necessary to adapt this to other real-world boards for fully-working firmware (this work goes beyond the current repository purpose).

Simply compile with make clean && PLATFORM=cortexm4 make, this will produce a hom_ec17 ELF that represents a firmware that uses semi-hosting for the mps2-an386 board. Then, emulate with PLATFORM=cortexm4 make platform_test (this actually invokes qemu system with the proper arguments).

Quick compilation commands

To test the code:

  • Run the python3 configure.py script and follow the instructions to set up the desired tests and its parameters.
  • Depending on your target ("local", cross-compile Cortex-A, cross-compile Cortex-M4), compile with:
    • For local: make clean && make
    • For Cortex-A: make clean && PLATFORM=cortexa make
    • For Cortex-M4: make clean && PLATFORM=cortexm4 make
  • Depending on your target, run:
    • For local: ./hom_ec17
    • For Cortex-A: PLATFORM=cortexa make platform_test
    • For Cortex-M4: PLATFORM=cortexm4 make platform_test

Docker

You can find a Makefile.CI that will build a Docker machine that includes all the dependencies (cross-compilers and emulators). The CI (Continuous Integration) will be run with:

make -f Makefile.CI

This builds the Docker with the name ec17_tester.

If you want to manually use this Docker, execute the following:

docker run -it ec17_tester /bin/bash

This will drop a shell in the Docker with all the necessary tools installed, so that you can compile and test everything.

Bugs and test coverage

Due to the numerous amount of parameters, test coverage is implemented in the test_all_configs.py script. This scripts uses a DFS (Depth-First Search) tree traversal to first enumerate all the possible configurations with dynamic detection, and then for all these configurations it compiles and executes the tests for the Cortex-A and Cortex-M4 cross-compilation target. This script is called by the CI (Continuous Integration) Makefile.CI.

Although this should test exhaustively the configurations, bugs might still be present. If you find any bug while testing this code, please contact the authors or contributors (see below).

Contact

If you have any questions, please feel free to contact us at

About

Source code related to the Eurocrypt 2017 paper "How Fast Can Higher-Order Masking Be in Software?"

Resources

Stars

3 stars

Watchers

4 watching

Forks

Releases

Packages

Contributors

Languages