Skip to content

HotIceCream/janus

 
 

Repository files navigation

General information

What is Janus?

Janus is a small framework which is built in Java to provide assistance in finding independent-biclique attacks. Given a certain cipher, Janus not only finds an optimal bipartite graph (biclique), but also an optimal matching-with-precomputation step, rendering the found biclique, and determining the computational complexity of the attack.

What are independent-bicliques attacks?

Biclique cryptanalysis was first introduced by Khovratovich et al. in 2011 and presented at the FSE 2012. Bicliques represent an improvement of the splice-and-cut approach which is on the other hand based on common meet-in-the-middle techniques. More detailed, biclique cryptanalysis uses a complete bipartite graph (biclique) which can be constructed over a part of a primitive to extend an existing meet-in-the-middle or similar attack. While the biclique-based splice-and-cut approach was intentionally concepted to target hash functions, it has become a well-known technique to attack block ciphers today.

A biclique is a complete bipartite graph which covers some steps of a cipher. It connects every element in a set of starting states S_j with every element in a set of ending states C_i, where a path from S_{j} to C_{i} represents the encryption under a key K[i,j]. More formally, the 3-tuple of sets [\{S_j\}, \{C_i\}, \{K[i,j]\}] is called a d-dimensional biclique, if

\forall i,j \in \{0, \ldots, 2^d - 1 \}: \quad S_j \xrightarrow[B]{K[i,j]} C_i.

The basic idea is to divide the key space into 2^{k-2d} groups of 2^{2d} keys, where k denotes the length of the secret key and d is the dimension of the biclique. A biclique can then be defined for one such group of keys K[i,j], where the individual keys are represented relative to a so-called \textit{base key} of the group, K[0,0], and two differences \Delta^K_{i} and \nabla^K_{j}:

K[i,j] = K[0,0] \oplus \Delta_{i} \oplus \nabla_{j}.

For every biclique, the adversary tests 2^{2d} keys with 2 \cdot 2^d computations. Therefore, it needs to construct 2^{k - 2d} bicliques to cover the full key space. For the time complexity, Bogdanov et al. proposed the equation:

C_{full} = 2^{k - 2d} \left(C_{biclique} + C_{decrypt} + C_{precompute} + C_{recompute} + C_{falsepos}\right),

where
  • C_{biclique} denotes the costs for constructing a biclique,
  • C_{decrypt} is the complexity of the oracle to decrypt 2^d ciphertexts,
  • C_{precompute} represents the effort for the computation of v for 2^d computations of E_2 \circ E_1,
  • C_{recompute} describes the costs of recomputing 2^{2d} values v_{i,j} in both directions, and
  • C_{falsepos} is the complexity to eliminate false positives.
The full computational effort of the attack is dominated by the recomputations costs. The memory requirements are upper bounded by storing 2^d intermediate states v_{i,j}.

Moreover, in the case when a constructed biclique is quite short and the matching part needs to cover too many rounds, then a meet-in-the-middle attack may no longer be applicable. In such cases, Bogdanov et al. proposed an alternative procedure called matching with precomputations. This approach is practically an optimized exhaustive search. Thus, it can be applied to any primitive and any given number of rounds. The important point is to identify a matching which has a lowest possible effort.

Why use an automated approach?

Finding good (independent) bicliques over a given number of rounds is a time-consuming task which requires in-depth knowledge of the investigated cipher to find well-suited differentials. Thus, it is adequate to think about using a computer to find such bicliques. Usually, implementations of common block cipher APIs are not designed to provide a suffciently fine granularity, e.g., access to single steps and the basic operations of the cipher is not supported, but required to find good bicliques.

A unified API is needed to reduce the effort of modifying a block cipher implementation for the biclique search. In addition, such an API would allow to apply one single biclique-searching framework that fits all. Janus is open source and free to use. It provides a highly modular and flexible API, i.e., it allows the user to determine parameters like the used cryptographic primitive, the starting/ending round, the dimension of the biclique, the starting difference, etc.

Concepts of Janus

