Attacks on White Box Crypto - Hands On Single Bit Attack

White Box Cryptography is a fascinating topic. It deals with cryptography when the attacker has complete control over the box. A good example is a DRM implementation in software, where a video comes in as an encrypted stream and the software has to decrypt the stream to display it. The decryption key is *somewhere* in the software, so it's just a simple reverse engineering job, right? Wrong! There are sophisticated ways to hide the key, and just as sophisticated attacks to try and recover it. It's a cat-and-mouse game involving all the usual software protection tricks (code obfuscation, anti-debugging, packing, etc....) with the addition of hiding the cryptographic information as it's processed. Cool!

There is a large amount of information out on the web on this topic in terms of papers and presentations, but I think a bit more hands-on type information could be useful to people interested.

There are, of course, the usual ways of attacking an application, but in this case, statistical attacks are possible that work in a completely different way. A great place to start is with the Side Channel Marvels repository. The methods there take existing attack vectors designed for grey box hardware exploitation and extend them to the software white box paradigm:

https://github.com/SideChannelMarvels

There are a number of utilities there, including a collection of white box cryptography challenges and solutions:

  • Wyseur 2007 challenge - A Linux binary implementing a DES.
  • Hack.lu 2009 challenge - A Windows binary implementing an AES 128.
  • Karroumi 2010 challenge - A Linux binary implementing an AES 128.
  • SSTIC 2012 challenge - A Python serialized object implementing a DES.
  • NoSuchCon 2013 challenge - A Windows binary implementing an AES 128 with uncompensated external encodings.
  • NoSuchCon 2013 variants - Variants of the NoSuchCon 2013 challenge, using the same white-box generator but compiled for Linux, without obfuscation and with compensated external encodings.
  • PlaidCTF 2013 challenge - A Linux binary implementing an AES 128.
  • CHES 2015 challenge - A GameBoy ROM implementing an AES 128.
  • OpenWhiteBox AES Chow - An implementation of Chow written in Go, implementing an AES 128.
  • OpenWhiteBox AES Xiao-Lai - An implementation of Xiao-Lai written in Go, implementing an AES 128.
  • OpenWhiteBox AES Full - An implementation of OpenWhiteBox paper written in Go, implementing an AES 128.
  • CHES 2016 challenge - A Linux binary (and source) implementing an AES 128.
  • Kryptologik challenge - A JavaScript implementing an AES 256 with diversified round keys.
  • Lee CASE1 challenge - A Linux binary implementing an AES 128.
  • RHME3 prequal whitebox challenge - A Linux binary implementing an AES 128.
  • White Magic challenge - A linux binary implementing ... (CTF still open)

That is a great variety of implementations, but very few cryptosystems. Essentially, they are all AES or DES. The repository provides two general techniques to try and attack the white boxes: Differential Computational Analysis (where we mess with candidate keys) and Differential Fault Analysis (where we mess with the state of the white box). Since these methods take a cue from grey box attacks it's important to be familiar with those.

The image illustrates traditional DPA. The important thing to notice is that one needs a way to predict something that happens differently if at least a subpart of the key is correct. That is, the correct key will cause a very slight difference in the execution trace/power measurement/etc. This does not have to be perfect, it just has to be statistically significant. I have a couple of pages on this available:

The basic idea behind these Differential Analysis attacks is that we can measure *anything* that depends on part of the key being correct, then we can exploit that to determine that part of the key. In grey box attacks, when this is called Differential Power Analysis, this is a power trace that is slightly different when part of the key is correct. But or electromagnetic emissions, run time variations, even sound emissions could be used. In DPA, there is usually a lot of noise in the data. For example, a power trace does not only represent a particular register or bit operation, but the whole operation of the chip, plus electrical noise. In DCA, we have a much more accurate information, because we can take an execution trace of the program and examine every operation and all of memory with perfect digital accuracy.

Since my previous examples on DPA focused on AES, lets try to extend that code to perform an AES whitebox attack. The only real difference between the two attacks is that we perform an execution trace instead of a power trace. SideChannelMarvels provides two tools to collect these execution traces. The difference between the tools is only in the way they collect these traces. TracerGrind uses Valgrind, while TracerPIN uses Intels' PIN. Let's install TracerPIN on a clean Ubuntu 18.04.

