Masking

CryptoSideChannel.Masking.MaskedType
struct Masked{M, T1, T2}
    val::T1
    mask::T2
end

The Masked datatype behaves like an integer, but splits its internal value into two shares. Hence, the plain value held by a Masked type should not be observable in memory

Warning

The above statement holds only in theory. See the article on problems with high-level software masking for details on this problem.

Type Arguments

  • M is the way in which the underlying value is masked. M can be either Boolean or Arithmetic, representing boolean masking or arithmetic masking, respectively.
  • T1 is the type of the first share. This can be any integer-like type: A primitive integer, a GenericLog type, or another Masked type for higher-order masking.
  • T2 is the type of the second share. This should always be either a primitive integer type, or a GenericLog type.
source

It may be useful to extract the content of a Masked type, for example at the end of a cryptographic calculation.

CryptoSideChannel.Masking.unmaskFunction
unmask(a::Masked)

Unmask the contained integer by calculating val ⊻ mask, or val + mask respectively.

Note that this function is unsafe with respect to side-channels. After calling this function, the data will no longer be split into two shares. Thus, this method should only be called at the end of a cryptographic algorithm to extract the final result.

source

Masking Types

Boolean Masking

CryptoSideChannel.Masking.BooleanMaskFunction
BooleanMask(v)

Create a masked integer holding value v. Internally, v will be stored in two shares, val and mask, such that v = val ⊻ mask. The latter condition is an invariant of this datatype.

It should always be the case that mask is a primitive type, i.e. of the type Integer or GenericLog. If higher-order masking is desired, val can be of the type Masked.

source

Arithmetic Masking

CryptoSideChannel.Masking.ArithmeticMaskFunction
ArithmeticMask(v)

Create a masked integer holding value v. Internally, v will be stored in two shares, val and mask, such that v = val - mask. The latter condition is an invariant of this datatype.

It should always be the case that mask is a primitive type, i.e. of the type Integer or GenericLog. If higher-order masking is desired, val can be of the type Masked.

source

Conversion

Problems with High-level Masking

We defined our Masked datatype as a construct in the high-level Julia language. This poses some problems with respect to compiler optimisations. Essentially, all masking security guarantees rely on the fact that some intermediate values will never appear in memory. However, the masking approach introduces new, "unneccessary" computation instructions. Depending on compiler optimisations, some of the masking steps may be removed in the final executable.

For example, consider the masked array lookup. Recall that for a table lookup $Y = T[X]$, with $X = A_X \oplus M_X$ a table $T'$ with $T'[X] = T[X \oplus M_X] \oplus M'_X$ is computed. Later on, the table is only accessed at index $A_X$. Hence, a compiler may notice that all other fields of the table are never accessed, and may optimize the code in the final program to only compute $T'[A_X] = T[A_X \oplus M_X] \oplus M'_X$. However, note that for computing the latter, $A_X \oplus M_X$ appears as an intermediate result. Thus, this may allow conclusions about the unmasked value.

The consequences of this issues are simple: This project is for academic, testing, and educational purposes only. Do not use the Masked datatype as a protection in a real-world system. Exploring ways to preserve masking through compiler optimisations in Julia could be done in future work.

Defining new methods for Masked types

It is possible to extend the provided methods for Masked types with custom methods.

The first decision that has to be made is whether the operation should be implemented for arithmetic masking or for boolean masking. In general, methods requiring arithmetic over $\mathbb{Z}$ are suitable for arithmetic masking, while methods using bitwise operations often require boolean masking.

Next, the desired operator has to be implemented. For example, we will show how to implement the boolean negation ~. Since this operator works bitwise, we will immplement the masked version on boolean masking. Recall that we want to invert a value $X = A_X \oplus M_X$. Here, it is sufficient to simply invert the mask:

function Base.:(~)(a::Masked{Boolean})::Masked{Boolean}
    Masked{Boolean,typeof(a.val),typeof(a.mask)}(a.val, ~a.mask)
end

To complete this definition for all masked datatypes, we need to define the method for arithmetic masking as well. However, now simple conversion can be used to convert from arithmetic to boolean masking:

Base.:(~)(a::Masked{Arithmetic}) = ~arithmeticToBoolean(a)
Warning

Be cautious when extending the Masked datatype. During any new custom operation the unmasked value should never be computed.