Our current implementation consists of four subsystems, as shown in the figure below.

  1. The biclique search subsystem is responsible for searching for independent differential trails over some rounds of a given primitive.
  2. Given a found biclique, the matching subsystem analyzes the remaining parts of the cipher to find a matching which leads to an attack with a minimal computational effort.
  3. The rendering subsystem can visualize bicliques as well as matching phase differentials in PDF format, using the community version 5.3.0 of the open-source library iText.
  4. Moreover, the framework contains a number of common components, such as cipher implementations, serialization and utility classes, as well as cipher-dependent helper classes which generate and compare differentials.

Biclique search

The task of finding independent bicliques can be transformed into the task of finding pairs of independent differentials (\Delta, \nabla). In advance, the user needs to specify:

  • a target cipher E,
  • a round range [r,s] which define a subcipher B in E,
  • the dimension of bicliques d,
  • a strategy to test the independency of differentials,
  • and a strategy to define and generate round key differences.
Assume that B covers the rounds [r, s], 1 \leq r \leq s \leq N_r of a given cipher E, where N_r is the total number of rounds in E. We denote by N_r^{B} = s - r + 1 the number of rounds covered by B, and
  • by T_i the state after Round i,
  • by U_i the intermediate state after all non-linear operations in Round i,
  • and by K_i the round key which is used in Round i.
We further call the state of the cipher's key register which contains the key for Round r, as the starting key, and the state which contains the key for Round s the ending key.

First, we fix K[0,0] and S_0 to all zeroes and derive C_0. This base computation is computed only once for a given cipher and round interval. We create a trail \Delta^f which will contain all state values T_i, all intermediate state values U_i, as well all round keys K_i which are used in B. At the beginning, we initialize them with all-zero values. Then, we choose a starting key difference \Delta^{K^f} with respect to K[0,0] with d bits set. In the following, we iterate over all 2^d possible values for the d set bits in \Delta^{K^f}, and compute 2^{d} - 1 differential trails

S_0 \xrightarrow[B]{K[0,0] \oplus \Delta^{K^f}_i} C^f_i, \quad \forall\ i \in \{1, \ldots, 2^d - 1\}.

We derive the bit-wise XOR between the corresponding states, intermediate states and round keys of the trail \Delta^f and the base computation:

\Delta^f_i = \left(S_0 \xrightarrow[B]{K[0,0]} C_0 \right) \oplus \left(S_0 \xrightarrow[B]{K[0,0] \oplus \Delta^{K^f}_i} C^f_i \right).

Bits which are active in any of the 2^{d - 1} differential trails \Delta^f_i should remain active in the differential \Delta^f. Thus, the \Delta^f_i-trails are accumulated to \Delta^f by applying the logical OR pair-wise to all corresponding state and round key differences of all differentials \Delta^f_i:

\Delta^f \gets \bigvee\limits_{i = 1}^{2^d - 1} \Delta^f_i.

The procedure is then repeated for in total N_d unique starting key differences \Delta^{K^f}, \forall f \in \{1, \ldots, N^d - 1\}. All N_d accumulated forward trails \Delta^f are stored in a list, before the N_d backward trails \nabla^b are computed with a similar procedure.

We need to check for every pair of differentials (\Delta^f, \nabla^b), if there are any corresponding intermediate states or round keys, which share parts that are active in non-linear operations. If not, the pair yields an independent biclique. Since any identified biclique can be used to mount an attack, we provide an option for the early abort as soon as the first pair has been found. The time complexity of the biclique search process is given by

C_{time} = C_{forward} + C_{backward} + C_{testing},

where
  • C_{forward} is given by constructing 2^{N_d} \Delta-differentials,
  • C_{backward} by constructing 2^{N_d} \nabla-differentials,
  • and C_{testing} by comparing 2^{2N_d} pairs of differentials (\Delta, \nabla).
The complexity is dominated by the effort for testing 2^{2N_d} pairs of differentials. We store the states and round keys of 2^{N_d} forward differentials, where every differential holds N_r^{B} + 1 (from r - 1 to s) state differences, N_r^{B} (from r to s) intermediate state differences, and a cipher-dependent number of N_k round key differences (since their can be pre- or post-whitening keys). Hence, we need to store

C_{memory} = 2^{N_d} \cdot (2 N_r^{B} + 1) \cdot n + N_k \cdot k