TracerPIN is part of the Tracer package:

sudo apt install git
git clone https://github.com/SideChannelMarvels/Tracer.git

now add to the sources to be able to install old software

sudo gedit /etc/apt/sources.list

then add some extra source to the bottom of the list

deb http://archive.ubuntu.com/ubuntu/ xenial main
deb http://archive.ubuntu.com/ubuntu/ xenial universe

then we can install the prerequisites for the Intel PIN build

sudo dpkg --add-architecture i386
sudo apt update

sudo apt-get install --no-install-recommends wget make libstdc++-4.9-dev libssl-dev libsqlite3-dev gcc-multilib g++-multilib libstdc++-4.9-dev:i386 libssl-dev:i386 libsqlite3-dev:i386 g++-4.9-multilib

wget http://software.intel.com/sites/landingpage/pintool/downloads/pin-2.14-71313-gcc.4.4.7-linux.tar.gz
tar xzf pin-2.14-71313-gcc.4.4.7-linux.tar.gz
sudo mv pin-2.14-71313-gcc.4.4.7-linux /opt
export PIN_ROOT=/opt/pin-2.14-71313-gcc.4.4.7-linux
echo -e "\nexport PIN_ROOT=/opt/pin-2.14-71313-gcc.4.4.7-linux" >> ~/.bashrc
cd Tracer/TracerPIN/
make CXX=g++-4.9
sudo make install

Tracer is now installed and allows us to trace executables.

$ Tracer -o ls.log -- ls
$ Attach to pid 11811 failed. 
E:   The Operating System configuration prevents Pin from using the default (parent) injection mode.
E:   To resolve this, either execute the following (as root):
E:   $ echo 0 > /proc/sys/kernel/yama/ptrace_scope
E:   Or use the "-injection child" option.
E:   For more information, regarding child injection, see Injection section in the Pin User Manual.
E: 

so

$ sudo su
# echo 0 > /proc/sys/kernel/yama/ptrace_scope
# exit
$ 

now it runs

$ Tracer -o ls.log -- ls
Makefile  obj-ia32  obj-intel64  pin.log  README.md  Tracer  Tracer.cpp  version.mk  version.sh
$ ls
Makefile  obj-ia32  obj-intel64  pin.log  README.md  Tracer  Tracer.cpp  version.mk  version.sh

but it does not produce a log file. The solution is to install an old kernel

wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v4.11.12/linux-headers-4.11.12-041112_4.11.12-041112.201707210350_all.deb
wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v4.11.12/linux-headers-4.11.12-041112-generic_4.11.12-041112.201707210350_amd64.deb
wget https://kernel.ubuntu.com/~kernel-ppa/mainline/v4.11.12/linux-image-4.11.12-041112-generic_4.11.12-041112.201707210350_amd64.deb
sudo dpkg -i *.deb
rm *.deb

Any kernel after 4.11 does not seem to work with the PIN version used. However, with kernel 4.11 we finally see the log file.

$ Tracer -o ls.log -- ls
[*] Trace file ls.log opened for writing...

ls.log  Makefile  obj-ia32  obj-intel64  pin.log  README.md  Tracer  Tracer.cpp  version.mk  version.sh
$

The log file contains a human-readable trace of the execution of ls.

[...]

[B]         1     0x100005850 loc_100005850: // size=42 thread=0x100000000
[I]         1     0x100005850    xor ebp, ebp                             31 ed
[I]         2     0x100005852    mov r9, rdx                              49 89 d1
[R]         3     0x100005855                                                        0x7fffffffde10 size= 8 value=0x0000000000000001
[I]         3     0x100005855    pop rsi                                  5e
[I]         4     0x100005856    mov rdx, rsp                             48 89 e2
[I]         5     0x100005859    and rsp, 0xfffffffffffffff0              48 83 e4 f0
[I]         6     0x10000585d    push rax                                 50
[W]         6     0x10000585d                                                        0x7fffffffde08 size= 8 value=0x000000000000001c
[I]         7     0x10000585e    push rsp                                 54
[W]         7     0x10000585e                                                        0x7fffffffde00 size= 8 value=0x00007fffffffde08
[I]         8     0x10000585f    lea r8, ptr [rip+0x10aca]                4c 8d 05 ca 0a 01 00
[I]         9     0x100005866    lea rcx, ptr [rip+0x10a53]               48 8d 0d 53 0a 01 00
[I]        10     0x10000586d    lea rdi, ptr [rip-0x19e4]                48 8d 3d 1c e6 ff ff
[C]        11 Calling function 0x7fffe3f72ab0(__libc_start_main)
[!] Function 0x7fffe3f72ab0 is filtered, no tracing
[R]        11     0x100005874                                                           0x10021ffd8 size= 8 value=0x00007fffe3f72ab0
[I]        11     0x100005874    call qword ptr [rip+0x21a75e]            ff 15 5e a7 21 00
[W]        11     0x100005874                                                        0x7fffffffddf8 size= 8 value=0x000000010000587a

