Skip to main content

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

  1. Implement your own class inheriting from WhiteBoxedAES to provide the script an access to the whitebox to attack.
  2. 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 value 0 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. If True, a separate process is used to solve these equations, otherwise, the Sage library is loaded within the current Python process
  • step1DoubleValue : apply Step 1 with the property used in the paper (two fixed values by column) (default: False). If this option is False, 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 to fround+1. A valid fault position must fault 4 bytes of the reversed output with this method.
  • reverseRoundMethod2: reverse the result up to fround+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 when fround == 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

Quarkslab

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

Metadata

Release files for darkphoenixAES 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for darkphoenixAES 1.0.0
File Size Uploaded Provenance
darkphoenixAES-1.0.0.tar.gz 144.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for darkphoenixAES 1.0.0
File Interpreter ABI Platform
darkphoenixAES-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 312.7 kB

Release files / darkphoenixAES-1.0.0.tar.gz

Download URL darkphoenixAES-1.0.0.tar.gz
Size 144.2 kB
Tags Source
SHA-256 checksum
How to use checksums
e85ccf378241bd935204b13ff3cec61af3bb16b6f9cdded490d0810ce3da0671
BLAKE2b-256 checksum
How to use checksums
d6b94eb57884dd96f5fe77731b00553dbd371f7f97cb88099dd9ac52f8371428
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.11.1

Release files / darkphoenixAES-1.0.0-py3-none-any.whl

Download URL darkphoenixAES-1.0.0-py3-none-any.whl
Size 168.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f810ba6175b4490dc1d1fe856f9a4bbe089143308af4f9823a7aee3372d2ef8f
BLAKE2b-256 checksum
How to use checksums
c574563b8f4ab250e4a04d94e9acdc3e4a6fd003a0388bbbf2261e5ed73826dd
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/4.0.2 CPython/3.11.1

Security for darkphoenixAES 1.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page