bits, where n and k denote state and key size, respectively. If the available memory is not sufficient to store all forward differentials, the biclique search can be performed in multiple iterations, where in each iteration, a different fraction of the differentials is stored and tested.

In our framework the class BicliqueFinder (finder, hereafter) is responsible for starting and coordinating the biclique search. Prior, the client passes the required parameters -- the round range [r, s], the target cipher, the dimension d, etc. -- encapsulated in an instance of type BicliqueFinderContext. To speed up the search, the finder then simply delegates the computation and testing of differentials to parallel-working instances of two internal classes DeltaThread and NablaThread, which compute all forward differentials first, and backward differentials second.

Since ciphers contain non-linear operations at different steps in its round function, the finder needs a cipher-dependent strategy to test the independency of differential pairs. Therefore, our framework contains an interface DifferentialComparator and a package with helper classes, e.g, AESHelper or ARIAHelper that implement it and solve this task for a particular primitive. The correct helper class that shall be used in an attack can be set as a parameter by the client.

Differences

We have to clarify the strategy to generate key differences, i.e., locating the d active bits. The number of the differentials to test, N_d, depends on the dimension of the biclique and the key length. Assume, we are given a cipher with a key length k and a chosen dimension d. Then, we could potentially test N_{d} = \binom{k}{d} differentials, which becomes inpracticable fast for k \geq 64. Though, this effort can be reduced significantly for byte-wise- or nibble-wise operating ciphers. Here, we consider three different strategies to generate key differences.
  1. Firstly, we can set only a minimum of d active bits in the key difference. Then, considering our example with byte-wise operating primitives, there are only \frac{k}{\lceil d/8\rceil} bytes which can be active in the round key at the first or last round of the biclique. As a consequence, for byte-wise and nibble-wise operating primitives, the testing effort reduces to

    N_{d} = \tbinom{k / 8}{\lceil d / 8\rceil} \quad \text{and} \quad N_{d} = \tbinom{k / 4}{\lceil d / 4\rceil}

    differentials, respectively. For bit-wise operating primitives, one can limit the number of differentials to a user-definable number of unique random start key differences.
  2. As a second approach, one can use the same active bits multiple times in the start key difference. At the first sight, these will produce additional active bytes in the state after a key injection, making it harder for the differential to be independent in a pair. At second sight, the further active bytes may cancel out byte differences in the key schedule and/or the round transformation of AES-like ciphers, as we can learn from the attack on SQUARE by Mala. Though, this strategy increases the number of tested keys to N_d = 2^{k/8} for byte-wise and N_d = 2^{k/4} for nibble-wise primitives, respectively.
  3. Alternatively, one can employ custom rules to generate round-key differences. In their attack on AES-192, Bogdanov \etAl employed the inverse result of a \termname{MixColumns} operation as a part of the round key difference. In their attack on ARIA-256, Chen and Xue used a dedicated difference where the first half of the 256-bit key canceled the difference injected by the left half. One can learn from those examples that cipher-specific key differences can result in longer bicliques for AES-like ciphers. Since the testing of all custom differences in the key space is infeasible, the task of choosing "good" key differentials can be left open to the user.

Matching

A matching-with-precomputations step is supposed to be applied to the subciphers -- in our case E_2 \circ E_1 -- not covered by a given biclique. Hence, this step is located after a biclique search. Our framework investigates two aspects to identify a well-suited matching: first, it tests all possible rounds which can be used to locate V:

P \xrightarrow[E_1]{} V \xleftarrow[E_2^{-1}]{} S,