[...]

This is really useful for many purposes. Here, we are interested in encryption, though, so we stay with that.

This human-readable trace is encoded in ASCII and not the correct binary pattern we need to look up. So we need to modify the Tracer.cpp source code to allow a new option for printing out a bit-by-bit representation of the execution trace. Now the -t parameter takes in human, sqlite, and bits. There are a number of changes necessary, but the part printing out the trace is this:

static VOID RecordMemBits(ADDRINT ip, CHAR r, ADDRINT addr, UINT8* memdump, INT32 size, BOOL isPrefetch)
{
    TraceFile << std::bitset<64>(*(UINT64*)memdump);
    TraceFile << setfill(' ') << endl;
}

So that a trace looks like this:

$ Tracer -o ls.log -t bits -- ls
[*] Trace file ls.log opened for writing...

ls.log  Makefile  obj-ia32  obj-intel64  pin.log  README.md  Tracer  Tracer.cpp  version.mk  version.sh
$ $ more ls.log 
0000000000000000000000000000000000000000000000000000000000000001
0000000000000000000000000000000000000000000000000000000000011100
0000000000000000011111111111111111111111111111111101111010111000
0000000000000000011111111111111111100101010101001110101010000000
0000000000000000000000000000000100000000000000000110000101011010
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000000000000000000
0000000000000000011111111111111111111111111111111101111011000000
0000000000000000000000000000000100000000000000000110000100110000
0000000000000000000000000000000100000000000000010110101111100000
0000000000000000000000000000000000000000000000000000000000000000
[ ... ]

So all memory reads and writes are now available as a bitstream of sorts, though the bitstream is written as the ASCII values for '0' and '1'.

The simplest implementation of AES is using a block cipher mode called Electronic Codebook. The usage of a particular block cipher mode has repercussions on the DCA attack. We will look at the different cipher modes in the following, but a detailed explanation is available here: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation

Electronic Codebook is the cipher mode assumed in the DPA examples, and we can expect the simple SBOX attack to work:

Next, we need a binary to trace. I have chosen to modify the test.c example from tiny-AES-c, available here: https://github.com/kokke/tiny-AES-c

I changed the test.c file to accept data from stdin and to just to encryption using ECB mode.

#include <stdio.h>#include <string.h>#include <stdint.h>
// Enable ECB mode#define ECB 1
#include "aes.h"
unsigned char data[16];
// prints string as hexstatic void phex(uint8_t* str){ uint8_t len = 16; unsigned char i; for (i = 0; i < len; ++i) printf("%.2x", str[i]); printf("\n");}

void main(int argc, char *argv[]){ if (17 == argc) // 128bit data from input as space separated hex values like: ./aes-128-ecb.elf b0 b1 b2 b3 b4 b5 b6 b7 b8 b9 ba bb bc bd be bf { for (int i = 0; i < 16; i++) { sscanf(argv[i + 1], "%x", &data[i]); } } else { printf("Bad input, using default data\n"); for (int i = 0; i < 16; i++) { data[i] = (unsigned char) i; } } // make sure the library is configured for AES128#if defined(AES128) printf("\nTesting AES128\n\n");#else printf("You need to specify AES128. Exiting"); return 0;#endif
// Example of more verbose verification uint8_t i; uint8_t key[16] = { (uint8_t) 0x00, (uint8_t) 0x11, (uint8_t) 0x22, (uint8_t) 0x33, (uint8_t) 0x44, (uint8_t) 0x55, (uint8_t) 0x66, (uint8_t) 0x77, (uint8_t) 0x88, (uint8_t) 0x99, (uint8_t) 0xaa, (uint8_t) 0xbb, (uint8_t) 0xcc, (uint8_t) 0xdd, (uint8_t) 0xee, (uint8_t) 0xff }; // print text to encrypt, key and IV printf("ECB encrypt verbose:\n\n"); printf("plain text:\n"); for (i = (uint8_t) 0; i < (uint8_t) 1; ++i) { phex(data + i * (uint8_t) 16); } printf("\n");
// print the resulting cipher as 1 x 16 byte strings printf("ciphertext:\n");
struct AES_ctx ctx; AES_init_ctx(&ctx, key);
for (i = 0; i < 1; ++i) { AES_ECB_encrypt(&ctx, data + (i * 16)); phex(data + (i * 16)); } printf("\n"); return;}

