Masking
CryptoSideChannel.Masking.Masked — Typestruct Masked{M, T1, T2}
val::T1
mask::T2
endThe 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
The above statement holds only in theory. See the article on problems with high-level software masking for details on this problem.
Type Arguments
Mis the way in which the underlying value is masked.Mcan be eitherBooleanorArithmetic, representing boolean masking or arithmetic masking, respectively.T1is the type of the first share. This can be any integer-like type: A primitive integer, aGenericLogtype, or anotherMaskedtype for higher-order masking.T2is the type of the second share. This should always be either a primitive integer type, or aGenericLogtype.
It may be useful to extract the content of a Masked type, for example at the end of a cryptographic calculation.
CryptoSideChannel.Masking.unmask — Functionunmask(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.
Masking Types
Boolean Masking
CryptoSideChannel.Masking.BooleanMask — FunctionBooleanMask(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.
Arithmetic Masking
CryptoSideChannel.Masking.ArithmeticMask — FunctionArithmeticMask(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.
Conversion
CryptoSideChannel.Masking.arithmeticToBoolean — FunctionarithmeticToBoolean(a::Masked{Arithmetic})::Masked{Boolean}Execute the algorithm outlined in Goubin's paper to convert from algebraic shares to boolean shares.
See also: arithmeticToBoolean
CryptoSideChannel.Masking.booleanToArithmetic — FunctionbooleanToArithmetic(a::Masked{Boolean})::Masked{Arithmetic}Execute the algorithm outlined in Goubin's paper to convert from boolean shares to algebraic shares.
See also: arithmeticToBoolean
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)
endTo 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)Be cautious when extending the Masked datatype. During any new custom operation the unmasked value should never be computed.