and second, it tests all possible nibbles or bytes in V which can be used for a partial matching. For every round i that can be used to locate V, we perform four steps:
  1. First, we compute differentials from the start and the end of the matching part to the middle:

    P \xrightarrow[E_1]{K[0,0] \oplus \nabla^K_j} v_i \oplus \nabla v_i, \qquad \text{and} \qquad v_i \oplus \Delta v_i \xleftarrow[E_2^{-1}]{K[0,0] \oplus \Delta^K_i} S.

    Note, that these differential trails result from injecting differences in the round keys.
  2. Then, we create a new difference \delta v in which the bits that are used for matching are set. Then, we compute the differentials from v to the start and to the end:

    P \oplus \delta P \xleftarrow[E_1^{-1}]{K[0,0]} v_i \oplus \delta v_i \qquad \text{and} \qquad v_i \oplus \delta v_i \xrightarrow[E_2]{K[0,0]} S \oplus \delta S,

    These trails reflect all bits in the state which are needed to compute the bits that are used for partial matching.
  3. We need to consider only those parts of the states and round keys in the differential trails that are active in both differential trails, P \rightarrow \delta v and $\delta P \leftarrow v, for the recomputation. We denote by \wedge the logical bit-, nibble- or byte-wise AND (depending on the cipher) which we apply between all bits of the corresponding states and round keys to compute the accumulated differential

    \Delta^p_i = (P \rightarrow \delta v_i) \wedge (\delta P \leftarrow v_i).

    Similarly, we compute

    \nabla^s_i = (\delta v_i \rightarrow S) \wedge (v_i \leftarrow \delta S).

  4. As the final step, we count the number of active bits/nibbles/bytes in non-linear operations are counted in both \Delta^p_i and \nabla^s_i to have a single number which refers best to the recomputational effort.

The merging is illustrated schematically in the following figure. At the top, we investigate how the key differences affect the state from outside in. In the middle, we choose a part of the state which we use to match at the state v, and go from inside out, investigating which parts of the states are needed to reconstruct it. Since we need to consider only those parts of the states and round keys in the differential trails that are active in both differential trails, we merge them in the bottom illustration.

Considering our implementation, the class MatchingDifferentialBuilder is responsible to perform the steps above. Similar to the biclique search, the required parameters are bundled in a context class MatchingContext. As a parameter, the context expects the found biclique, the cipher and a cipher-specific helper class, which allows to determine the number of active bits, nibbles or bytes in non-linear operations. After the optimal matching has been found and returned by the MatchingDifferentialBuilder, the class ComplexityCalculator computes the total complexity of the attack.

Rendering

Our framework allows to render identified bicliques and matching phases in PDF format, using the community version 5.3.0 of the open-source library iText which is licensed under the AGPL. The classes BicliqueRenderer and MatchingRenderer are responsible to carry out the rendering process. In the following, we provide the interfaces DifferentialRenderer and StateRenderer so that the rendering classes can be simply exchanged by the client according to the type of differential and the cipher.

Quickstart User Guide

Applications

The source code is given in the src folder. Additionally, two runnable java archives bicliquesearch.jar, and matcher.jar have been exported. The classes BicliqueFinderApplication and MatchingApplication are used as entry points for the JAR files.

The class BicliqueFinderApplication represents the application for the biclique search. Its method setUp() creates instances of the classes BicliqueFinder and BicliqueFinderContext, and instantiates the required parameters for the biclique search from the command line arguments. The arguments should include the target cipher, a differenceBuilder, a differentialComparator, the dimension of bicliques, and a value maxNumBicliquesRounds for the maximal number of rounds of the ciphers, where bicliques are searched in.

The cipher is instantiated using a class CipherFactory and its static method createCipher(CipherName cipher), which accepts a user-provided string for the cipher name. Valid names are defined in an enum CipherName in the CipherFactory class. Depending on the type of the cipher, the differenceBuilder is initialized with an instance of type BytewiseDifferenceBuilder, BitwiseDifferenceBuilder, or NibblewiseDifferenceBuilder.

Since the non-linear operations can be positioned at different steps in the round function of different ciphers, we needed a cipher-dependent helper class, which tests, if two differentials share active components steps. Therefore, we created an interface DifferentialComparator, and a package with helper classes (for instance, AESHelper, ThreeFishHelper), that implement this interface for each cipher. They CipherHelper interface extends the DifferentialComparator interface. A CipherHelperFactory and its static method createCipherHelper are used to instantiate the correct helper class for the user-provided cipher.

After the context parameters are created, the BicliqueFinderApplication calls the methods testFindBicliquesAtEnd(), and testFindBicliquesAtStart(). Both methods contain a loop, which iterates over the number of tested rounds for the created biclique. Found bicliques are printed in the console output and are stored in a file. Finally, the method tearDown() of the application calls tearDown() of the BicliqueFinder to clear the lists of stored differentials and bicliques.

