Masking
CryptoSideChannel.Masking.Masked
— Typestruct 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
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 eitherBoolean
orArithmetic
, 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, aGenericLog
type, or anotherMasked
type for higher-order masking.T2
is the type of the second share. This should always be either a primitive integer type, or aGenericLog
type.
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)
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)
Be cautious when extending the Masked
datatype. During any new custom operation the unmasked value should never be computed.