DarkPhoenix is a tool to perform differential fault analysis attacks (DFA) against AES whiteboxes with external encodings
Project description
Dark Phoenix
The Phoenix became Dark Phoenix due to allowing human emotions to cloud its judgment. In this state, Phoenix was the strongest, but also an evil entity that thirsted for power and destruction. Totally uncontrollable, Dark Phoenix was a force to be reckoned with as it was not bound by a human conscience.
DarkPhoenix is a tool to perform differential fault analysis attacks (DFA) against AES whiteboxes with external encodings, as described in
- A DFA Attack on White-Box Implementations of AES with External Encodings, Alessandro Amadori, Wil Michiels, Peter Roelse [1]
Dependencies
In order to solve some equations, you should have SageMath installed on your computer and available in your PATH (under the name sage
).
Install
$ pip install darkphoenixAES
Test
$ python3 -m darkphoenixAES --selftest
Usage
To use this attack, you should
- Implement your own class inheriting from
WhiteBoxedAES
to provide the script an access to the whitebox to attack. - Instantiate the
Attack
class with your own class as parameter, and run it.
1. Implement a class inheriting from WhiteBoxedAES
The class inheriting from WhiteBoxedAES
will be the interface between the whitebox and the attack script.
This class must be able to introduce a fault at a given position in the whitebox.
Here is an example of implementation. More information is available in the file WhiteBoxedAES.py. A complete example is implemented in WhiteBoxedAESTest.py.
from darkphoenixAES import WhiteBoxedAES
class MyWhiteBoxedAES(WhiteBoxedAES):
def __init__(self, ...):
self.aeswb = ...
def getRoundNumber(self):
# return the number of rounds of the whitebox (10 for AES128,
# 12 for AES192 and 14 for AES256)
return 10
def isEncrypt(self):
# Does the whitebox encrypt of decrypt data
# (needed and validate with the MixColumns result)
return True
def hasReverse(self):
# Is there an applyReverse method that can be called?
return False
def apply(self, data):
# Apply the whitebox on a buffer
# [param] data a buffer of 16 bytes (type bytes)
# return 16 bytes of the encrypted/decrypted data
return # TODO : return the encrypted value of data
def applyFault(self, data, faults):
# Apply the whitebox on a buffer and inject fault at the given position
# [param] data a buffer of 16 bytes (type bytes)
# [param] faults a list of faults to apply:
# fround, fbytes, fxorval = faults[0]
# fround the round to apply the fault. 0 is the first round
# fbytes the position of the internal state byte to fault (between 0 and 15)
# fxorval the fault to apply by xor (between 1 and 255)
# return 16 bytes of the faulted encrypted data
return # TODO : return the encrypted faulted value of data with the given faults
2. Instantiate and run the Attack class
from darkphoenixAES import Attack
import MyWhiteBoxedAES
# initialize your whitebox
myWB = MyWhiteBoxedAES(...)
# run the attack
# The file "backup.json" will be used the save the result of
# each step and must be removed before running on a new instance.
attack = Attack(myWB)
attack.run("backup.json")
# print extracted roundKey
attack.printKey()
# get the extracted Key
key = attack.getKey()
print("key:", key.hex())
# get the external encoding
inputE, outputE = attack.externalEncoding()
When instantiating the Attack class, you can specify the following optional arguments:
nprocess
: The number of processes used by multiprocess (default: autodetect (None
)) The special value0
disables the use of multiprocess.noprogress
: Enable or disable the progress bar (default: autodetect TTY (None
))sageSubProc
: Use Sage in a subprocess (default:True
). The attack needs SageMath to solve some equations. IfTrue
, a separate process is used to solve these equations, otherwise, the Sage library is loaded within the current Python processstep1DoubleValue
: apply Step 1 with the property used in the paper (two fixed values by column) (default:False
). If this option isFalse
, only one fixed value is needed in Step 1 (reducing the complexity by 256). However, this optimization delays the detection of a wrong injection position during Step 2.
Advanced Usage
Selecting fault position during the attack (manually)
To perform the attack, the fault must first be injected one MixColumn before the output, then two MixColumn before, etc. While the position of the first faults can be found by looking at the output, this not the case for the next ones.
If you want to manually select the fault position during the attack, you can use the base class WhiteBoxedAESDynamic
instead of WhiteBoxedAES
:
from darkphoenixAES import WhiteBoxedAESDynamic
class MyWhiteBoxedAES(WhiteBoxedAESDynamic):
# ... same as WhiteBoxedAES
def prepareFaultPosition(self, fround, reverseRoundMethod, reverseRoundMethod2=None):
# TODO search fault for the round `fround`
pass
When the attack needs to inject faults in a new round, the method
prepareFaultPosition
will first be called. In order to verify if the fault is valid for this round, two methods are given:
reverseRoundMethod
: reverse the result up tofround+1
. A valid fault position must fault 4 bytes of the reversed output with this method.reverseRoundMethod2
: reverse the result up tofround+2
. A valid fault position must fault all the bytes of the reversed output with this method. This method is provided to detect is the fault is applied at fround+2 instead of fround. This method is not provided whenfround == wb.getRoundNumber()-1
When prepareFaultPosition
returns, 16 different fault positions must have been found according to reverseRoundMethod
and reverseRoundMethod2
.
Selecting fault position during the attack (automatically)
DarkPhoenix integrates a second mechanism to identify the fault position. To use it, the base class WhiteBoxedAESAuto
must be used, and the method Attack.run
should be replaced by Attack.runAuto
:
from darkphoenixAES import WhiteBoxedAESAuto
class MyWhiteBoxedAES(WhiteBoxedAESAuto):
# ... same as WhiteBoxedAES
def changeFaultPosition(self, fround, fbytes):
# TODO select a random fault position for (fround, fbytes)
pass
def applyFault(self, data, faults):
for fround, fbytes, fxorval in faults:
# TODO Apply the fault fxorval at the last selected position for (fround, fbytes)
The method changeFaultPosition
selects a random fault position and associates (fround, fbytes) to this position. When a fault is asked with applyFault
with the same (fround, fbytes), this position should be used. If DarkPhoenix detects that the position is not valid, changeFaultPosition
will be called again until a valid position is found.
While DarkPhoenix is able to identify if the fault is mathematically valid, changeFaultPosition
must verify that the fault position is viable (i.e. it does not crash the process) for any fault value.
WhiteBoxedAES compatible with multiprocessing
When Attack
is not called with nprocess=0
, the computation of the two first steps will be performed in many multiprocessing.Process
or in a multiprocessing.Pool
. On Linux, this is equivalent to a fork (see multiprocessing documentation).
When a new process is created during these steps, the new process has it own copy of WhiteBoxedAES
. However, depending of the implementation of WhiteBoxedAES
, some resources need to be recreated (file descriptor, subprocess, debugged process, ...). For this purpose, the method newThread
will be called on the new copy of WhiteBoxedAES
when the new thread start.
However, the first WhiteBoxedAES
and any copy of it must return the same result for the same input with the same fault. If this not possible, you should disable multiprocessing with nprocess=0
.
If using dynamic fault position, prepareFaultPosition
and changeFaultPosition
are always called on the first instance of WhiteBoxedAES
. The fault position must be shared with any future copy of WhiteBoxedAES
.
About
Authors and Contributors
Initial Authors and Contributors:
- Nicolas Surbayrole
- Philippe Teuwen
For next contributions, see the git project history.
Copyright
License
DarkPhoenix is provided under the Apache 2.0 license.
Credits
Many thanks to Alessandro Amadori, author of [1], for having shared his simulation scripts, which greatly helped us verify our own implementation during its development.
References
[1] Amadori A., Michiels W., Roelse P. (2020) A DFA Attack on White-Box Implementations of AES with External Encodings. In: Paterson K., Stebila D. (eds) Selected Areas in Cryptography – SAC 2019. SAC 2019. Lecture Notes in Computer Science, vol 11959. Springer, Cham. https://doi.org/10.1007/978-3-030-38471-5_24
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file darkphoenixAES-1.0.0.tar.gz
.
File metadata
- Download URL: darkphoenixAES-1.0.0.tar.gz
- Upload date:
- Size: 144.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.1
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | e85ccf378241bd935204b13ff3cec61af3bb16b6f9cdded490d0810ce3da0671 |
|
MD5 | b9754cab98fe348acd9c496b34e25d07 |
|
BLAKE2b-256 | d6b94eb57884dd96f5fe77731b00553dbd371f7f97cb88099dd9ac52f8371428 |
File details
Details for the file darkphoenixAES-1.0.0-py3-none-any.whl
.
File metadata
- Download URL: darkphoenixAES-1.0.0-py3-none-any.whl
- Upload date:
- Size: 168.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/4.0.2 CPython/3.11.1
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | f810ba6175b4490dc1d1fe856f9a4bbe089143308af4f9823a7aee3372d2ef8f |
|
MD5 | feb517fc6c3f1baa9f20d487835823c2 |
|
BLAKE2b-256 | c574563b8f4ab250e4a04d94e9acdc3e4a6fd003a0388bbbf2261e5ed73826dd |