The class MatchingApplication is structured similarly to the BicliqueFinderApplication class. Its method setUp() instantiates a MatchingDifferentialBuilder and a ComplexityCalculator. The user provides a path to an XML file of a serialized biclique. The biclique is created by an instance of the class BicliqueXMLParser. Moreover, the user provides a cipher name as argument, from which the corresponding cipher and a cipherHelper are constructed by the factories, like in the BicliqueFinderApplication.

The CipherHelper interface also extends the DifferentialActiveComponentsCounter, which is used to determine the number of active components in non-linear operations in the matching rounds. The found matching phase is given as the input for a ComplexityCalculator, and the individual summands of the attack complexity are printed on the console. At the end, the matching phase is rendered and saved in PDF format.

For every cipher and cipher version, there exists a JUnit test case <cipher+version>BicliqueTest for the biclique search, and a <cipher+version>MatchingTest for the creation of the matching phase, respectively, for instance, AES128BicliqueTest and AES128MatchingTest. To avoid code repetition, the tests inherit from the abstract classes AbstractBicliqueFinderTest and AbstractMatchingTest.

Both applications employ the Apache Commons CLI library in version 1.2 to process command line options. As required parameters, the exported bicliquesearch.jar runnable expects a cipher, the dimension, and the maximal number of rounds. A valid call would be, for instance,

java -jar bicliquesearch.jar -c aes128 -d 8 -r 4.

If a parameter is missing, the application displays a help view:

Missing required options: c, d, r
usage: bicliquesearch
-c <arg> Name of the cipher to test. Allowed values for cipherName
include (any casing allowed):
AES128, AES192, AES256, ARIA128, ARIA192,
ARIA256, BKSQ96, BKSQ144, BKSQ192, KHAZAD,
LED64, LED128, PRESENT80, PRESENT128, SQUARE,
THREEFISH256, THREEFISH512, THREEFISH1024, WHIRLPOOLCIPHER
-d <arg> Dimension of the biclique. Defaults to 8.
-debug Log debugging information. Defaults to false.
-r <arg> Maximum number of rounds for bicliques.
-stop Stops the search on current rounds, if one biclique was found.
Defaults to true.

The exported runnable matchingsearch.jar expects the name of a cipher, a path to a serialized biclique file, and a output path, where a PDF will be created, which contains a visual representation of the best found matching phase. A valid call would be, for instance,

java -jar matchingsearch.jar -c aes128 -i results/xml/aes128_8_10.xml -o results/matching/aes128_8_10.pdf.

Ciphers

We need cipher implementations, which support round-wise encryption and decryption, and which offer access to internals such as round keys. Unfortunately, these requirements discard existing cipher implementations. Instead, we needed custom implementations of the investigated ciphers, and have proposed a given interface RoundBasedSymmetricCipher. Our cipher implementations inherit from the abstract class AbstractRoundBasedSymmetricCipher. For ciphers which provide several versions, like the AES with the 128-bit, 192-bit, and 256-bit versions, there is one abstract base class (e.g., AES), which contains the major parts of the cipher logic, and concrete child classes (AES128, AES192, AES256), which initialize their parent class with individual state and key sizes, and may contain individual key schedules. To ensure that all our cipher implementations work correctly, there are JUnit test cases for all ciphers for both encryption and decryption with the test vectors of their specifications.

To extend the length of bicliques, we need to start at round keys with a low difference, and derive the secret keys from these. As a consequence, we need to invert the key schedule of the investigated ciphers in order to generate the secret key out from a given round key. This is not possible with all ciphers. Though, the ciphers we are interested in -- AES-like constructions, PRESENT, or ThreeFish -- allow to reconstruct the secret key from a given state of the key-register at a certain round. The method boolean canInvertKeySchedule() indicates, whether a primitive is able to invert the key schedule. If this method returns true, the method ByteArray computeExpandedKey(ByteArray keyPart, int round) is expected to compute the full expanded key from a given part of the expanded key. The provided part is expected to represent the state of the key register after the subkey for the given round was computed. The method ByteArray computeExpandedKey(ByteArray keyPart, int round) is used in the classes DifferentialBuilder and in the MatchingDifferentialBuilder.

About

A framework for automated search for independent-biclique attacks

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Java 100.0%