We already have a way to make execution traces of binaries. We still need a way to load them into matlab/octave.

I do this using some Matlab code to generate a shell script that we can run to make the traces:

% print shell script to make trace files
fileID = fopen('tracescript.sh','w');
for i=0:199
  clearTextVector = (0:15) + mod(i,241); %create a vector of clear text bytes to encrypt
  commandString = sprintf('Tracer -o tracebits%d.bin -t bits -- ./aes-128-ecb.elf ',i);
  commandString = strcat(commandString, sprintf(' %x',clearTextVector)); % run whitebox with vector
  commandString = strcat(commandString, sprintf('\n')) % newline between commands
  fprintf(fileID, '%s', commandString);
end
fclose(fileID);

We can then execute this script in the shell

$ tail -n4 tracescript.sh 
Tracer -o tracebits196.bin -t bits -- ./aes-128-ecb.elf c4 c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3
Tracer -o tracebits197.bin -t bits -- ./aes-128-ecb.elf c5 c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4
Tracer -o tracebits198.bin -t bits -- ./aes-128-ecb.elf c6 c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5
Tracer -o tracebits199.bin -t bits -- ./aes-128-ecb.elf c7 c8 c9 ca cb cc cd ce cf d0 d1 d2 d3 d4 d5 d6

$ ./tracescript.sh 
[*] Trace file tracebits0.bin opened for writing...


Testing AES128

ECB encrypt verbose:

plain text:
000102030405060708090a0b0c0d0e0f

ciphertext:
279fb74a7572135e8f9b8ef6d1eee003

[*] Trace file tracebits1.bin opened for writing...


[ ... ]

Which results in the traces we need. Next, load them back into Matlab with a script to that they are in the traces variable.

for i=0:199
  fileNameString = sprintf('tracebits%d.bin',i);
  tracefile = fopen(fileNameString,'r');
  traces(i+1,:) = fread(tracefile, 'int8');
  fclose(tracefile);
end


And we are now in a position the re-use the code that we had for the DPA attack!!!:


% declaration of the SBOXSBOX=[099 124 119 123 242 107 111 197 048 001 103 043 254 215 171 118 ... 202 130 201 125 250 089 071 240 173 212 162 175 156 164 114 192 ... 183 253 147 038 054 063 247 204 052 165 229 241 113 216 049 021 ... 004 199 035 195 024 150 005 154 007 018 128 226 235 039 178 117 ... 009 131 044 026 027 110 090 160 082 059 214 179 041 227 047 132 ... 083 209 000 237 032 252 177 091 106 203 190 057 074 076 088 207 ... 208 239 170 251 067 077 051 133 069 249 002 127 080 060 159 168 ... 081 163 064 143 146 157 056 245 188 182 218 033 016 255 243 210 ... 205 012 019 236 095 151 068 023 196 167 126 061 100 093 025 115 ... 096 129 079 220 034 042 144 136 070 238 184 020 222 094 011 219 ... 224 050 058 010 073 006 036 092 194 211 172 098 145 149 228 121 ... 231 200 055 109 141 213 078 169 108 086 244 234 101 122 174 008 ... 186 120 037 046 028 166 180 198 232 221 116 031 075 189 139 138 ... 112 062 181 102 072 003 246 014 097 053 087 185 134 193 029 158 ... 225 248 152 017 105 217 142 148 155 030 135 233 206 085 040 223 ... 140 161 137 013 191 230 066 104 065 153 045 015 176 084 187 022];

numberOfTraces = 200;traceSize = max(size(traces(1,:))); for i=0:(numberOfTraces-1) plaintext(i+1,:) = (0:15) + mod(i,241);end

byteStart = 1;byteEnd = 16;keyCandidateStart = 0;keyCandidateStop = 255;solvedKey = zeros(1,byteEnd);
% for every byte in the key do:for currentKeyByte=byteStart:byteEnd currentKeyByte for currentKeyByteGuess = keyCandidateStart:keyCandidateStop % iterate through all candidate key bytes, 0x00 to 0xff currentKeyByteGuess xorResult = bitxor(plaintext(:,currentKeyByte),currentKeyByteGuess); % first operation is an XOR between the cleartext and the guessed key Hypothesis(:,currentKeyByteGuess+1)=SBOX(xorResult+1); % then the XOR gets substituted +1 because MATlab index starts at group1= zeros(1,traceSize); group2= zeros(1,traceSize); numberOfTracesInGroup1 = 0; numberOfTracesInGroup2 = 0;
% for all traces put into one or other group based on predicted Least Significant Bit for L = 1:numberOfTraces firstByte = bitget(Hypothesis(L,currentKeyByteGuess+1),1); % get the expected least significant bit from the hypothesis if firstByte == 1 group1(1,:) = group1(1,:) + traces(L,:); numberOfTracesInGroup1 = numberOfTracesInGroup1 + 1; else group2(1,:) = group2(1,:) + traces(L,:); numberOfTracesInGroup2 = numberOfTracesInGroup2 + 1; end; end; % Calculation of the average of the groups group1(1,:) = group1(1,:) / numberOfTracesInGroup1; % average of group 1 group2(1,:) = group2(1,:) / numberOfTracesInGroup2; % average of group 2 groupDifference = abs(group1(1,:)-group2(1,:)); % subtracting the averages and taking the absolute value. We are interested in the largest difference, positive or negative from average groupFin(currentKeyByteGuess+1,:) = groupDifference; % storing the difference trace based on the current key byte guess. maxDifference(currentKeyByteGuess+1) = max(groupDifference); end; % so now we have all the trace differences and we need to find the one that contains the largest value [traceDifferenceWithLargestValueX, traceDifferenceWithLargestValueY] = find(groupFin==max(groupFin(:))); solvedKey(1,currentKeyByte) = traceDifferenceWithLargestValueX(1) - 1; fprintf('%x ', solvedKey); fprintf('\n');end;solvedKey

The result is that the key gets broken very quickly. We have essentially perfect measurements and don't have to content with the electrical noise...it's like shooting fish in a barrel.

Correct Key:                        00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

Predicted(200 traces using bit 1):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted(100 traces using bit 1):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted( 50 traces using bit 1):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted( 30 traces using bit 1):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

Predicted( 20 traces using bit 1):  00 11 22 0d 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted( 20 traces using bit 2):  00 11 22 33 44 55 66 77 88 99 3d BB CC DD EE FF
Predicted( 20 traces using bit 3):  00 11 22 33 44 55 66 77 88 99 AA BB CC 12 EE FF
Predicted( 20 traces using bit 4):  00 11 22 33 44 77 66 77 88 99 AA BB CC DD EE FF
Predicted( 20 traces using bit 5):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted( 20 traces using bit 6):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
Predicted( 20 traces using bit 7):  00 11 22 33 44 55 66 77 88 15 0E BB CC DD EE FF
Predicted( 20 traces using bit 8):  00 11 22 5b 44 55 4a 77 88 99 AA BB CC DD EE FF

Predicted( 20 traces consensus)  :  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF

Predicted( 15 traces using bit 1):  40 47 85 0E 44 54 66 0F AB AC 2B A9 7E 1B EE CC
Predicted( 15 traces using bit 2):  7C 7C 20 B3 44 47 24 C1 10 3A 3D 31 E4 80 AE 39
Predicted( 15 traces using bit 3):  8C 11 90 A9 44 1F 3C 77 88 13 96 C8 94 12 31 C4
Predicted( 15 traces using bit 4):  00 51 15 53 A8 CE A5 9A 6E 4B 42 D4 54 D7 B5 27
Predicted( 15 traces using bit 5):  B8 11 FA 33 44 55 8E 8D 1D 99 92 91 5B 72 4A FF
Predicted( 15 traces using bit 6):  00 7D 30 30 44 E7 66 08 20 F3 00 BB E7 68 C3 DC
Predicted( 15 traces using bit 7):  05 32 1E B5 44 55 66 CE 0E 15 0E ED CC 33 38 85
Predicted( 15 traces using bit 8):  65 AF A7 5B 44 8F 92 C8 96 A4 E2 37 4A DD 02 2D

Predicted( 15 traces consensus)  :  00 11 ?? ?? 44 55 66 ?? ?? ?? ?? ?? ?? ?? ?? ??

This concludes the test of the single bit Matlab based attack on a basic AES-128-CBC executable. However, this section is about white box crypto, and so I want to run a test on a bona-fide white box executable as well. We will use the wbs_aes_kryptologik whitebox challenge available at https://github.com/SideChannelMarvels/Deadpool

Previously, for the Differential Power Analysis we had with 200 power traces. Here, we have 200 execution traces. Let's do the 1 bit attack on the 8 bits separately and see how well it works

Correct Key:             0D 9B E9 60 C4 38 FF 85 F6 56 BD 48 B7 8A 0E E2

Predicted(using bit 1):  0D 5E E9 60 C4 38 FF 85 F6 56 BD 48 B7 8A 0E E2
Predicted(using bit 2):  0D 9B E9 60 C4 38 FF 85 97 56 BD 48 B7 8A 2F E2
Predicted(using bit 3):  0D 9B E9 3A C4 38 FF 85 F6 56 BD 48 B7 8A 0E E2
Predicted(using bit 4):  0D 9B E9 C9 C4 38 FF 85 F6 56 BD 48 B7 8A 8C E2
Predicted(using bit 5):  0D 9B E9 FF C4 38 FF 85 F6 56 BD 48 B7 7D 0E E2
Predicted(using bit 6):  0D 9B 42 60 2C 99 FF 85 F6 C5 BD 48 B7 8A EF D7
Predicted(using bit 7):  0D 9B BD 60 64 38 FF 85 F6 25 C7 48 B7 8A 0E 66
Predicted(using bit 8):  0D 28 E9 E8 C4 38 DD 85 F6 F7 BD 48 B7 8A 0E E2

Predicted(consensus)  :  0D 9B E9 60 C4 38 FF 85 F6 56 BD 48 B7 8A 0E E2

It is tougher than basic AES-CBC, but it fails nontheless.

All the files used here are available at https://github.com/janbbeck/SideChannelMatlab

References:

[1] Differential Computation Analysis: Hiding your White-Box Designs is Not Enough. https://eprint.iacr.org/2015/753.pdf

[2] Attacks and Countermeasures for White-box Designs. https://eprint.iacr.org/2018/049.pdf

[3] Computational Aspects of Correlation Power Analysis. https://eprint.iacr.org/2015/260.pdf

[4] Unboxing the White-Box. https://www.blackhat.com/docs/eu-15/materials/eu-15-Sanfelix-Unboxing-The-White-Box-Practical-Attacks-Against-Obfuscated-Ciphers-wp.pdf

[5] Hiding your White-Box Designs is Not Enough. https://www.troopers.de/media/filer_public/b8/4f/b84f0051-3992-4b34-8b7d-7f0be5f209e0/troopers16_teuwen_hiding_your_wb_designs.pdf

[6] Differential Power Analysis of HMAC SHA-2in the Hamming Weight Model. https://www.cryptoexperts.com/sbelaid/articleHMAC.pdf

[7] NSA Suite B Crypto, Keys, and Side Channel Attacks. https://www.rambus.com/wp-content/uploads/2015/08/2013-JunMarson-SuiteBAndSideChannel.pdf

[8] Introduction to Side-Channel Power Analysis (SCA, DPA). https://www.youtube.com/watch?v=OlX-p4AGhWs&t=2336s

[9] T.S. Messerges. Using second-order power analysis to attack DPA resistant soft-ware. InCryptographic Hardware and Embedded Systems

[10] https://github.com/SideChannelMarvels

[11] https://github.com/kokke/tiny-AES-c

[12] https://github.com/janbbeck/SideChannelMatlab