├── synthesis ├── build │ └── .keep ├── traces │ └── .keep ├── circuits │ └── .keep ├── whitebox │ ├── ciphers │ │ ├── __init__.py │ │ ├── AES │ │ │ ├── __init__.py │ │ │ ├── ref.py │ │ │ ├── bitaes.py │ │ │ ├── keyschedule.py │ │ │ ├── linear.py │ │ │ └── sbox.py │ │ └── Simon │ │ │ └── simon.py │ ├── tree │ │ ├── __init__.py │ │ ├── node │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ ├── bitnode.py │ │ │ ├── op.py │ │ │ ├── optbitnode.py │ │ │ └── node.py │ │ ├── trace.py │ │ ├── anf.py │ │ └── orderer.py │ ├── templates │ │ ├── __init__.py │ │ ├── whibox2019.c │ │ ├── whibox2019compact.c │ │ └── template.py │ ├── containers │ │ ├── __init__.py │ │ ├── rect.py │ │ └── vector.py │ ├── __init__.py │ ├── utils.py │ ├── prng.py │ ├── fastcircuit.h │ ├── whibox.py │ ├── tracing.py │ ├── fastcircuit.py │ ├── fastcircuit.c │ ├── masking.py │ ├── orderer.py │ └── serialize.py ├── slides.pdf ├── attacks_summary.png ├── .gitignore ├── Makefile ├── examples │ ├── compile_run.py │ ├── minimal.py │ └── generate.py ├── tools │ ├── trace.py │ └── runcircuit.c ├── attacks │ ├── AES_AFTER_SBOX │ ├── combine4daredevil.py │ ├── sbox.py │ ├── reader.py │ ├── analyze_linalg_1st.py │ └── analyze_exact.py └── README.md ├── .gitignore ├── algebraic_security_AC2018 ├── implementation │ ├── libwb │ │ ├── __init__.py │ │ ├── gates │ │ │ ├── __init__.py │ │ │ ├── bitaes.py │ │ │ ├── keyschedule.py │ │ │ ├── linear.py │ │ │ └── sbox.py │ │ ├── ops.py │ │ ├── orderer.py │ │ ├── bit.py │ │ └── containers.py │ ├── key │ ├── .gitignore │ ├── test_plaintext │ ├── test_ciphertext │ ├── reference_impl.py │ ├── run.sh │ ├── buildrun.sh │ ├── README.md │ └── build │ │ └── template.c ├── verification │ ├── .gitignore │ ├── tree │ │ ├── __init__.py │ │ ├── node │ │ │ ├── __init__.py │ │ │ ├── utils.py │ │ │ ├── bitnode.py │ │ │ ├── op.py │ │ │ ├── node.py │ │ │ └── optbitnode.py │ │ ├── trace.py │ │ ├── anf.py │ │ └── symbolic.py │ ├── utils.py │ ├── minimalist3.py │ ├── gadget_check.py │ ├── gadget_trace.py │ └── README.md ├── attacks │ ├── .gitignore │ ├── 2_DCA_linalg_1st.sh │ ├── 0_build.sh │ ├── 2_DCA_exact_1st_2nd.sh │ ├── 2_DCA_Daredevil_1st.sh │ ├── 1_trace.sh │ ├── reader.py │ ├── maketrace.py │ ├── combine4daredevil.py │ ├── AES_AFTER_SBOX │ ├── sbox.py │ ├── README.md │ ├── analyze_linalg_1st.py │ └── analyze_exact.py ├── slides.pdf ├── WhiteBoxAttacksCountermeasures.pdf └── README.md ├── LICENSE.md └── README.md /synthesis/build/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /synthesis/traces/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /synthesis/circuits/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.bin 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/__init__.py: -------------------------------------------------------------------------------- 1 | from node import * 2 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/.gitignore: -------------------------------------------------------------------------------- 1 | traces/* 2 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/key: -------------------------------------------------------------------------------- 1 | samplekey1234567 2 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/gates/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/.gitignore: -------------------------------------------------------------------------------- 1 | traces/* 2 | daredevil.config 3 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/__init__.py: -------------------------------------------------------------------------------- 1 | from node import * 2 | -------------------------------------------------------------------------------- /synthesis/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/synthesis/slides.pdf -------------------------------------------------------------------------------- /synthesis/whitebox/templates/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .template import * 4 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/.gitignore: -------------------------------------------------------------------------------- 1 | build/opcodes.bin 2 | build/code.bin 3 | build/code.c 4 | -------------------------------------------------------------------------------- /synthesis/attacks_summary.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/synthesis/attacks_summary.png -------------------------------------------------------------------------------- /algebraic_security_AC2018/slides.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/algebraic_security_AC2018/slides.pdf -------------------------------------------------------------------------------- /synthesis/whitebox/containers/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .vector import Vector 4 | from .rect import Rect 5 | -------------------------------------------------------------------------------- /synthesis/whitebox/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from whitebox.tree.node import OptBitNode as Bit 4 | from whitebox.orderer import * 5 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from bitaes import BitAES 4 | from aes import encrypt, encrypt_pycrypto 5 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/2_DCA_linalg_1st.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | NTRACES=$1 4 | WINDOW=$2 5 | 6 | sage analyze_linalg_1st.py "$NTRACES" "$WINDOW" 7 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/test_plaintext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/algebraic_security_AC2018/implementation/test_plaintext -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/test_ciphertext: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/algebraic_security_AC2018/implementation/test_ciphertext -------------------------------------------------------------------------------- /algebraic_security_AC2018/WhiteBoxAttacksCountermeasures.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cryptolu/whitebox/HEAD/algebraic_security_AC2018/WhiteBoxAttacksCountermeasures.pdf -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from node import Node 4 | from bitnode import BitNode 5 | from optbitnode import OptBitNode 6 | 7 | Bit = OptBitNode 8 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/0_build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | PYTHON=$(which pypy || which python2 || which python) 4 | 5 | (cd ../implementation/ && "$PYTHON" generate.py && ./buildrun.sh) 6 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/2_DCA_exact_1st_2nd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | NTRACES=$1 4 | WINDOW=$2 5 | PYTHON=$(which pypy || which python2 || which python) 6 | 7 | "$PYTHON" analyze_exact.py "$NTRACES" "$WINDOW" 8 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/__init__.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from node import Node 4 | from bitnode import BitNode 5 | from optbitnode import OptBitNode 6 | 7 | from op import OP, BitOP 8 | 9 | Bit = OptBitNode 10 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def tag_bits(**k): 4 | for name, bit in k.items(): 5 | bit.meta["tag"] = name 6 | 7 | def fake_inputs(**k): 8 | for name, bit in k.items(): 9 | bit.meta["fake-input"] = name 10 | -------------------------------------------------------------------------------- /synthesis/.gitignore: -------------------------------------------------------------------------------- 1 | build/opcodes.bin 2 | build/code.bin 3 | build/code.c 4 | build/submit.c 5 | build/submit 6 | build/* 7 | traces/* 8 | circuits/* 9 | maskverif* 10 | *.bin 11 | *.gz 12 | *.so 13 | *.o 14 | plain 15 | cipher 16 | tools/runcircuit 17 | daredevil.config 18 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def tag_bits(**k): 4 | for name, bit in k.items(): 5 | bit.meta["tag"] = name 6 | 7 | def fake_inputs(**k): 8 | for name, bit in k.items(): 9 | bit.meta["fake-input"] = name 10 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/2_DCA_Daredevil_1st.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | NTRACES=$1 4 | PYTHON=$(which pypy || which python2 || which python) 5 | 6 | echo Combining traces for Daredevil... 7 | "$PYTHON" combine4daredevil.py "$NTRACES" 8 | 9 | echo Running Daredevil 10 | daredevil -c daredevil.config 11 | -------------------------------------------------------------------------------- /synthesis/whitebox/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def str2bin(s): 4 | return map(int, "".join(bin(ord(c))[2:].zfill(8) for c in s)) 5 | 6 | def bin2str(b): 7 | assert len(b) % 8 == 0 8 | v = int("".join(map(str, b)), 2) 9 | v = ("%x" % v).zfill(len(b) / 4) 10 | return v.decode("hex") 11 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/1_trace.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | NTRACES=$1 4 | PYTHON=$(which pypy || which python2 || which python) 5 | 6 | echo Cleaning traces... 7 | rm -f traces/*.{pt,ct,bin,input,output} 8 | 9 | echo Tracing... 10 | export OPCODES=../implementation/build/opcodes.bin 11 | "$PYTHON" maketrace.py ../implementation/build/code.bin "$NTRACES" 12 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/reference_impl.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | from Crypto.Cipher import AES 5 | 6 | k = open("key").read()[:16] 7 | 8 | pt = open("test_plaintext").read() 9 | ct = AES.new(k).encrypt(pt) 10 | open("test_ciphertext", "w").write(ct) 11 | 12 | print "key: ", k.encode("hex"), `k` 13 | print "plaintext: ", pt.encode("hex"), `pt` 14 | print "ciphertext:", ct.encode("hex"), `ct` 15 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/ref.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from pyaes import * 4 | 5 | def encrypt(plain, key, nr=10): 6 | ks = ks_expand(map(ord, key)) 7 | s = transpose(map(ord, plain)) 8 | for r in xrange(nr): 9 | s = AddRoundKey(s, ks[r]) 10 | s = SubBytes(s) 11 | s = ShiftRows(s) 12 | if r < nr - 1: 13 | s = MixColumns(s) 14 | s = AddRoundKey(s, ks[nr]) 15 | return "".join(map(chr, transpose(s))) 16 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/trace.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | def circuit_trace(outputs, input_order, inputs_list): 4 | traces = defaultdict(int) 5 | input_names = [bit.name() for bit in input_order] 6 | for n, input_values in enumerate(inputs_list): 7 | trace = dict(zip(input_order, input_values)) 8 | for bit in outputs: 9 | bit.eval(trace) 10 | 11 | for bit, value in trace.items(): 12 | value &= 1 13 | traces[bit] |= (value << n) 14 | return traces 15 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/trace.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | def circuit_trace(outputs, input_order, inputs_list): 4 | traces = defaultdict(int) 5 | input_names = [bit.name() for bit in input_order] 6 | for n, input_values in enumerate(inputs_list): 7 | trace = dict(zip(input_order, input_values)) 8 | for bit in outputs: 9 | bit.eval(trace) 10 | 11 | for bit, value in trace.items(): 12 | value &= 1 13 | traces[bit] |= (value << n) 14 | return traces 15 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | rm -f build/code.bin || true 4 | 5 | CC=$(which tcc || which gcc || which clang) 6 | "$CC" build/code.c -o build/code.bin 7 | 8 | export OPCODES=./build/opcodes.bin 9 | 10 | >./build/ciphertext.bin 11 | 12 | echo "Encrypting..." 13 | time ./build/code.bin ./build/ciphertext.bin 14 | echo 15 | 16 | echo "Ciphertext:" 17 | xxd ./build/ciphertext.bin 18 | echo 19 | 20 | echo "Difference:" 21 | diff <(xxd ./build/ciphertext.bin) <(xxd test_ciphertext) 22 | 23 | echo ========== 24 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/buildrun.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | rm -f build/code.bin || true 4 | 5 | CC=$(which tcc || which gcc || which clang) 6 | "$CC" build/code.c -o build/code.bin 7 | 8 | export OPCODES=./build/opcodes.bin 9 | 10 | >./build/ciphertext.bin 11 | 12 | echo "Encrypting..." 13 | time ./build/code.bin ./build/ciphertext.bin 14 | echo 15 | 16 | echo "Ciphertext:" 17 | xxd ./build/ciphertext.bin 18 | echo 19 | 20 | echo "Difference:" 21 | diff <(xxd ./build/ciphertext.bin) <(xxd test_ciphertext) 22 | 23 | echo ========== 24 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/utils.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from itertools import product 4 | from collections import defaultdict 5 | 6 | from random import choice, randint 7 | 8 | def sbin(x, n): 9 | return "".join(map(str, tobin(x, n))) 10 | 11 | def tobin(x, n): 12 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 13 | 14 | def frombin(v): 15 | return int("".join(map(str, v)), 2 ) 16 | 17 | def hamming(x): 18 | ans = 0 19 | while x: 20 | ans += x & 1 21 | x >>= 1 22 | return ans 23 | 24 | hw = hamming 25 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/anf.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from symbolic import Bit, Var, Const 4 | 5 | def compute_anfs(bit): 6 | if bit.is_input(): 7 | bit.meta["anf"] = Bit(Var(bit.name())) 8 | return 9 | 10 | if bit.is_const(): 11 | bit.meta["anf"] = Bit(Const(bit.value())) 12 | return 13 | 14 | TreeBit = bit.__class__ 15 | res = [] 16 | for sub in bit.args: 17 | if isinstance(sub, TreeBit) and "anf" not in sub.meta: 18 | compute_anfs(sub) 19 | res.append(sub.meta["anf"]) 20 | bit.meta["anf"] = bit.OP.eval(bit.op, res) 21 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/anf.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from cryptools.py.anf.symbolic import Bit 4 | 5 | def compute_anfs(bit): 6 | if bit.is_input(): 7 | bit.meta["anf"] = Bit(bit.name()) 8 | return 9 | 10 | if bit.is_const(): 11 | bit.meta["anf"] = Bit(bit.value()) 12 | return 13 | 14 | TreeBit = bit.__class__ 15 | res = [] 16 | for sub in bit.args: 17 | if isinstance(sub, TreeBit) and "anf" not in sub.meta: 18 | compute_anfs(sub) 19 | res.append(sub.meta["anf"]) 20 | bit.meta["anf"] = bit.OP.eval(bit.op, res) 21 | # print "BIT", bit, "ANF", bit.meta["anf"] 22 | -------------------------------------------------------------------------------- /synthesis/Makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | all: tools/runcircuit whitebox/libfastcircuit.so 4 | 5 | tools/runcircuit: tools/runcircuit.c whitebox/fastcircuit.c whitebox/fastcircuit.h 6 | gcc -Wall -O3 -Iwhitebox/ whitebox/fastcircuit.c tools/runcircuit.c -o tools/runcircuit 7 | 8 | whitebox/libfastcircuit.so: whitebox/fastcircuit.c whitebox/fastcircuit.h 9 | gcc -Wall -O3 -Iwhitebox/ whitebox/fastcircuit.c -fPIC -shared -o whitebox/libfastcircuit.so 10 | 11 | clean: 12 | rm -f tools/runcircuit whitebox/libfastcircuit.so 13 | 14 | submit: 15 | gcc -O3 build/submit.c build/main.c -o build/submit 16 | diff <(./build/submit . 16 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/maketrace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys, os 5 | from subprocess import check_output 6 | 7 | program = sys.argv[1] 8 | n = int(sys.argv[2]) 9 | 10 | pts = [] 11 | cts = [] 12 | 13 | for i in xrange(n): 14 | print "Trace", i 15 | fpt = "traces/%04d.pt" % i 16 | fct = "traces/%04d.ct" % i 17 | ft = "traces/%04d.bin" % i 18 | 19 | if not os.path.exists(fpt): 20 | print " ", "new plaintext" 21 | pt = os.urandom(16) 22 | with open(fpt, "w") as f: f.write(pt) 23 | with open(fpt) as f: pt = f.read(16) 24 | 25 | os.environ["TRACE"] = ft 26 | ct = check_output([program], stdin=open(fpt)) 27 | with open(fct, "w") as f: f.write(ct) 28 | print " ", "size", os.stat(ft).st_size 29 | -------------------------------------------------------------------------------- /synthesis/examples/compile_run.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from whitebox.tree.node import OptBitNode as Bit 4 | from whitebox.utils import str2bin, bin2str 5 | from whitebox.ciphers.AES import BitAES 6 | from whitebox.serialize import RawSerializer 7 | from whitebox.fastcircuit import FastCircuit 8 | 9 | KEY = "MySecretKey!2019" 10 | 11 | pt = Bit.inputs("pt", 128) 12 | ct, k10 = BitAES(pt, Bit.consts(str2bin(KEY)), rounds=10) 13 | 14 | # Encode circuit to file 15 | RawSerializer().serialize_to_file(ct, "circuits/aes10.bin") 16 | 17 | # Python API for C simulator 18 | C = FastCircuit("circuits/aes10.bin") 19 | 20 | ciphertext = C.compute_one("my_plaintext_abc") 21 | 22 | # Verify correctness 23 | from Crypto.Cipher import AES 24 | ciphertext2 = AES.new(KEY).encrypt("my_plaintext_abc") 25 | assert ciphertext == ciphertext2 26 | -------------------------------------------------------------------------------- /synthesis/tools/trace.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import sys, os 4 | from whitebox.fastcircuit import FastCircuit, chunks 5 | from whitebox.tracing import trace_split_batch 6 | 7 | FC = FastCircuit(sys.argv[1]) 8 | NAME = os.path.basename(sys.argv[1]) 9 | N = int(sys.argv[2]) 10 | 11 | pts = [os.urandom(16) for _ in xrange(N)] 12 | 13 | cts = FC.compute_batches( 14 | inputs=pts, 15 | trace_filename_format="./traces/" + NAME + ".%d" 16 | ) 17 | for i in xrange((N+63)//64): 18 | print "splitting", i 19 | filename = "./traces/" + NAME + ".%d" % i 20 | trace_split_batch( 21 | filename=filename, 22 | make_output_filename=lambda j: "./traces/%04d.bin" % (i * 64 + j), 23 | ntraces=64, 24 | packed=True) 25 | os.unlink(filename) 26 | 27 | for i, (pt, ct) in enumerate(zip(pts, cts)): 28 | with open("traces/%04d.pt" % i, "wb") as f: 29 | f.write(pt) 30 | with open("traces/%04d.ct" % i, "wb") as f: 31 | f.write(ct) 32 | 33 | -------------------------------------------------------------------------------- /synthesis/whitebox/prng.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | class PRNG(object): 4 | n = NotImplemented # state size 5 | 6 | def set_state(self, state): 7 | self.state = list(state) 8 | assert len(self.state) == self.n 9 | 10 | def step(self): 11 | raise NotImplementedError() 12 | 13 | 14 | class LFSR(PRNG): 15 | def __init__(self, taps, state): 16 | self.n = len(state) 17 | self.set_state(state) 18 | 19 | self.taps = tuple(map(int, taps)) 20 | assert all(0 <= tap < self.n for tap in taps) 21 | assert 0 in taps 22 | 23 | def step(self): 24 | res = reduce(lambda a, b: a ^ b, [self.state[i] for i in self.taps]) 25 | self.state = [res] + self.state[:-1] 26 | return res 27 | 28 | 29 | from random import choice 30 | 31 | class Pool(PRNG): 32 | def __init__(self, prng, n=1000): 33 | self.prng = prng 34 | self.n = int(n) 35 | self.set_state([prng.step() for _ in xrange(n)]) 36 | 37 | def step(self): 38 | return choice(self.state) 39 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/bitnode.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from op import BitOP 4 | from node import Node 5 | 6 | class BitNode(Node): 7 | OP = BitOP() 8 | 9 | def make_binary(op): 10 | def f(a, b): 11 | if isinstance(b, int): b = a.const(b) 12 | return a.__class__(op, a, b) 13 | return f 14 | 15 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 16 | And = __and__ = __rand__ = make_binary(OP.AND) 17 | Or = __or__ = __ror__ = make_binary(OP.OR) 18 | del make_binary 19 | 20 | def __invert__(self): 21 | return self.new(self.OP.NOT, self) 22 | Not = __invert__ 23 | 24 | def is_const(self): 25 | return self.op in (self.OP.ONE, self.OP.ZERO) 26 | 27 | def value(self): 28 | assert self.op in (self.OP.ONE, self.OP.ZERO) 29 | return int(self.op == self.OP.ONE) 30 | 31 | @classmethod 32 | def const(cls, v): 33 | return cls.new(cls.OP.ONE) if v else cls.new(cls.OP.ZERO) 34 | 35 | BitNode.ZERO = BitNode.const(0) 36 | BitNode.ONE = BitNode.const(1) 37 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/bitnode.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from op import BitOP 4 | from node import Node 5 | 6 | class BitNode(Node): 7 | OP = BitOP() 8 | 9 | def make_binary(op): 10 | def f(a, b): 11 | if isinstance(b, (int, long)): b = a.const(b) 12 | return a.__class__(op, a, b) 13 | return f 14 | 15 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 16 | And = __and__ = __rand__ = make_binary(OP.AND) 17 | Or = __or__ = __ror__ = make_binary(OP.OR) 18 | del make_binary 19 | 20 | def __invert__(self): 21 | return self.new(self.OP.NOT, self) 22 | Not = __invert__ 23 | 24 | def is_const(self): 25 | return self.op in (self.OP.ONE, self.OP.ZERO) 26 | 27 | def value(self): 28 | assert self.op in (self.OP.ONE, self.OP.ZERO) 29 | return int(self.op == self.OP.ONE) 30 | 31 | @classmethod 32 | def const(cls, v): 33 | return cls.new(cls.OP.ONE) if int(v) else cls.new(cls.OP.ZERO) 34 | 35 | @classmethod 36 | def consts(cls, vs): 37 | return [cls.const(v) for v in vs] 38 | -------------------------------------------------------------------------------- /synthesis/whitebox/fastcircuit.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // depends on the need of batch executions, may be reduced to char (e.g., for challenge submission) 4 | typedef uint64_t WORD; 5 | 6 | // if circuit requires more memory than 2^16 bits, need to change this 7 | typedef uint16_t ADDR; 8 | 9 | // unlikely that there are more opcodes (and serialization method relies on this structure...) 10 | typedef uint8_t BYTE; 11 | 12 | typedef struct { 13 | uint64_t input_size; 14 | uint64_t output_size; 15 | uint64_t num_opcodes; 16 | uint64_t opcodes_size; 17 | uint64_t memory; 18 | } CircuitInfo; 19 | 20 | typedef struct { 21 | CircuitInfo info; 22 | ADDR *input_addr; 23 | ADDR *output_addr; 24 | BYTE *opcodes; 25 | WORD *ram; 26 | } Circuit; 27 | 28 | enum OP {_, XOR, AND, OR, NOT, RANDOM}; 29 | 30 | void __attribute__ ((constructor)) set_seed_time(); 31 | void set_seed(uint64_t seed); 32 | WORD randbit(); 33 | 34 | Circuit *load_circuit(char *fname); 35 | void free_circuit(Circuit *C); 36 | void circuit_compute(Circuit *C, uint8_t *inp, uint8_t *out, char *trace_filename, int batch); 37 | -------------------------------------------------------------------------------- /synthesis/examples/minimal.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import os 4 | from whitebox.tree.node import OptBitNode as Bit 5 | from whitebox.utils import str2bin, bin2str 6 | 7 | NR = 10 8 | KEY = "MySecretKey!2019" 9 | 10 | from whitebox.ciphers.AES import BitAES 11 | pt = Bit.inputs("pt", 128) 12 | ct, k10 = BitAES(pt, Bit.consts(str2bin(KEY)), rounds=NR) 13 | 14 | from whitebox.prng import LFSR, Pool 15 | prng = LFSR(taps=[0, 2, 5, 18, 39, 100, 127], 16 | state=BitAES(pt, pt[::-1], rounds=2)[0]) 17 | rand = Pool(n=128, prng=prng).step 18 | 19 | from whitebox.masking import MINQ, DOM, mask_circuit 20 | ct = mask_circuit(ct, MINQ(rand=rand)) 21 | ct = mask_circuit(ct, DOM(rand=rand, nshares=2)) 22 | 23 | # a) generate WhibOx submission 24 | from whitebox.whibox import whibox_generate 25 | whibox_generate(ct, "build/submit.c", "Ok, world!") 26 | 27 | # b) compile circuit to file 28 | from whitebox.serialize import RawSerializer 29 | RawSerializer().serialize_to_file(ct, "circuits/aes10.bin") 30 | 31 | # c) compute reference AES to verify correctness 32 | from whitebox.ciphers.AES.aes import encrypt 33 | pt = os.urandom(64) 34 | ct = "".join(encrypt(pt[i:i+16], KEY, nr=NR) for i in xrange(0, len(pt), 16)) 35 | open("build/cipher", "w").write(ct) 36 | 37 | -------------------------------------------------------------------------------- /synthesis/tools/runcircuit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "fastcircuit.h" 8 | 9 | // void AES_128_encrypt(char *ct, char *pt) { 10 | // compute(pt, ct, 1); 11 | // } 12 | 13 | int main(int argc, char *argv[]) { 14 | if (getenv("RANDOM_SEED")) 15 | set_seed(atoi(getenv("RANDOM_SEED"))); 16 | 17 | char *trace_fname = getenv("TRACE"); 18 | 19 | if (argc <= 1) { 20 | printf("Usage: %s \n", argv[0]); 21 | return -1; 22 | } 23 | // read circuit 24 | char *fname = argv[1]; 25 | assert(fname); 26 | Circuit *C = load_circuit(fname); 27 | 28 | char plaintexts[64][16]; 29 | char ciphertexts[64][16]; 30 | int nread = 0; 31 | int eof = 0; 32 | while (1) { 33 | if (fread(plaintexts + nread, 1, 16, stdin) != 16) 34 | eof = 1; 35 | else 36 | nread++; 37 | 38 | if (nread == 64 || (eof && nread)) { 39 | fprintf(stderr, "computing batch %d\n", nread); 40 | circuit_compute(C, (unsigned char*)plaintexts, (unsigned char*)ciphertexts, trace_fname, nread); 41 | fwrite(ciphertexts, nread, 16, stdout); 42 | nread = 0; 43 | } 44 | if (eof) break; 45 | } 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/combine4daredevil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys 5 | import os 6 | 7 | n = int(sys.argv[1]) 8 | 9 | pts = [] 10 | cts = [] 11 | 12 | out_pt = open("traces/all.input", "w") 13 | out_ct = open("traces/all.output", "w") 14 | out_t = open("traces/all.bin", "w") 15 | 16 | nsamples = None 17 | 18 | for i in xrange(n): 19 | fpt = "traces/%04d.pt" % i 20 | fct = "traces/%04d.ct" % i 21 | ft = "traces/%04d.bin" % i 22 | 23 | with open(fpt) as f: pt = f.read(16) 24 | with open(fct) as f: ct = f.read(16) 25 | with open(ft) as f: trace = f.read() 26 | 27 | if nsamples is None: 28 | nsamples = len(trace) 29 | else: 30 | assert nsamples == len(trace) 31 | 32 | out_pt.write(pt) 33 | out_ct.write(ct) 34 | out_t.write(trace) 35 | 36 | print n, "traces" 37 | 38 | config = """ 39 | [Traces] 40 | files=1 41 | trace_type=i 42 | transpose=true 43 | index=0 44 | nsamples=%(nsamples)d 45 | trace=traces/all.bin %(n)d %(nsamples)d 46 | 47 | [Guesses] 48 | files=1 49 | guess_type=u 50 | transpose=true 51 | guess=traces/all.input %(n)d 16 52 | 53 | [General] 54 | threads=8 55 | order=1 56 | //window=0 57 | return_type=double 58 | algorithm=AES 59 | position=AES_AFTER_SBOX 60 | round=0 61 | bitnum=all 62 | bytenum=all 63 | //correct_key=??? 64 | memory=16G 65 | top=20 66 | """ % dict(nsamples=nsamples, n=n) 67 | 68 | with open("daredevil.config", "w") as f: 69 | f.write(config) 70 | -------------------------------------------------------------------------------- /synthesis/whitebox/whibox.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | """ 4 | Helper module for generating submission code right from the circuit. 5 | """ 6 | 7 | import sys 8 | 9 | from whitebox.serialize import RawSerializer, CompactRawSerializer 10 | from whitebox.templates import Template, encode_bytes 11 | 12 | def whibox_generate(ct, filename, comment=""): 13 | try: 14 | RS = CompactRawSerializer() 15 | header, opcodes = RS.serialize(ct) 16 | template = "whibox2019compact.c" 17 | kw = dict(op_bits=RS.op_bits) 18 | print >>sys.stderr, "Compact serialization succeeded!" 19 | except ValueError: 20 | print >>sys.stderr, "Compact serialize failed (too much ram usage), falling back to basic one." 21 | RS = RawSerializer() 22 | header, opcodes = RS.serialize(ct) 23 | template = "whibox2019.c" 24 | kw = dict() 25 | 26 | opcodes_data = "".join(opcodes) 27 | 28 | code = Template(template).subs( 29 | input_addr=RS.input_addr, 30 | output_addr=RS.output_addr, 31 | opcodes_encoded='"%s"' % encode_bytes(opcodes_data), 32 | ram_size=RS.ram_size, 33 | num_opcodes=len(opcodes), 34 | comment=comment, 35 | **kw 36 | ) 37 | with open(filename, "w") as f: 38 | f.write(code) 39 | print >>sys.stderr, "Source code %.2f MB, opcodes: %.2f MB" % (len(code) / 2.0**20, len(opcodes_data) / 2.0**20) 40 | print >>sys.stderr, "RAM: %d bits (bytes)" % RS.ram_size 41 | return RS, code 42 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/bitaes.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from whitebox.containers import Vector, Rect 4 | 5 | from .sbox import bitSbox 6 | from .linear import ShiftRow, MixColumn 7 | from .keyschedule import KS_round 8 | 9 | def BitAES(plaintext, key, rounds=10): 10 | bx = Vector(plaintext).split(16) 11 | bk = Vector(key).split(16) 12 | 13 | state = Rect(bx, w=4, h=4).transpose() 14 | kstate = Rect(bk, w=4, h=4).transpose() 15 | 16 | for rno in xrange(rounds): 17 | state = AK(state, kstate) 18 | state = SB(state) 19 | state = SR(state) 20 | if rno < rounds-1: 21 | state = MC(state) 22 | kstate = KS(kstate, rno) 23 | state = AK(state, kstate) 24 | 25 | state = state.transpose() 26 | kstate = kstate.transpose() 27 | bits = sum( map(list, state.flatten()), []) 28 | kbits = sum( map(list, kstate.flatten()), []) 29 | return bits, kbits 30 | 31 | def AK(state, kstate): 32 | return state.zipwith(lambda a, b: a ^ b, kstate) 33 | 34 | def SB(state, inverse=False): 35 | return state.apply(lambda v: Vector(bitSbox(v, inverse=inverse))) 36 | 37 | def SR(state, inverse=False): 38 | for y in xrange(4): 39 | state.apply_row(y, lambda row: ShiftRow(row, y, inverse=inverse)) 40 | return state 41 | 42 | def MC(state, inverse=False): 43 | for x in xrange(4): 44 | state.apply_col(x, lambda v: map(Vector, MixColumn(v))) 45 | return state 46 | 47 | def KS(kstate, rno): 48 | return KS_round(kstate, rno) 49 | 50 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/gates/bitaes.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from libwb.containers import Vector, Rect 4 | 5 | from libwb.gates.sbox import bitSbox 6 | from libwb.gates.linear import ShiftRow, MixColumn 7 | from libwb.gates.keyschedule import KS_round 8 | 9 | def AES(x, k, nr=10): 10 | bx = [Vector(x[i:i+8]) for i in xrange(0, len(x), 8)] 11 | bk = [Vector(k[i:i+8]) for i in xrange(0, len(k), 8)] 12 | 13 | state = Rect(bx, w=4, h=4).transpose() 14 | kstate = Rect(bk, w=4, h=4).transpose() 15 | 16 | for rno in xrange(nr): 17 | state = AK(state, kstate) 18 | state = SB(state) 19 | state = SR(state) 20 | if rno < 9: 21 | state = MC(state) 22 | pass 23 | kstate = KS(kstate, rno) 24 | state = AK(state, kstate) 25 | 26 | state = state.transpose() 27 | kstate = kstate.transpose() 28 | bits = sum( map(list, state.flatten()), []) 29 | kbits = sum( map(list, kstate.flatten()), []) 30 | return bits, kbits 31 | 32 | def AK(state, kstate): 33 | return state.zipwith(lambda a, b: a ^ b, kstate) 34 | 35 | def SB(state, inverse=False): 36 | return state.apply(lambda v: Vector(bitSbox(v, inverse=inverse))) 37 | 38 | def SR(state, inverse=False): 39 | for y in xrange(4): 40 | state.apply_row(y, lambda row: ShiftRow(row, y, inverse=inverse)) 41 | return state 42 | 43 | def MC(state, inverse=False): 44 | for x in xrange(4): 45 | state.apply_col(x, lambda v: map(Vector, MixColumn(v))) 46 | return state 47 | 48 | def KS(kstate, rno): 49 | return KS_round(kstate, rno) 50 | 51 | -------------------------------------------------------------------------------- /synthesis/attacks/AES_AFTER_SBOX: -------------------------------------------------------------------------------- 1 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 2 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 3 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 4 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 5 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 6 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 7 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 8 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 9 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 10 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 11 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 12 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 13 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 14 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 15 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 16 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 17 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/AES_AFTER_SBOX: -------------------------------------------------------------------------------- 1 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 2 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 3 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 4 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 5 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 6 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 7 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 8 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 9 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 10 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 11 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 12 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 13 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 14 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 15 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 16 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 17 | -------------------------------------------------------------------------------- /synthesis/whitebox/templates/whibox2019.c: -------------------------------------------------------------------------------- 1 | /* 2 | $comment 3 | */ 4 | typedef unsigned char W; 5 | typedef unsigned short A; 6 | typedef unsigned char B; 7 | 8 | enum OP {_, XOR, AND, OR, NOT, RANDOM}; 9 | 10 | A input_addr[] = {$input_addr}; 11 | A output_addr[] = {$output_addr}; 12 | B opcodes[] = $opcodes_encoded; 13 | W ram[$ram_size]; 14 | 15 | void AES_128_encrypt(char *ct, char *pt) { 16 | for (int i = 0; i < 128; i++) { 17 | ram[input_addr[i]] = (pt[i>>3]>>(7-i&7)) & 1; 18 | } 19 | 20 | #define pop() p+=sizeof(A) 21 | B *p = opcodes; 22 | for(int i = 0; i < $num_opcodes; i++) { 23 | B op = *p++; 24 | A dst = *((A *)p); pop(); 25 | A a, b; 26 | switch (op) { 27 | case XOR: 28 | a = *((A *)p); pop(); 29 | b = *((A *)p); pop(); 30 | ram[dst] = ram[a] ^ ram[b]; 31 | break; 32 | case AND: 33 | a = *((A *)p); pop(); 34 | b = *((A *)p); pop(); 35 | ram[dst] = ram[a] & ram[b]; 36 | break; 37 | case OR: 38 | a = *((A *)p); pop(); 39 | b = *((A *)p); pop(); 40 | ram[dst] = ram[a] | ram[b]; 41 | break; 42 | case NOT: 43 | a = *((A *)p); pop(); 44 | ram[dst] = 1^ram[a]; 45 | break; 46 | case RANDOM: 47 | ram[dst] = 0; // not useful for contest (or implement your rand and obfuscate...) 48 | break; 49 | default: 50 | return; // ouch? 51 | } 52 | } 53 | 54 | for (int i = 0; i < 128; i++) { 55 | if (!(i&7)) ct[i>>3] = 0; 56 | ct[i>>3] |= (ram[output_addr[i]]&1) << (7-i&7); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /synthesis/whitebox/templates/whibox2019compact.c: -------------------------------------------------------------------------------- 1 | /* 2 | $comment 3 | */ 4 | typedef unsigned char W; 5 | typedef unsigned short A; 6 | typedef unsigned char B; 7 | 8 | enum OP {XOR, AND, OR, NOT}; 9 | 10 | A input_addr[] = {$input_addr}; 11 | A output_addr[] = {$output_addr}; 12 | B opcodes[] = $opcodes_encoded; 13 | W ram[$ram_size]; 14 | 15 | void AES_128_encrypt(char *ct, char *pt) { 16 | for (int i = 0; i < 128; i++) { 17 | ram[input_addr[i]] = (pt[i>>3]>>(7-i&7)) & 1; 18 | } 19 | 20 | #define pop() p+=sizeof(A) 21 | B *p = opcodes; 22 | B op_mask = (1 << $op_bits) - 1; 23 | for(int i = 0; i < $num_opcodes; i++) { 24 | B op = *p++; 25 | A dst = *p++; 26 | 27 | // compact 28 | dst |= (op >> $op_bits) << 8; 29 | op &= op_mask; 30 | // --- 31 | 32 | A a, b; 33 | switch (op) { 34 | case XOR: 35 | a = *((A *)p); pop(); 36 | b = *((A *)p); pop(); 37 | ram[dst] = ram[a] ^ ram[b]; 38 | break; 39 | case AND: 40 | a = *((A *)p); pop(); 41 | b = *((A *)p); pop(); 42 | ram[dst] = ram[a] & ram[b]; 43 | break; 44 | case OR: 45 | a = *((A *)p); pop(); 46 | b = *((A *)p); pop(); 47 | ram[dst] = ram[a] | ram[b]; 48 | break; 49 | case NOT: 50 | a = *((A *)p); pop(); 51 | ram[dst] = 1^ram[a]; 52 | break; 53 | default: 54 | return; // ouch? 55 | } 56 | } 57 | 58 | for (int i = 0; i < 128; i++) { 59 | if (!(i&7)) ct[i>>3] = 0; 60 | ct[i>>3] |= (ram[output_addr[i]]&1) << (7-i&7); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /synthesis/attacks/combine4daredevil.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys 5 | import os 6 | 7 | n = int(sys.argv[1]) 8 | packed = True 9 | 10 | pts = [] 11 | cts = [] 12 | 13 | out_pt = open("./traces/all.input", "w") 14 | out_ct = open("./traces/all.output", "w") 15 | out_t = open("./traces/all.bin", "w") 16 | 17 | nsamples = None 18 | 19 | def expand_byte(b): 20 | res = [] 21 | b = ord(b) 22 | for i in xrange(8): 23 | res.append("\x00\x01"[(b >> (7 - i & 7)) & 1]) 24 | return "".join(res) 25 | 26 | for i in xrange(n): 27 | fpt = "./traces/%04d.pt" % i 28 | fct = "./traces/%04d.ct" % i 29 | ft = "./traces/%04d.bin" % i 30 | 31 | with open(fpt) as f: pt = f.read(16) 32 | with open(fct) as f: ct = f.read(16) 33 | with open(ft) as f: trace = f.read() 34 | if packed: 35 | trace = "".join(map(expand_byte, trace)) 36 | 37 | if nsamples is None: 38 | nsamples = len(trace) 39 | else: 40 | assert nsamples == len(trace) 41 | 42 | out_pt.write(pt) 43 | out_ct.write(ct) 44 | out_t.write(trace) 45 | 46 | print n, "traces" 47 | 48 | config = """ 49 | [Traces] 50 | files=1 51 | trace_type=i 52 | transpose=true 53 | index=0 54 | nsamples=%(nsamples)d 55 | trace=traces/all.bin %(n)d %(nsamples)d 56 | 57 | [Guesses] 58 | files=1 59 | guess_type=u 60 | transpose=true 61 | guess=traces/all.input %(n)d 16 62 | 63 | [General] 64 | threads=8 65 | order=1 66 | //window=0 67 | return_type=double 68 | algorithm=AES 69 | position=attacks/AES_AFTER_SBOX 70 | round=0 71 | bitnum=0 72 | bytenum=all 73 | //correct_key=??? 74 | memory=16G 75 | top=20 76 | """ % dict(nsamples=nsamples, n=n) 77 | 78 | with open("daredevil.config", "w") as f: 79 | f.write(config) 80 | -------------------------------------------------------------------------------- /synthesis/attacks/sbox.py: -------------------------------------------------------------------------------- 1 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 2 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 3 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 4 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 5 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 6 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 7 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 8 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 9 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 10 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 11 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 12 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 13 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 14 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 15 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 16 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 17 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 18 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 19 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 20 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 21 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 22 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 23 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 24 | 0x54, 0xbb, 0x16] 25 | 26 | rsbox = map(sbox.index, range(256)) 27 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/sbox.py: -------------------------------------------------------------------------------- 1 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 2 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 3 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 4 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 5 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 6 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 7 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 8 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 9 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 10 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 11 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 12 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 13 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 14 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 15 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 16 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 17 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 18 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 19 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 20 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 21 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 22 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 23 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 24 | 0x54, 0xbb, 0x16] 25 | 26 | rsbox = map(sbox.index, range(256)) 27 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/minimalist3.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | class Scheme(object): 4 | N = NotImplemented 5 | NR = NotImplemented 6 | NAME = NotImplemented 7 | 8 | def __str__(self): 9 | return "" % (self.NAME, self.N, self.NR) 10 | 11 | class Minimalist3(Scheme): 12 | N = 3 13 | NR = 3 14 | NAME = "minimalist3" 15 | 16 | def Decode(self, x): 17 | a, b, c = x 18 | return (a & b) ^ c 19 | 20 | def Refresh(self, x, y, z, rx, ry, rr): 21 | if 1: # secure 22 | xrr = x ^ rr 23 | yrr = y ^ rr 24 | rz = (rx & yrr) ^ (ry & xrr) ^ ((rx ^ rr) & (ry ^ rr)) ^ rr 25 | else: # insecure 26 | rz = (rx & y) ^ (ry & x) ^ (rx & ry) 27 | 28 | x ^= rx 29 | y ^= ry 30 | z ^= rz 31 | return x, y, z 32 | 33 | def EvalXOR(self, x, y, rx, ry): 34 | x = self.Refresh(*(x + rx)) 35 | y = self.Refresh(*(y + ry)) 36 | 37 | a, b, c = x 38 | d, e, f = y 39 | ae = a & e 40 | bd = b & d 41 | a_d = a ^ d 42 | b_e = b ^ e 43 | x = a_d 44 | y = b_e 45 | z = c ^ f ^ ae ^ bd 46 | return x, y, z 47 | 48 | def EvalAND(self, x, y, rx, ry): 49 | zero = rx[0] ^ rx[0] 50 | x = self.Refresh(*(x + rx)) 51 | y = self.Refresh(*(y + ry)) 52 | 53 | a, b, c = x 54 | d, e, f = y 55 | 56 | rf = ry[2] 57 | rc = rx[2] 58 | # rf = rc = zero # introduces another insecurity 59 | 60 | x = (a & e) ^ rf 61 | y = (b & d) ^ rc 62 | 63 | triple1 = ((c & e) ^ (b & rf)) & d 64 | triple2 = ((b & f) ^ (e & rc)) & a 65 | double = c & f 66 | fix = rf & rc 67 | 68 | z = triple1 ^ triple2 ^ double ^ fix 69 | return x, y, z 70 | -------------------------------------------------------------------------------- /synthesis/whitebox/templates/template.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import os, re, sys 4 | DIR = os.path.dirname(os.path.abspath(__file__)) 5 | 6 | class Template(object): 7 | show_warnings = True # t = Template(...); t.show_warnings = False to disable 8 | 9 | def __init__(self, name=None, filename=None, code=None): 10 | if name: 11 | filename = os.path.join(DIR, name) 12 | assert os.path.isfile(filename) 13 | 14 | if filename: 15 | self.code = open(filename).read() 16 | else: 17 | assert code 18 | self.code = code 19 | 20 | def subs(self, **repl): 21 | def rep(m): 22 | key = m.group(1) 23 | if key in repl: 24 | res = repl[key] 25 | if isinstance(res, tuple) or isinstance(res, list): 26 | return ",".join(map(str, res)) 27 | return str(repl[key]) 28 | if self.show_warnings: 29 | print >>sys.stderr, "WARNING: Template has unset variable %s" % m.group(0) 30 | return m.group(0) 31 | return re.sub(r"\$(\w+)\b", rep, self.code) 32 | 33 | 34 | def encode_bytes(s): 35 | """ 36 | this encoding includes raw symbols 37 | annoys many editors 38 | is very compact (can be improved by avoiding null bytes) 39 | idea by Vlad Roskov 40 | """ 41 | packed = [] 42 | for c in s: 43 | if c == "\x00": packed.append(r"\0") 44 | elif c == "\n": packed.append(r"\n") 45 | elif c == "\r": packed.append(r"\r") 46 | elif c == "\"": packed.append(r"\"") 47 | elif c == "\\": packed.append(r"\\") 48 | elif c == "\x1b": packed.append(r"\e") 49 | elif c == "?": packed.append(r"\?") 50 | elif c in "1234567890": packed.append(r"\%03o" % ord(c)) 51 | else: packed.append(c) 52 | return "".join(packed) 53 | -------------------------------------------------------------------------------- /synthesis/whitebox/tracing.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import os, sys 4 | 5 | 6 | def trace_split_batch(filename, make_output_filename=None, ntraces=64, packed=True): 7 | """Split batched trace into byte-packed independent traces 8 | Not very efficient now. 9 | """ 10 | if make_output_filename is None: 11 | make_output_filename = lambda i: filename + ".%02d" % i 12 | assert 0 <= ntraces <= 64 13 | sz = os.stat(filename).st_size 14 | bytes_per_node = (ntraces + 7) // 8 15 | 16 | assert sz % bytes_per_node == 0, "incorrect traces size (%d traces -> %d bytes per node * ? nodes = %d bytes trace file?)" % (ntraces, bytes_per_node, sz) 17 | num_nodes = sz / bytes_per_node 18 | 19 | traces = [0 for _ in xrange(ntraces)] 20 | fos = [open(make_output_filename(i), "wb") for i in xrange(ntraces)] 21 | bits = 0 22 | with open(filename, "rb") as f: 23 | for inode in xrange(num_nodes): 24 | block = bytearray(f.read(bytes_per_node)) 25 | for i in xrange(ntraces): 26 | bit = (block[i >> 3] >> (7 - i & 7)) & 1 27 | traces[i] = (traces[i] << 1) | bit 28 | 29 | bits += 1 30 | 31 | if packed: 32 | if inode == num_nodes - 1: 33 | for i in xrange(ntraces): 34 | traces[i] = traces[i] << (8 - bits) 35 | bits = 8 36 | 37 | if bits == 8: 38 | for i in xrange(ntraces): 39 | fos[i].write(chr(traces[i])) 40 | traces[i] = 0 41 | bits = 0 42 | else: 43 | for i in xrange(ntraces): 44 | fos[i].write(chr(traces[i])) 45 | traces[i] = 0 46 | bits = 0 47 | 48 | for fo in fos: 49 | fo.close() 50 | 51 | if __name__ == '__main__': 52 | trace_split_batch(sys.argv[1], ntraces=int(sys.argv[2])) 53 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/ops.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import operator 4 | 5 | class OP(object): 6 | name = {} 7 | symmetric = set() 8 | primitive = set() 9 | operators = {} 10 | neutral = {} 11 | c_op = {} 12 | 13 | def eval(self, op, args): 14 | assert op != self.INPUT 15 | if op not in self.operators: 16 | from pprint import pprint 17 | raise KeyError, (self.name[op], op) 18 | if len(args) > 1: 19 | return reduce(self.operators[op], args) 20 | elif len(args) == 1: 21 | return self.operators[op](args[0]) 22 | elif len(args) == 0: 23 | return self.operators[op]() 24 | else: 25 | assert False 26 | 27 | OP = OP() 28 | 29 | OPERATORS = dict( 30 | XOR=operator.xor, 31 | AND=operator.and_, 32 | OR=operator.or_, 33 | NXOR=lambda a,b: a^b^1, 34 | NAND=lambda a,b: a&b^1, 35 | NOR=lambda a,b: a|b^1, 36 | NOT=lambda a: a^1, 37 | ORNOT=lambda a: a|(b^1), 38 | ANDNOT=lambda a: a&(b^1), 39 | 40 | ZERO=lambda: 0, 41 | ONE=lambda: 1, 42 | 43 | RANDOM=None, 44 | INPUT=None, 45 | ) 46 | OPS_SYMMETRIC = "XOR AND OR NXOR NAND NOR".split() 47 | OPS_PRIMITIVE = "ONE ZERO INPUT".split() 48 | OPS_NEUTRAL = dict(XOR=0, AND=1, OR=0) 49 | OPS_C_OP = dict( 50 | XOR=lambda args: "^".join(args), 51 | AND=lambda args: "&".join(args), 52 | OR=lambda args: "|".join(args), 53 | NOT=lambda args: "~" + args[0], 54 | ) 55 | 56 | 57 | for opnum, name in enumerate(OPERATORS): 58 | setattr(OP, name, opnum) 59 | OP.name[opnum] = name 60 | OP.operators[opnum] = OPERATORS[name] 61 | 62 | for opname, val in OPS_NEUTRAL.items(): 63 | OP.neutral[getattr(OP, opname)] = val 64 | 65 | for opname, val in OPS_C_OP.items(): 66 | OP.c_op[getattr(OP, opname)] = val 67 | 68 | for opname in OPS_SYMMETRIC: 69 | OP.symmetric.add(getattr(OP, opname)) 70 | for opname in OPS_PRIMITIVE: 71 | OP.primitive.add(getattr(OP, opname)) 72 | 73 | 74 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/op.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import operator 4 | 5 | class OP(object): 6 | OPS = {} 7 | 8 | def __init__(self): 9 | self.free_id = 0 10 | 11 | self.name = {} 12 | self.arity = {} 13 | 14 | self.operator = {} 15 | self.symbol = {} 16 | self.symmetric = {} 17 | 18 | for name, data in self.OPS.items(): 19 | self.add_op(name, data) 20 | 21 | def add_op(self, name, data): 22 | name = name.upper() 23 | opnum = data.get("id", self.free_id) 24 | self.free_id = max(self.free_id, opnum + 1) 25 | 26 | setattr(self, name, opnum) 27 | for alias in data.get("aliases", ()): 28 | setattr(self, alias, opnum) 29 | 30 | self.name[opnum] = name 31 | self.arity[opnum] = int(data["arity"]) 32 | 33 | self.process_data(opnum, name, data) 34 | 35 | def process_data(self, opnum, name, data): 36 | self.operator[opnum] = data.get("operator", None) 37 | self.symbol[opnum] = data.get("symbol", None) 38 | self.symmetric[opnum] = data.get("symmetric", False) 39 | 40 | def eval(self, op, args): 41 | if not self.operator[op]: 42 | raise NotImplementedError("Operator %s can not be evaluated" % self.name[op]) 43 | return self.operator[op](*args) 44 | 45 | 46 | class BitOP(OP): 47 | OPS = dict( 48 | XOR=dict(operator=operator.xor, 49 | symbol="^", 50 | symmetric=True, 51 | arity=2), 52 | AND=dict(operator=operator.and_, 53 | symbol="&", 54 | symmetric=True, 55 | arity=2), 56 | OR =dict(operator=operator.or_, 57 | symbol="|", 58 | symmetric=True, 59 | arity=2), 60 | 61 | NOT=dict(operator=lambda a: ~a, 62 | symbol="~", 63 | arity=2), 64 | 65 | ZERO=dict(operator=lambda: 0, 66 | symbol="0", 67 | arity=0), 68 | ONE =dict(operator=lambda: 1, 69 | symbol="1", 70 | arity=0), 71 | 72 | INPUT =dict(operator=None, 73 | symbol="x", 74 | arity=0), 75 | OUTPUT=dict(operator=lambda a: a, 76 | symbol="@", 77 | arity=1), 78 | ) 79 | 80 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # White-box Analysis and Implementation Tools 2 | 3 | This repository contains various tools for (crypt)analysis and implementation of white-box designs. Currently, it has two major parts. 4 | 5 | 6 | ## [White-box Algebraic Security](./algebraic_security_AC2018) 7 | This folder contains proof-of-concept code for the paper 8 | 9 | [Attacks and Countermeasures for White-box Designs](https://eprint.iacr.org/2018/049) \ 10 | by Alex Biryukov and Aleksei Udovenko ([ASIACRYPT 2018](https://www.springer.com/gp/book/9783030033286)) 11 | 12 | The code is splitted into three parts: 13 | 14 | 1. **Implementation**: Proof-of-concept implementation of AES using the new nonlinear masking scheme. 15 | 1. **Verification**: Code for verifying algebraic security of gadgets. 16 | 1. **Attacks**: Several attacks from the paper. 17 | 18 | [Slides](./algebraic_security_AC2018/slides.pdf) from the ASIACRYPT presentation are available. 19 | 20 | [Paper](./algebraic_security_AC2018/WhiteBoxAttacksCountermeasures.pdf) is available. 21 | 22 | **Requirements**: Python2, [SageMath](http://www.sagemath.org/), [PyPy2](https://pypy.org/) (recommended) 23 | 24 | 25 | ``` 26 | @inproceedings{AC18BU, 27 | author = {Alex Biryukov and 28 | Aleksei Udovenko}, 29 | title = {Attacks and Countermeasures for White-box Designs}, 30 | booktitle = {{ASIACRYPT} {(2)}}, 31 | series = {Lecture Notes in Computer Science}, 32 | volume = {11273}, 33 | pages = {373--402}, 34 | publisher = {Springer}, 35 | year = {2018} 36 | } 37 | ``` 38 | 39 | ## [Synthesis Tools for White-box Implementations](./synthesis) 40 | 41 | This repository contains a framework for implementing and analysing circuit-based implementations. It was presented at the [WhibOx 2019](https://www.cryptoexperts.com/whibox2019/) workshop by Aleksei Udovenko. It is basically a separated and improved version of the implementation framework used in the [White-box Algebraic Security](./algebraic_security_AC2018) part. 42 | 43 | [Slides](./synthesis/slides.pdf) from the workshop are available. 44 | 45 | **Requirements**: Python2, [SageMath](http://www.sagemath.org/), [PyPy2](https://pypy.org/) (recommended). Python 3 support may be added soon. 46 | 47 | ``` 48 | @misc{WB2019U, 49 | author = {Aleksei Udovenko}, 50 | title = {Synthesis Tools for White-box Implementations}, 51 | howpublished = {WhibOx 2019. White-Box Cryptography and Obfuscation (2nd Edition)}, 52 | year = {2019} 53 | } 54 | ``` 55 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/node.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | 4 | class Node(object): 5 | COUNTER = 0 6 | OP = NotImplemented 7 | 8 | meta = {} 9 | 10 | def __init__(self, op, *args, **kwargs): 11 | self.op = op 12 | self.args = list(args) 13 | self.meta = self.meta.copy() 14 | self.meta.update(kwargs) 15 | 16 | self.id = self.COUNTER 17 | type(self).COUNTER += 1 18 | 19 | @classmethod 20 | def new(cls, *args, **kwargs): 21 | return cls(*args, **kwargs) 22 | 23 | def __iter__(self): 24 | for sub in self.args: 25 | if isinstance(sub, Node): 26 | yield sub 27 | 28 | def __str__(self): 29 | if self.op == self.OP.INPUT: return str(self.args[0]) 30 | if self.meta.get("fake-input"): return self.meta.get("fake-input") 31 | sym = str(self.OP.symbol[self.op]) 32 | 33 | if len(self.args) > 1: 34 | return "(" + (" " + sym + " ").join(map(str, self.args)) + ")" 35 | elif len(self.args) == 1: 36 | return sym + str(self.args[0]) 37 | elif len(self.args) == 0: 38 | return sym 39 | 40 | def __hash__(self): 41 | return hash(id(self)) 42 | 43 | def structure_hash(self): 44 | if self.__dict__.get("_sh", None) is None: 45 | self._sh = hash((self.op,) + tuple(hash(v) for v in self.args)) 46 | return self._sh 47 | 48 | def is_input(self): 49 | return self.op == self.OP.INPUT 50 | 51 | @classmethod 52 | def input(cls, name): 53 | return cls(cls.OP.INPUT, name) 54 | 55 | @classmethod 56 | def inputs(cls, name, n, tostr=True): 57 | return tuple(cls.input(name+str(i) if tostr else (name, i)) for i in xrange(n)) 58 | 59 | def name(self): 60 | assert self.is_input() 61 | return self.args[0] 62 | 63 | def flatten(self, out=None): 64 | if out is None: 65 | out = set() 66 | if self in out: 67 | return 68 | for sub in self.args: 69 | if isinstance(sub, Node): 70 | sub.flatten(out=out) 71 | out.add(self) 72 | return out 73 | 74 | @staticmethod 75 | def flatten_many(nodes): 76 | out = set() 77 | for node in nodes: 78 | node.flatten(out=out) 79 | return out 80 | 81 | def eval(self, acc): 82 | """acc is dict {bit: value} with initial values (typically input bits)""" 83 | if self not in acc: 84 | acc[self] = self.OP.eval(self.op, [v.eval(acc) for v in self.args]) 85 | return acc[self] 86 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/README.md: -------------------------------------------------------------------------------- 1 | # AES implementation 2 | 3 | This folder contains a bitwise **AES-128** implementation protected with the *minimalist quadratic masking* scheme from the paper and a first-order linear masking scheme. 4 | 5 | **It is a masking proof-of-concept and NOT a secure white-box implementation!** 6 | 7 | ### How to run: 8 | 9 | 1. Put an AES-128 key in the file `key` 10 | 2. Run `./generate.py` to generate the source code file `build/code.c` together with the opcode table `/build/opcodes.bin`. 11 | 3. Run `./buildrun.sh` to compile and test the implementation by encrypting the file `test_plaintext`. 12 | 13 | It is preferable to use PyPy for performance reasons. 14 | 15 | To manually run the compiled program set the `OPCODES` environment variable to the path to the opcode table file (e.g. `build/opcodes.bin`) and run the program with the plaintext in the standard input (stdin). 16 | 17 | ### Options: 18 | 19 | 1. Set the `TRACE` environment variable to the path where all computed values should be recorded. This allows to produce computational traces for DCA attacks. 20 | 1. Set `DISABLE_NONLINEAR_MASKING` environment variable to 1 (during **compilation**). Then only the first-order linear masking is used. 21 | 1. Set `DISABLE_RANDOM` environment variable to 1 (during **execution**). Then the randomness is replaced by constants. 22 | 23 | ### Example 24 | 25 | ``` 26 | $ DISABLE_NONLINEAR_MASKING=0 pypy generate.py 27 | Building circuit for 10 rounds of AES 28 | :: circuit walk 29 | :: ordering 31783 nodes 30 | :: code size: 63343 operations 31 | Masking 63343 events 32 | [L]inear masking is ENABLED 33 | [N]onlinear masking is ENABLED 34 | Used 409664 random bits 35 | Generating final code 36 | :: circuit walk 37 | :: ordering 2588871 nodes 38 | :: code size: 5177486 operations 39 | Maximum state size: 926 bits 40 | Memory usage: 2^10 = 1024 bytes 41 | Opcode data size: 16.47 MB 42 | Num opcodes: 2588743 of which random: 409664 43 | (Time: ~15 seconds) 44 | ========== 45 | $ ./buildrun.sh 46 | Encrypting... 47 | 48 | real 0m0.193s 49 | user 0m0.185s 50 | sys 0m0.008s 51 | 52 | Ciphertext: 53 | 00000000: 47e8 c363 429a 5f3a dec2 12ed 3b38 2157 G..cB._:....;8!W 54 | 00000010: 29cc c6d3 dc02 fd64 1244 e00b 2d53 1a86 )......d.D..-S.. 55 | 00000020: 9a62 043a c28d 2f79 0091 1395 9458 0d13 .b.:../y.....X.. 56 | 00000030: a65f e8ef b243 4c67 fe8c 0864 851e e622 ._...CLg...d..." 57 | 00000040: b356 1bf9 6f9c 0fd9 7217 8305 e4a5 ca77 .V..o...r......w 58 | 00000050: cec6 074e 7bb9 e27d 2699 a8fd 8798 9f0a ...N{..}&....... 59 | 00000060: 6eed 52fc 79ec 82cb e0eb c5a1 1789 4a2d n.R.y.........J- 60 | 00000070: c608 e021 222b a499 613a cb46 d934 bb33 ...!"+..a:.F.4.3 61 | 62 | Difference: 63 | ========== 64 | ``` 65 | -------------------------------------------------------------------------------- /synthesis/examples/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | from random import choice 5 | 6 | from struct import pack 7 | 8 | # preserve the circuit completely, 9 | # from whitebox.tree.node import BitNode as Bit 10 | # or do some trivial optimizations? 11 | from whitebox.tree.node import OptBitNode as Bit 12 | 13 | from whitebox.orderer import Orderer, circuit_filter_op 14 | from whitebox.utils import str2bin 15 | from whitebox.masking import * 16 | 17 | from whitebox.prng import LFSR 18 | 19 | from whitebox.ciphers.AES import BitAES 20 | 21 | NR = 2 22 | print NR, "rounds" 23 | 24 | KEY = "ABCDxyzwUIOPvbnm" 25 | KEY_BITS = Bit.consts(str2bin(KEY)) 26 | 27 | def check(ct): 28 | co = Orderer(ct).compile() 29 | print "rand bits:", len(list(circuit_filter_op(ct, Bit.OP.RANDOM))) 30 | 31 | pt = Bit.inputs("pt", 128) 32 | ct, k10 = BitAES(pt, KEY_BITS, rounds=NR) 33 | check(ct) 34 | 35 | if 1: 36 | # pseudorandomness (via a small pool for efficiency) 37 | from whitebox.prng import LFSR, Pool 38 | rand = Pool(n=1000, prng=LFSR(taps=[0, 2, 5, 18, 39, 100, 127], state=pt)).step 39 | else: 40 | # simulated randomness 41 | rand = lambda: Bit(Bit.OP.RANDOM) 42 | 43 | minq = MINQ(rand=rand) 44 | lin3 = DOM(rand=rand, nshares=2) 45 | 46 | for scheme in (minq, lin3): 47 | # for scheme in (lin3,): 48 | print scheme 49 | # ct = mask_circuit(ct, scheme) 50 | # check(ct) 51 | print 52 | 53 | print "Generating final code" 54 | code = [] 55 | co = Orderer(ct).compile() 56 | 57 | from whitebox.serialize import RawSerializer 58 | 59 | RS = RawSerializer() 60 | header, opcodes = RS.serialize(co) 61 | print "Memory usage:", RS.ram_size 62 | 63 | # a) for local testing, tracing, analysis, etc. 64 | header_data = "".join(header) 65 | opcodes_data = "".join(opcodes) 66 | with open("circuits/test.bin", "w") as f: 67 | f.write(header_data) 68 | f.write(opcodes_data) 69 | 70 | # b) for whibox submission 71 | from whitebox.templates import Template, encode_bytes 72 | code = Template("whibox2019.c").subs( 73 | input_addr=RS.input_addr, 74 | output_addr=RS.output_addr, 75 | opcodes_encoded='"%s"' % encode_bytes(opcodes_data), 76 | ram_size=RS.ram_size, 77 | num_opcodes=len(opcodes), 78 | ) 79 | with open("build/submit.c", "w") as f: 80 | f.write(code) 81 | 82 | # some stats 83 | print "Opcodes size: %.2f megabytes" % (len(opcodes_data) / 2.0**20), 84 | print ";", len(opcodes), "operations" 85 | # print "Encoded opcodes: %.2f megabytes" % (len(opcodes_encoded) / 2.0**20) 86 | print "Total submission size: %.2f megabytes" % (len(code) / 2.0**20) 87 | 88 | # generate reference ciphertexts for validation 89 | from AES.aes import encrypt 90 | pt = open("plain").read() 91 | ct = "".join(encrypt(pt[i:i+16], KEY, nr=NR) for i in xrange(0, len(pt), 16)) 92 | open("cipher", "w").write(ct) 93 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/build/template.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | FILE *ftrace = NULL; 8 | 9 | typedef unsigned char WORD; 10 | 11 | WORD m[1<<$PLACE_MEMLOG]; 12 | int ct_pos[128] = {$CT_POSES}; 13 | 14 | int num_opcodes = $NUM_OPCODES; 15 | 16 | unsigned char opcodes[$OPCODES_SIZE] = {}; 17 | 18 | unsigned char randbit() { 19 | return rand() & 1; 20 | } 21 | 22 | void compute() { 23 | unsigned char *p = (void*)opcodes; 24 | for(int i = 0; i < num_opcodes; i++) { 25 | unsigned char op = *p++; 26 | // printf("%d: %d\n", i, op); 27 | unsigned short dst = *((unsigned short *)p); p+=2; 28 | if (op == 0) { 29 | unsigned short a = *((unsigned short *)p); p+=2; 30 | unsigned short b = *((unsigned short *)p); p+=2; 31 | m[dst] = m[a] ^ m[b]; 32 | } 33 | else if (op == 1) { 34 | unsigned short a = *((unsigned short *)p); p+=2; 35 | unsigned short b = *((unsigned short *)p); p+=2; 36 | m[dst] = m[a] & m[b]; 37 | } 38 | else if (op == 2) { 39 | unsigned short a = *((unsigned short *)p); p+=2; 40 | m[dst] = ((WORD)-1) ^ m[a]; 41 | } 42 | else if (op == 3) { 43 | m[dst] = randbit(); 44 | } 45 | else { 46 | printf("unknown opcode %d\n", op); 47 | exit(0); 48 | } 49 | 50 | if (ftrace) { 51 | m[dst] &= 1; 52 | fwrite(m+dst, 1, 1, ftrace); 53 | } 54 | } 55 | } 56 | 57 | void AES_128_encrypt(char*ct, char*pt) { 58 | for(int i = 0; i < 128; i++) { 59 | m[i] = (pt[i/8] >> (7 - i % 8)) & 1; 60 | } 61 | 62 | compute(); 63 | 64 | for(int i = 0; i < 16; i++) { 65 | ct[i] = 0; 66 | } 67 | for(int i = 0; i < 128; i++) { 68 | ct[i/8] |= (m[ct_pos[i]]&1) << (7 - i % 8); 69 | } 70 | return; 71 | } 72 | 73 | int main() { 74 | int tmp; 75 | 76 | // CSPRNG?.. 77 | srand(time(NULL)); 78 | srand(rand() ^ getpid()); 79 | 80 | if (getenv("DISABLE_RANDOM") && atoi(getenv("DISABLE_RANDOM"))) 81 | srand(4294967291); 82 | 83 | char *trace_fname = getenv("TRACE"); 84 | if (trace_fname) { 85 | ftrace = fopen(trace_fname, "w"); 86 | assert(ftrace); 87 | } 88 | 89 | char *opcodes_fname = getenv("OPCODES"); 90 | assert(opcodes_fname); 91 | FILE * fd = fopen(opcodes_fname, "r"); 92 | assert(fd); 93 | assert(sizeof(opcodes) == fread(opcodes, 1, sizeof(opcodes), fd)); 94 | fclose(fd); 95 | 96 | char plaintext[16]; 97 | char ciphertext[16]; 98 | while (fread(plaintext, 1, 16, stdin) == 16) { 99 | AES_128_encrypt(ciphertext, plaintext); 100 | fwrite(ciphertext, 1, 16, stdout); 101 | } 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/op.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import sys 4 | import operator 5 | from random import randint 6 | 7 | class OP(object): 8 | OPS = {} 9 | 10 | def __init__(self): 11 | self.free_id = 10 # to avoid bugs, e.g. Bit(0) will raise error (for const need to use Bit.const(0)) 12 | 13 | self.name = {} 14 | self.arity = {} 15 | 16 | self.operator = {} 17 | self.symbol = {} 18 | self.symmetric = {} 19 | 20 | for name, data in self.OPS: 21 | self.add_op(name, data) 22 | 23 | def add_op(self, name, data): 24 | name = name.upper() 25 | opnum = data.get("id", self.free_id) 26 | self.free_id = max(self.free_id, opnum + 1) 27 | 28 | setattr(self, name, opnum) 29 | for alias in data.get("aliases", ()): 30 | setattr(self, alias, opnum) 31 | 32 | self.name[opnum] = name 33 | self.arity[opnum] = int(data["arity"]) 34 | 35 | self.process_data(opnum, name, data) 36 | 37 | def process_data(self, opnum, name, data): 38 | self.operator[opnum] = data.get("operator", None) 39 | self.symbol[opnum] = data.get("symbol", None) 40 | self.symmetric[opnum] = data.get("symmetric", False) 41 | 42 | def eval(self, op, args): 43 | if not self.operator[op]: 44 | raise NotImplementedError("Operator %s can not be evaluated" % self.name[op]) 45 | return self.operator[op](*args) 46 | 47 | def __contains__(self, op): 48 | return op in self.name 49 | 50 | def dump(self, file=sys.stderr): 51 | for opnum, name in sorted(self.name.items()): 52 | print >>file, "%2d: %s" % (opnum, name) 53 | 54 | class BitOP(OP): 55 | OPS = ( 56 | ("XOR", dict(operator=operator.xor, 57 | symbol="^", 58 | symmetric=True, 59 | arity=2)), 60 | ("AND", dict(operator=operator.and_, 61 | symbol="&", 62 | symmetric=True, 63 | arity=2)), 64 | ("OR", dict(operator=operator.or_, 65 | symbol="|", 66 | symmetric=True, 67 | arity=2)), 68 | 69 | ("NOT", dict(operator=lambda a: 1^a, 70 | symbol="~", 71 | arity=1)), 72 | 73 | ("ZERO", dict(operator=lambda: 0, 74 | symbol="0", 75 | arity=0)), 76 | ("ONE", dict(operator=lambda: 1, 77 | symbol="1", 78 | arity=0)), 79 | 80 | # special nodes / ops 81 | ("INPUT", dict(operator=None, 82 | symbol="i", 83 | arity=0)), 84 | ("OUTPUT", dict(operator=lambda a: a, 85 | symbol="o", 86 | arity=1)), 87 | ("FREE", dict(operator=None, 88 | symbol="f", 89 | arity=1)), 90 | 91 | ("RANDOM", dict(operator=lambda: randint(0, 1), 92 | symbol="$", 93 | arity=0)), 94 | ) 95 | 96 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/keyschedule.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from whitebox.containers import Vector 4 | 5 | from .sbox import bitSbox 6 | 7 | Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 8 | 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 9 | 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 10 | 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 11 | 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 12 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 13 | 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 14 | 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 15 | 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 16 | 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 17 | 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 18 | 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 19 | 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 20 | 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 21 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 22 | 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 23 | 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 24 | 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 25 | 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 26 | 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 27 | 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 28 | 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 29 | 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 30 | 0xe8, 0xcb ] 31 | 32 | def tobin(x, n): 33 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 34 | 35 | def c8(c): 36 | return Vector(tobin(c, 8)) 37 | 38 | BitRcon = map(c8, Rcon) 39 | 40 | def ks_rotate(word): 41 | return word[1:] + word[:1] 42 | 43 | def ks_core(word, iteration): 44 | word = word.rol(1) 45 | word = word.map(lambda b: Vector(bitSbox(b))) 46 | word = word.set(0, word[0] ^ BitRcon[iteration]) 47 | return word 48 | 49 | def KS_round(kstate, rno): 50 | t = ks_core(kstate.col(3), rno+1) 51 | kstate.apply_col(0, lambda c: c ^ t) 52 | t = kstate.col(0) 53 | kstate.apply_col(1, lambda c: c ^ t) 54 | t = kstate.col(1) 55 | kstate.apply_col(2, lambda c: c ^ t) 56 | t = kstate.col(2) 57 | kstate.apply_col(3, lambda c: c ^ t) 58 | return kstate 59 | 60 | def KS_unround(kstate, rno): 61 | t = kstate.col(2) 62 | kstate.apply_col(3, lambda c: c ^ t) 63 | t = kstate.col(1) 64 | kstate.apply_col(2, lambda c: c ^ t) 65 | t = kstate.col(0) 66 | kstate.apply_col(1, lambda c: c ^ t) 67 | 68 | t = ks_core(kstate.col(3), rno+1) 69 | kstate.apply_col(0, lambda c: c ^ t) 70 | return kstate 71 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/linear.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def ShiftRow(row, nr, inverse=False): 4 | if inverse: 5 | nr = -nr 6 | off = nr % 4 7 | return row[off:] + row[:off] 8 | 9 | def MixColumn(col, inverse=False): 10 | res = [[0] * 8 for _ in xrange(4)] 11 | table = MCi_TABLE if inverse else MC_TABLE 12 | for yi in xrange(4): 13 | for yj in xrange(8): 14 | y = yi * 8 + yj 15 | for x in table[y]: 16 | xi, xj = divmod(x, 8) 17 | res[yi][yj] ^= col[xi][xj] 18 | return res 19 | 20 | # y -> set of x indices to xor 21 | MC_TABLE = [{1, 8, 9, 16, 24}, {2, 9, 10, 17, 25}, {3, 10, 11, 18, 26}, {0, 4, 8, 11, 12, 19, 27}, {0, 5, 8, 12, 13, 20, 28}, {6, 13, 14, 21, 29}, {0, 7, 8, 14, 15, 22, 30}, {0, 8, 15, 23, 31}, {0, 9, 16, 17, 24}, {1, 10, 17, 18, 25}, {2, 11, 18, 19, 26}, {3, 8, 12, 16, 19, 20, 27}, {4, 8, 13, 16, 20, 21, 28}, {5, 14, 21, 22, 29}, {6, 8, 15, 16, 22, 23, 30}, {7, 8, 16, 23, 31}, {0, 8, 17, 24, 25}, {1, 9, 18, 25, 26}, {2, 10, 19, 26, 27}, {3, 11, 16, 20, 24, 27, 28}, {4, 12, 16, 21, 24, 28, 29}, {5, 13, 22, 29, 30}, {6, 14, 16, 23, 24, 30, 31}, {7, 15, 16, 24, 31}, {0, 1, 8, 16, 25}, {1, 2, 9, 17, 26}, {2, 3, 10, 18, 27}, {0, 3, 4, 11, 19, 24, 28}, {0, 4, 5, 12, 20, 24, 29}, {5, 6, 13, 21, 30}, {0, 6, 7, 14, 22, 24, 31}, {0, 7, 15, 23, 24}] 22 | MCi_TABLE = [{1, 2, 3, 8, 9, 11, 16, 18, 19, 24, 27}, {0, 2, 3, 4, 8, 9, 10, 12, 16, 17, 19, 20, 24, 25, 28}, {1, 3, 4, 5, 8, 9, 10, 11, 13, 17, 18, 20, 21, 24, 25, 26, 29}, {2, 4, 5, 6, 8, 9, 10, 11, 12, 14, 16, 18, 19, 21, 22, 25, 26, 27, 30}, {1, 2, 5, 6, 7, 10, 12, 13, 15, 16, 17, 18, 20, 22, 23, 24, 26, 28, 31}, {1, 6, 7, 8, 9, 13, 14, 17, 21, 23, 24, 25, 29}, {2, 7, 8, 9, 10, 14, 15, 16, 18, 22, 25, 26, 30}, {0, 1, 2, 8, 10, 15, 17, 18, 23, 26, 31}, {0, 3, 9, 10, 11, 16, 17, 19, 24, 26, 27}, {0, 1, 4, 8, 10, 11, 12, 16, 17, 18, 20, 24, 25, 27, 28}, {0, 1, 2, 5, 9, 11, 12, 13, 16, 17, 18, 19, 21, 25, 26, 28, 29}, {1, 2, 3, 6, 10, 12, 13, 14, 16, 17, 18, 19, 20, 22, 24, 26, 27, 29, 30}, {0, 2, 4, 7, 9, 10, 13, 14, 15, 18, 20, 21, 23, 24, 25, 26, 28, 30, 31}, {0, 1, 5, 9, 14, 15, 16, 17, 21, 22, 25, 29, 31}, {1, 2, 6, 10, 15, 16, 17, 18, 22, 23, 24, 26, 30}, {2, 7, 8, 9, 10, 16, 18, 23, 25, 26, 31}, {0, 2, 3, 8, 11, 17, 18, 19, 24, 25, 27}, {0, 1, 3, 4, 8, 9, 12, 16, 18, 19, 20, 24, 25, 26, 28}, {1, 2, 4, 5, 8, 9, 10, 13, 17, 19, 20, 21, 24, 25, 26, 27, 29}, {0, 2, 3, 5, 6, 9, 10, 11, 14, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30}, {0, 1, 2, 4, 6, 7, 8, 10, 12, 15, 17, 18, 21, 22, 23, 26, 28, 29, 31}, {1, 5, 7, 8, 9, 13, 17, 22, 23, 24, 25, 29, 30}, {0, 2, 6, 9, 10, 14, 18, 23, 24, 25, 26, 30, 31}, {1, 2, 7, 10, 15, 16, 17, 18, 24, 26, 31}, {0, 1, 3, 8, 10, 11, 16, 19, 25, 26, 27}, {0, 1, 2, 4, 8, 9, 11, 12, 16, 17, 20, 24, 26, 27, 28}, {0, 1, 2, 3, 5, 9, 10, 12, 13, 16, 17, 18, 21, 25, 27, 28, 29}, {0, 1, 2, 3, 4, 6, 8, 10, 11, 13, 14, 17, 18, 19, 22, 26, 28, 29, 30}, {2, 4, 5, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 23, 25, 26, 29, 30, 31}, {0, 1, 5, 6, 9, 13, 15, 16, 17, 21, 25, 30, 31}, {0, 1, 2, 6, 7, 8, 10, 14, 17, 18, 22, 26, 31}, {0, 2, 7, 9, 10, 15, 18, 23, 24, 25, 26}] 23 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/gates/keyschedule.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from libwb.containers import Vector 4 | from libwb.gates.sbox import bitSbox 5 | 6 | Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 7 | 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 8 | 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 9 | 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 10 | 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 11 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 12 | 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 13 | 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 14 | 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 15 | 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 16 | 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 17 | 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 18 | 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 19 | 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 20 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 21 | 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 22 | 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 23 | 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 24 | 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 25 | 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 26 | 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 27 | 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 28 | 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 29 | 0xe8, 0xcb ] 30 | 31 | def tobin(x, n): 32 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 33 | 34 | def c8(c): 35 | return Vector(tobin(c, 8)) 36 | 37 | BitRcon = map(c8, Rcon) 38 | 39 | def ks_rotate(word): 40 | return word[1:] + word[:1] 41 | 42 | def ks_core(word, iteration): 43 | word = word.rol(1) 44 | word = word.map(lambda b: Vector(bitSbox(b))) 45 | word = word.set(0, word[0] ^ BitRcon[iteration]) 46 | return word 47 | 48 | def KS_round(kstate, rno): 49 | t = ks_core(kstate.col(3), rno+1) 50 | kstate.apply_col(0, lambda c: c ^ t) 51 | t = kstate.col(0) 52 | kstate.apply_col(1, lambda c: c ^ t) 53 | t = kstate.col(1) 54 | kstate.apply_col(2, lambda c: c ^ t) 55 | t = kstate.col(2) 56 | kstate.apply_col(3, lambda c: c ^ t) 57 | return kstate 58 | 59 | def KS_unround(kstate, rno): 60 | t = kstate.col(2) 61 | kstate.apply_col(3, lambda c: c ^ t) 62 | t = kstate.col(1) 63 | kstate.apply_col(2, lambda c: c ^ t) 64 | t = kstate.col(0) 65 | kstate.apply_col(1, lambda c: c ^ t) 66 | 67 | t = ks_core(kstate.col(3), rno+1) 68 | kstate.apply_col(0, lambda c: c ^ t) 69 | return kstate 70 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/gates/linear.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | def ShiftRow(row, nr, inverse=False): 4 | if inverse: 5 | nr = -nr 6 | off = nr % 4 7 | return row[off:] + row[:off] 8 | 9 | def MixColumn(col, inverse=False): 10 | res = [[0] * 8 for _ in xrange(4)] 11 | table = MCi_TABLE if inverse else MC_TABLE 12 | for yi in xrange(4): 13 | for yj in xrange(8): 14 | y = yi * 8 + yj 15 | for x in table[y]: 16 | xi, xj = divmod(x, 8) 17 | res[yi][yj] ^= col[xi][xj] 18 | return res 19 | 20 | # y -> set of x indices to xor 21 | MC_TABLE = [{1, 8, 9, 16, 24}, {2, 9, 10, 17, 25}, {3, 10, 11, 18, 26}, {0, 4, 8, 11, 12, 19, 27}, {0, 5, 8, 12, 13, 20, 28}, {6, 13, 14, 21, 29}, {0, 7, 8, 14, 15, 22, 30}, {0, 8, 15, 23, 31}, {0, 9, 16, 17, 24}, {1, 10, 17, 18, 25}, {2, 11, 18, 19, 26}, {3, 8, 12, 16, 19, 20, 27}, {4, 8, 13, 16, 20, 21, 28}, {5, 14, 21, 22, 29}, {6, 8, 15, 16, 22, 23, 30}, {7, 8, 16, 23, 31}, {0, 8, 17, 24, 25}, {1, 9, 18, 25, 26}, {2, 10, 19, 26, 27}, {3, 11, 16, 20, 24, 27, 28}, {4, 12, 16, 21, 24, 28, 29}, {5, 13, 22, 29, 30}, {6, 14, 16, 23, 24, 30, 31}, {7, 15, 16, 24, 31}, {0, 1, 8, 16, 25}, {1, 2, 9, 17, 26}, {2, 3, 10, 18, 27}, {0, 3, 4, 11, 19, 24, 28}, {0, 4, 5, 12, 20, 24, 29}, {5, 6, 13, 21, 30}, {0, 6, 7, 14, 22, 24, 31}, {0, 7, 15, 23, 24}] 22 | MCi_TABLE = [{1, 2, 3, 8, 9, 11, 16, 18, 19, 24, 27}, {0, 2, 3, 4, 8, 9, 10, 12, 16, 17, 19, 20, 24, 25, 28}, {1, 3, 4, 5, 8, 9, 10, 11, 13, 17, 18, 20, 21, 24, 25, 26, 29}, {2, 4, 5, 6, 8, 9, 10, 11, 12, 14, 16, 18, 19, 21, 22, 25, 26, 27, 30}, {1, 2, 5, 6, 7, 10, 12, 13, 15, 16, 17, 18, 20, 22, 23, 24, 26, 28, 31}, {1, 6, 7, 8, 9, 13, 14, 17, 21, 23, 24, 25, 29}, {2, 7, 8, 9, 10, 14, 15, 16, 18, 22, 25, 26, 30}, {0, 1, 2, 8, 10, 15, 17, 18, 23, 26, 31}, {0, 3, 9, 10, 11, 16, 17, 19, 24, 26, 27}, {0, 1, 4, 8, 10, 11, 12, 16, 17, 18, 20, 24, 25, 27, 28}, {0, 1, 2, 5, 9, 11, 12, 13, 16, 17, 18, 19, 21, 25, 26, 28, 29}, {1, 2, 3, 6, 10, 12, 13, 14, 16, 17, 18, 19, 20, 22, 24, 26, 27, 29, 30}, {0, 2, 4, 7, 9, 10, 13, 14, 15, 18, 20, 21, 23, 24, 25, 26, 28, 30, 31}, {0, 1, 5, 9, 14, 15, 16, 17, 21, 22, 25, 29, 31}, {1, 2, 6, 10, 15, 16, 17, 18, 22, 23, 24, 26, 30}, {2, 7, 8, 9, 10, 16, 18, 23, 25, 26, 31}, {0, 2, 3, 8, 11, 17, 18, 19, 24, 25, 27}, {0, 1, 3, 4, 8, 9, 12, 16, 18, 19, 20, 24, 25, 26, 28}, {1, 2, 4, 5, 8, 9, 10, 13, 17, 19, 20, 21, 24, 25, 26, 27, 29}, {0, 2, 3, 5, 6, 9, 10, 11, 14, 18, 20, 21, 22, 24, 25, 26, 27, 28, 30}, {0, 1, 2, 4, 6, 7, 8, 10, 12, 15, 17, 18, 21, 22, 23, 26, 28, 29, 31}, {1, 5, 7, 8, 9, 13, 17, 22, 23, 24, 25, 29, 30}, {0, 2, 6, 9, 10, 14, 18, 23, 24, 25, 26, 30, 31}, {1, 2, 7, 10, 15, 16, 17, 18, 24, 26, 31}, {0, 1, 3, 8, 10, 11, 16, 19, 25, 26, 27}, {0, 1, 2, 4, 8, 9, 11, 12, 16, 17, 20, 24, 26, 27, 28}, {0, 1, 2, 3, 5, 9, 10, 12, 13, 16, 17, 18, 21, 25, 27, 28, 29}, {0, 1, 2, 3, 4, 6, 8, 10, 11, 13, 14, 17, 18, 19, 22, 26, 28, 29, 30}, {2, 4, 5, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 23, 25, 26, 29, 30, 31}, {0, 1, 5, 6, 9, 13, 15, 16, 17, 21, 25, 30, 31}, {0, 1, 2, 6, 7, 8, 10, 14, 17, 18, 22, 26, 31}, {0, 2, 7, 9, 10, 15, 18, 23, 24, 25, 26}] 23 | -------------------------------------------------------------------------------- /synthesis/whitebox/containers/rect.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from .vector import Vector 4 | 5 | 6 | class Rect(object): 7 | def __init__(self, vec, h=None, w=None): 8 | assert h or w 9 | if h: 10 | w = len(vec) // h 11 | elif w: 12 | h = len(vec) // w 13 | assert w * h == len(vec) 14 | self.w, self.h = w, h 15 | 16 | self.lst = [] 17 | for i in xrange(0, len(vec), w): 18 | self.lst.append(list(vec[i:i+w])) 19 | 20 | @classmethod 21 | def from_rect(cls, rect): 22 | self = object.__new__(cls) 23 | self.lst = rect 24 | self.h = len(rect) 25 | self.w = len(rect[0]) 26 | return self 27 | 28 | def __getitem__(self, pos): 29 | y, x = pos 30 | return self.lst[y][x] 31 | 32 | def __setitem__(self, pos, val): 33 | y, x = pos 34 | self.lst[y][x] = val 35 | 36 | def row(self, i): 37 | return Vector(self.lst[i]) 38 | 39 | def col(self, i): 40 | return Vector(self.lst[y][i] for y in xrange(self.h)) 41 | 42 | def diag(self, x): 43 | assert self.w == self.h 44 | return Vector(self.lst[i][(x+i) % self.w] for i in xrange(self.h)) 45 | 46 | def set_row(self, y, vec): 47 | for x in xrange(self.w): 48 | self.lst[y][x] = vec[x] 49 | return self 50 | 51 | def set_col(self, x, vec): 52 | for y in xrange(self.h): 53 | self.lst[y][x] = vec[y] 54 | return self 55 | 56 | def set_diag(self, x, vec): 57 | assert self.w == self.h 58 | for i in xrange(self.h): 59 | self.lst[i][(x+i) % self.w] = vec[i] 60 | return self 61 | 62 | def apply(self, f, with_coord=False): 63 | for y in xrange(self.h): 64 | if with_coord: 65 | self.lst[y] = [f(y, x, v) for x, v in enumerate(self.lst[y])] 66 | else: 67 | self.lst[y] = map(f, self.lst[y]) 68 | return self 69 | 70 | def apply_row(self, x, func): 71 | return self.set_row(x, func(self.row(x))) 72 | 73 | def apply_col(self, x, func): 74 | return self.set_col(x, func(self.col(x))) 75 | 76 | def apply_diag(self, x, func): 77 | assert self.w == self.h 78 | return self.set_diag(x, func(self.diag(x))) 79 | 80 | def flatten(self): 81 | lst = [] 82 | for v in self.lst: 83 | lst += v 84 | return Vector(lst) 85 | 86 | def zipwith(self, f, other): 87 | assert isinstance(other, Rect) 88 | assert self.h == other.h 89 | assert self.w == other.w 90 | return Rect( 91 | [f(a, b) for a, b in zip(self.flatten(), other.flatten())], 92 | h=self.h, w=self.w 93 | ) 94 | 95 | def transpose(self): 96 | rect = [[self.lst[y][x] for y in xrange(self.h)] for x in xrange(self.w)] 97 | return Rect.from_rect(rect=rect) 98 | 99 | def __repr__(self): 100 | return "" % (self.h, self.w) 101 | -------------------------------------------------------------------------------- /synthesis/whitebox/fastcircuit.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import ctypes, os 4 | from ctypes import * 5 | 6 | path = os.path.join(os.path.abspath(os.path.dirname(__file__)), "libfastcircuit.so") 7 | lib = ctypes.cdll.LoadLibrary(path) 8 | 9 | lib.load_circuit.restype = c_void_p 10 | 11 | lib.circuit_compute.argtypes = (c_void_p, c_char_p, c_char_p, c_char_p, c_int) 12 | 13 | lib.set_seed.argtypes = c_uint64, 14 | 15 | # lib.RANDOM_ENABLED 16 | 17 | def set_seed(seed=None): 18 | if seed is None: # use nanoseconds 19 | lib.set_seed_time() 20 | else: 21 | lib.set_seed(seed) 22 | 23 | def randomness(on): 24 | if on: 25 | lib.RANDOM_ENABLED = 1 26 | else: 27 | lib.RANDOM_ENABLED = 0 28 | 29 | def chunks(s, n): 30 | return [s[i:i+n] for i in xrange(0, len(s), n)] 31 | 32 | class CircuitInfo(ctypes.Structure): 33 | _fields_ = [ 34 | ("input_size", c_uint64), 35 | ("output_size", c_uint64), 36 | ("num_opcodes", c_uint64), 37 | ("opcodes_size", c_uint64), 38 | ("memory", c_uint64), 39 | ] 40 | 41 | 42 | class FastCircuit(object): 43 | def __init__(self, fname): 44 | self.circuit = lib.load_circuit(fname) 45 | assert self.circuit 46 | self.info = CircuitInfo.from_address(self.circuit) 47 | 48 | def compute_one(self, input, trace_filename=None): 49 | output = ctypes.create_string_buffer( int((self.info.output_size + 7)//8) ) 50 | lib.circuit_compute(self.circuit, input, output, trace_filename, 1) 51 | return output.raw 52 | 53 | def compute_batch(self, inputs, trace_filename=None): 54 | bytes_per_output = (self.info.output_size + 7)//8 55 | output = ctypes.create_string_buffer( 56 | int(bytes_per_output * len(inputs)) 57 | ) 58 | input = "".join(inputs) 59 | lib.circuit_compute(self.circuit, input, output, trace_filename, len(inputs)) 60 | return chunks(output.raw, bytes_per_output) 61 | 62 | def compute_batches(self, inputs, trace_filename_format=None): 63 | outputs = [] 64 | for i, chunk in enumerate(chunks(inputs, 64)): 65 | trace_filename = trace_filename_format % i if trace_filename_format else None 66 | outputs += self.compute_batch(chunk, trace_filename) 67 | return outputs 68 | 69 | 70 | if __name__ == '__main__': 71 | print "input_size", FastCircuit("./circuits/test.bin").info.input_size 72 | print "output_size", FastCircuit("./circuits/test.bin").info.output_size 73 | print "num_opcodes", FastCircuit("./circuits/test.bin").info.num_opcodes 74 | print "opcodes_size", FastCircuit("./circuits/test.bin").info.opcodes_size 75 | print "memory", FastCircuit("./circuits/test.bin").info.memory 76 | print FastCircuit("./circuits/test.bin").compute_one("A" * 16).encode("hex") 77 | print FastCircuit("./circuits/test.bin").compute_one("A" * 16, trace_filename="./traces/test_one.bin").encode("hex") 78 | 79 | pts = open("plain").read() 80 | pts = chunks(pts, 16) 81 | print FastCircuit("./circuits/test.bin").compute_batch(pts, trace_filename="./traces/test_batch.bin") 82 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/node/optbitnode.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from op import BitOP 4 | from bitnode import BitNode 5 | 6 | class OptBitNode(BitNode): 7 | OP = BitOP() 8 | 9 | CANCEL_DOUBLE_NOT = True 10 | SINGLETON_CONSTANTS = True 11 | PRECOMPUTE_CONSTANTS = True 12 | PRECOMPUTE_ANNIHILATION = True 13 | CANCEL_NEUTRAL = True 14 | XOR1_TO_NEGATION = True 15 | 16 | # UNARY 17 | def __invert__(self): 18 | if self.CANCEL_DOUBLE_NOT and self.op == self.OP.NOT: 19 | return self.args[0] 20 | if self.PRECOMPUTE_CONSTANTS and self.is_const(): 21 | return self.const(self.value() ^ 1) 22 | return self.new(self.OP.NOT, self) 23 | Not = __invert__ 24 | 25 | # BINARY 26 | def make_binary(op): 27 | def f(a, b): 28 | if isinstance(b, int): b = a.const(b) 29 | a0, b0 = a, b 30 | # ensure b is constant if at least one is constant 31 | if a.is_const(): a, b = b, a 32 | if not b.is_const(): return a.new(op, a0, b0) 33 | 34 | if a.PRECOMPUTE_CONSTANTS: 35 | # both consts 36 | if a.is_const() and b.is_const(): 37 | return a.const(a.OP.eval(op, (a.value(), b.value()))) 38 | 39 | if a.PRECOMPUTE_ANNIHILATION: 40 | if b.is_const(): 41 | if op == a.OP.AND and b.value() == 0: return a.const(0) 42 | if op == a.OP.OR and b.value() == 1: return a.const(1) 43 | 44 | if a.CANCEL_NEUTRAL: 45 | if b.is_const(): 46 | if op == a.OP.AND and b.value() == 1: return a 47 | if op == a.OP.OR and b.value() == 0: return a 48 | if op == a.OP.XOR and b.value() == 0: return a 49 | 50 | if a.XOR1_TO_NEGATION: 51 | if op == a.OP.XOR and b.value() == 1: return ~a 52 | 53 | return a.new(op, a0, b0) 54 | return f 55 | 56 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 57 | And = __and__ = __rand__ = make_binary(OP.AND) 58 | Or = __or__ = __ror__ = make_binary(OP.OR) 59 | del make_binary 60 | 61 | # const optimizations 62 | @classmethod 63 | def const(cls, v): 64 | if cls.SINGLETON_CONSTANTS: 65 | return cls.ONE if v else cls.ZERO 66 | else: 67 | return cls.new(cls.OP.ONE) if v else cls.new(cls.OP.ZERO) 68 | 69 | OptBitNode.ZERO = OptBitNode.new(OptBitNode.OP.ZERO) 70 | OptBitNode.ONE = OptBitNode.new(OptBitNode.OP.ONE) 71 | 72 | 73 | if __name__ == '__main__': 74 | Bit = OptBitNode 75 | x = Bit.inputs("x", 8, tostr=True) 76 | y = ~(x[0] ^ x[1] & x[2]) ^ 1 77 | print "expr", y 78 | print "flattened:" 79 | for v in y.flatten(): 80 | print " ", v 81 | 82 | def updict(d): 83 | # return {("x", k): v for k, v in d.items()} 84 | return {("x%d" % k): v for k, v in d.items()} 85 | 86 | y = ~(x[0] ^ x[1] & x[2]) 87 | print "1 =?", y.eval(updict({0: 1, 1: 1, 2: 1})) 88 | y = (x[0] ^ x[1] & x[2]) 89 | print "0 =?", y.eval(updict({0: 1, 1: 1, 2: 1})) 90 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/optbitnode.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from op import BitOP 4 | from bitnode import BitNode 5 | 6 | class OptBitNode(BitNode): 7 | OP = BitOP() 8 | 9 | CANCEL_DOUBLE_NOT = True 10 | SINGLETON_CONSTANTS = True 11 | PRECOMPUTE_CONSTANTS = True 12 | PRECOMPUTE_ANNIHILATION = True 13 | CANCEL_NEUTRAL = True 14 | XOR1_TO_NEGATION = True 15 | 16 | _ONE = _ZERO = None 17 | 18 | # UNARY 19 | def __invert__(self): 20 | if self.CANCEL_DOUBLE_NOT and self.op == self.OP.NOT: 21 | return self.args[0] 22 | if self.PRECOMPUTE_CONSTANTS and self.is_const(): 23 | return self.const(self.value() ^ 1) 24 | return self.new(self.OP.NOT, self) 25 | Not = __invert__ 26 | 27 | # BINARY 28 | def make_binary(op): 29 | def f(a, b): 30 | if isinstance(b, (int, long)): b = a.const(b) 31 | a0, b0 = a, b 32 | 33 | # ensure b is constant if at least one is constant 34 | if a.is_const(): a, b = b, a 35 | if not b.is_const(): return a.new(op, a0, b0) 36 | 37 | if a.PRECOMPUTE_CONSTANTS: 38 | # both consts 39 | if a.is_const() and b.is_const(): 40 | return a.const(a.OP.eval(op, (a.value(), b.value()))) 41 | 42 | if a.PRECOMPUTE_ANNIHILATION: 43 | if b.is_const(): 44 | if op == a.OP.AND and b.value() == 0: return a.const(0) 45 | if op == a.OP.OR and b.value() == 1: return a.const(1) 46 | 47 | if a.CANCEL_NEUTRAL: 48 | if b.is_const(): 49 | if op == a.OP.AND and b.value() == 1: return a 50 | if op == a.OP.OR and b.value() == 0: return a 51 | if op == a.OP.XOR and b.value() == 0: return a 52 | 53 | if a.XOR1_TO_NEGATION: 54 | if op == a.OP.XOR and b.value() == 1: return ~a 55 | 56 | return a.new(op, a0, b0) 57 | return f 58 | 59 | Xor = __xor__ = __rxor__ = make_binary(OP.XOR) 60 | And = __and__ = __rand__ = make_binary(OP.AND) 61 | Or = __or__ = __ror__ = make_binary(OP.OR) 62 | del make_binary 63 | 64 | # const optimizations 65 | @classmethod 66 | def const(cls, v): 67 | if cls.SINGLETON_CONSTANTS: 68 | if cls._ONE is None: 69 | cls._ZERO = cls(cls.OP.ZERO) 70 | cls._ONE = cls(cls.OP.ONE) 71 | return cls._ONE if int(v) else cls._ZERO 72 | else: 73 | return cls(cls.OP.ONE) if int(v) else cls(cls.OP.ZERO) 74 | 75 | 76 | if __name__ == '__main__': 77 | Bit = OptBitNode 78 | x = Bit.inputs("x", 8, tostr=True) 79 | y = ~(x[0] ^ x[1] & x[2]) ^ 1 80 | print "expr", y 81 | print "flattened:" 82 | for v in y.flatten(): 83 | print " ", v 84 | 85 | def updict(d): 86 | # return {("x", k): v for k, v in d.items()} 87 | return {("x%d" % k): v for k, v in d.items()} 88 | 89 | y = ~(x[0] ^ x[1] & x[2]) 90 | print "1 =?", y.eval(updict({0: 1, 1: 1, 2: 1})) 91 | y = (x[0] ^ x[1] & x[2]) 92 | print "0 =?", y.eval(updict({0: 1, 1: 1, 2: 1})) 93 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/orderer.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | class Multiset(object): 4 | def __init__(self): 5 | self.data = {} 6 | 7 | def add(self, obj, num=1): 8 | self.data.setdefault(obj, 0) 9 | self.data[obj] += num 10 | 11 | def remove(self, obj, num=1): 12 | self.data[obj] -= num 13 | assert self.data[obj] >= 0 14 | if self.data[obj] == 0: 15 | del self.data[obj] 16 | 17 | def remove_all(self, obj): 18 | del self.data[obj] 19 | 20 | def items(self): 21 | return self.data.items() 22 | 23 | def __len__(self): 24 | return len(self.data) 25 | 26 | def __contains__(self, obj): 27 | return obj in self.data 28 | 29 | def __iter__(self): 30 | return self.data.__iter__() 31 | 32 | def __nonzero__(self): 33 | return bool(self.data) 34 | 35 | class ComputationOrder(object): 36 | ACTION_COMPUTE = "compute" 37 | ACTION_FREE = "free" 38 | # ACTION_ALLOC = "alloc" 39 | 40 | def __init__(self, code, xbits, ybits): 41 | self.code = code 42 | self.xbits = tuple(xbits) 43 | self.ybits = tuple(ybits) 44 | 45 | CO = ComputationOrder 46 | 47 | class Orderer(object): 48 | def __init__(self, xbits, ybits, quiet=False): 49 | self.ybits = ybits 50 | self.xbits = list(xbits) 51 | 52 | self.quiet = quiet 53 | 54 | def log(self, *args): 55 | if not self.quiet: 56 | print "::", 57 | for arg in args: 58 | print arg, 59 | print 60 | 61 | def compile(self): 62 | self.log("circuit walk") 63 | 64 | visited = set() 65 | using = {} 66 | 67 | q = [] 68 | for b in self.ybits: 69 | q.append(b) 70 | visited.add(b) 71 | while q: 72 | b = q.pop() 73 | for sub in b.args: 74 | if type(sub) == type(b): 75 | if sub not in visited: 76 | visited.add(sub) 77 | q.append(sub) 78 | 79 | if sub not in using: 80 | using[sub] = Multiset() 81 | using[sub].add(b) 82 | 83 | self.log("ordering", len(visited), "nodes") 84 | 85 | order = sorted(visited, key=lambda b: b.id) 86 | ready = set() 87 | code = [] 88 | for b in order: 89 | if b.is_primitive(): 90 | ready.add(b) 91 | continue 92 | 93 | code.append((CO.ACTION_COMPUTE, b)) 94 | ready.add(b) 95 | 96 | for sub in b.args: 97 | if type(sub) != type(b): 98 | continue 99 | assert sub in ready 100 | using[sub].remove_all(b) 101 | if not using[sub]: 102 | code.append((CO.ACTION_FREE, sub)) 103 | code = tuple(code) 104 | 105 | self.log("code size: %d operations" % len(code)) 106 | 107 | return ComputationOrder(xbits=self.xbits, ybits=self.ybits, code=code) 108 | -------------------------------------------------------------------------------- /synthesis/whitebox/containers/vector.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import operator 4 | 5 | class Vector(list): 6 | ZERO = 0 7 | WIDTH = None 8 | 9 | @classmethod 10 | def make(cls, lst): 11 | lst = list(lst) 12 | if cls.WIDTH is not None: 13 | assert len(lst) == cls.WIDTH 14 | return cls(lst) 15 | 16 | def split(self, n=2): 17 | assert len(self) % n == 0 18 | w = len(self) // n 19 | return Vector(self.make(self[i:i+w]) for i in xrange(0, len(self), w)) 20 | 21 | def concat(self, *lst): 22 | v = list(self) 23 | for t in lst: 24 | v += list(t) 25 | return self.make(v) 26 | 27 | def rol(self, n=1): 28 | n %= len(self) 29 | return self.make(self[n:] + self[:n]) 30 | 31 | def ror(self, n=1): 32 | return self.rol(-n) 33 | 34 | def shl(self, n=1): 35 | assert n >= 0 36 | n = min(n, len(self)) 37 | return self.make(list(self[n:]) + [self._zero() for i in xrange(n)]) 38 | 39 | def shr(self, n=1): 40 | assert n >= 0 41 | n = min(n, len(self)) 42 | return self.make([self._zero() for i in xrange(n)] + list(self[:-n])) 43 | 44 | def _zero(self): 45 | """method because sometimes need different instances""" 46 | return self.ZERO 47 | 48 | def __repr__(self): 49 | return "" % (len(self), list(self)) 50 | 51 | def flatten(self): 52 | if isinstance(self[0], Vector): 53 | return self[0].concat(*self[1:]) 54 | return reduce(operator.add, list(self)) 55 | 56 | def permute(self, perm, inverse=False): 57 | """ 58 | Perm contains indexes in original vector 59 | Example: 60 | vec = [0, 1, 2, 3] 61 | perm = [1, 2, 3, 0] 62 | res = [1, 2, 3, 0] 63 | """ 64 | if not inverse: 65 | lst = [self[i] for i in perm] 66 | else: 67 | lst = [None] * len(self) 68 | for i, j in enumerate(perm): 69 | lst[j] = self[i] 70 | return self.make(lst) 71 | 72 | def map(self, f, with_coord=False): 73 | if with_coord: 74 | return self.make(f(i, v) for i, v in enumerate(self)) 75 | else: 76 | return self.make(f(v) for v in self) 77 | 78 | def __xor__(self, other): 79 | assert isinstance(other, Vector) 80 | assert len(self) == len(other) 81 | return self.make(a ^ b for a, b in zip(self, other)) 82 | 83 | def __or__(self, other): 84 | assert isinstance(other, Vector) 85 | assert len(self) == len(other) 86 | return self.make(a | b for a, b in zip(self, other)) 87 | 88 | def __and__(self, other): 89 | assert isinstance(other, Vector) 90 | assert len(self) == len(other) 91 | return self.make(a & b for a, b in zip(self, other)) 92 | 93 | def set(self, x, val): 94 | return self.make(v if i != x else val for i, v in enumerate(self)) 95 | 96 | # for overriding 97 | def __add__(self, other): 98 | raise NotImplementedError("add vectors?") 99 | __radd__ = __add__ 100 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/gadget_check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | from sage.all import * 3 | 4 | FNAME = sys.argv[1] 5 | FINAME = sys.argv[1] + ".names" 6 | 7 | order = "Cxyrv" 8 | traces = {} 9 | inputs = [] 10 | nodes = [] 11 | ntraces = None 12 | 13 | for line in open(FNAME): 14 | parts = line.strip().split("`") 15 | if not parts: 16 | continue 17 | name, vec = parts 18 | traces[name] = map(int, vec) 19 | if not name.startswith("v"): 20 | inputs.append(name) 21 | nodes.append(name) 22 | if ntraces is None: 23 | ntraces = len(vec) 24 | assert ntraces == len(vec) 25 | assert ntraces & (ntraces - 1) == 0, "number of traces must be a power of 2" 26 | 27 | infos = {"C": "const1"} 28 | for line in open(FINAME): 29 | parts = line.strip().split("`") 30 | if not parts: 31 | continue 32 | name, info = parts 33 | infos[name] = info 34 | 35 | # constant vector 36 | inputs = ["C"] + inputs 37 | nodes = ["C"] + nodes 38 | traces["C"] = (1,) * ntraces 39 | 40 | inputs.sort(key=lambda name: order.index(name[0])) 41 | 42 | nmain = sum(1 for name in inputs if name[0] in "Cxy") 43 | nrand = len(inputs) - nmain 44 | 45 | main_inputs = inputs[:nmain] 46 | rand_inputs = inputs[nmain:nmain+nrand] 47 | 48 | print "Verifying 1-st order algebraic security" 49 | print "main inputs", " ".join(main_inputs) 50 | print "rand inputs", " ".join(rand_inputs) 51 | print " all inputs", " ".join(inputs) 52 | print " all nodes", " ".join(nodes) 53 | print 54 | 55 | print "INPUTS" 56 | mx = matrix(GF(2), ntraces, len(main_inputs)) 57 | num1 = mx.ncols() 58 | for x, node in enumerate(main_inputs): 59 | mx.set_column(x, traces[node]) 60 | num2 = mx.column_space().dimension() 61 | 62 | print "- reduction:", num1, "->", num2 63 | print 64 | assert num1 == num2, "inputs must be linearly independent" 65 | 66 | print "NODES" 67 | m0 = m = matrix(GF(2), ntraces, len(nodes)) 68 | num1 = m.ncols() 69 | for x, node in enumerate(nodes): 70 | m.set_column(x, traces[node]) 71 | num2 = m.ncols() 72 | print "- reduction:", num1, "->", num2 73 | print 74 | 75 | if 1: # print basis vectors 76 | for x in xrange(m.ncols()): 77 | col = m.column(x) 78 | sol = m0.solve_right(col) 79 | print "- nodes basis vector %3d:" % x, " + ".join(name for name, v in zip(nodes, sol) if v) 80 | # for name, v in zip(nodes, sol): 81 | # if v: 82 | # print "---", name, infos[name] 83 | print 84 | 85 | mrank = m.column_space().dimension() 86 | mxrank = mx.column_space().dimension() 87 | 88 | print "ZONES CHECK", mrank, mxrank 89 | zone_size = 1 << nrand 90 | for zone_index in xrange(ntraces >> nrand): 91 | sub = m[zone_index * zone_size : (zone_index + 1) * zone_size] 92 | subx = mx[zone_index * zone_size : (zone_index + 1) * zone_size] 93 | 94 | m_nullity = mrank - sub.column_space().dimension() 95 | mx_nullity = mxrank - subx.column_space().dimension() 96 | print "- zone %3d:" % zone_index, m_nullity, mx_nullity, "(from sub dim %d %d)" % (sub.column_space().dimension(), subx.column_space().dimension()) 97 | assert m_nullity == mx_nullity, "nullity must be equal (insecure!)" 98 | 99 | print "------------------------------------------------" 100 | print "VERDICT: Gadget is 1-th order algebraically secure" 101 | print "------------------------------------------------" 102 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/node/node.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | 4 | class Node(object): 5 | COUNTER = 0 6 | OP = NotImplemented 7 | 8 | meta = {} 9 | 10 | def __init__(self, op, *args, **kwargs): 11 | assert op in self.OP 12 | self.op = op 13 | self.args = list(args) 14 | self.meta = self.meta.copy() 15 | self.meta.update(kwargs) 16 | 17 | self.id = self.COUNTER 18 | type(self).COUNTER += 1 19 | 20 | @classmethod 21 | def new(cls, *args, **kwargs): 22 | return cls(*args, **kwargs) 23 | 24 | def __iter__(self): 25 | for sub in self.args: 26 | if isinstance(sub, Node): 27 | yield sub 28 | 29 | def __str__(self): 30 | if self.op == self.OP.INPUT: return str(self.args[0]) 31 | if self.meta.get("fake-input"): return self.meta.get("fake-input") 32 | sym = str(self.OP.symbol[self.op]) 33 | 34 | if len(self.args) > 1: 35 | return "(" + (" " + sym + " ").join(map(str, self.args)) + ")" 36 | elif len(self.args) == 1: 37 | return sym + str(self.args[0]) 38 | elif len(self.args) == 0: 39 | return sym 40 | 41 | def __repr__(self): 42 | cls = self.__class__.__name__ 43 | 44 | op = self.OP.name[self.op] 45 | 46 | args = [] 47 | for sub in self.args: 48 | if isinstance(sub, Node): 49 | assert sub.__class__ == self.__class__ 50 | if sub.is_input(): 51 | args.append(sub.args[0]) 52 | else: 53 | args.append("#%r" % (sub.id)) 54 | else: 55 | args.append(`sub`) 56 | return "<%s#%d = %s(%s)>" % (cls, self.id, op, ",".join(map(str, args))) 57 | 58 | def __hash__(self): 59 | return hash(id(self)) 60 | 61 | def structure_hash(self): 62 | if self.__dict__.get("_sh", None) is None: 63 | self._sh = hash((self.op,) + tuple(hash(v) for v in self.args)) 64 | return self._sh 65 | 66 | def is_input(self): 67 | return self.op == self.OP.INPUT 68 | 69 | @classmethod 70 | def input(cls, name): 71 | return cls(cls.OP.INPUT, name) 72 | 73 | @classmethod 74 | def inputs(cls, name, n, tostr=False): 75 | return tuple(cls.input(name+str(i) if tostr else (name, i)) for i in xrange(n)) 76 | 77 | def name(self): 78 | assert self.is_input() 79 | return self.args[0] 80 | 81 | def flatten(self, out=None): 82 | if out is None: 83 | out = set() 84 | if self in out: 85 | return 86 | for sub in self.args: 87 | if isinstance(sub, Node): 88 | sub.flatten(out=out) 89 | out.add(self) 90 | return out 91 | 92 | @staticmethod 93 | def flatten_many(nodes): 94 | out = set() 95 | for node in nodes: 96 | node.flatten(out=out) 97 | return out 98 | 99 | def eval(self, acc): 100 | """acc is dict {bit: value} with initial values (typically input bits)""" 101 | if self not in acc: 102 | acc[self] = self.OP.eval(self.op, [v.eval(acc) for v in self.args]) 103 | return acc[self] 104 | 105 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/gadget_trace.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import math 5 | from itertools import product 6 | 7 | from tree import OptBitNode as Bit 8 | from tree.trace import circuit_trace 9 | from tree.anf import compute_anfs 10 | OP = Bit.OP 11 | 12 | from minimalist3 import Minimalist3 13 | 14 | S = Minimalist3() 15 | 16 | print S 17 | print 18 | 19 | x = Bit.inputs("x", S.N) 20 | y = Bit.inputs("y", S.N) 21 | rx = Bit.inputs("rx", S.NR) 22 | ry = Bit.inputs("ry", S.NR) 23 | 24 | # Any circuit can be put in target, not necessarily a masking scheme 25 | 26 | # single operation 27 | # target = S.EvalXOR(x, y, rx, ry) 28 | # target = S.EvalAND(x, y, rx, ry) 29 | 30 | # two at the same time (equivalent to previous two) 31 | target = S.EvalXOR(x, y, rx, ry) + S.EvalAND(x, y, rx, ry) 32 | 33 | # testing refresh separately 34 | # target = S.Refresh(*(x + rx)) + S.Refresh(*(y+ry)) 35 | 36 | 37 | if 1: # compute ANFs and bias bound 38 | for bit in target: 39 | compute_anfs(bit) 40 | nodes = Bit.flatten_many(target) 41 | 42 | maxdeg = 0 43 | res = [] 44 | for b in sorted(nodes, key=lambda b: b.id): 45 | if b.op in (b.OP.AND, b.OP.OR): 46 | print b.id, "=", b.OP.name[b.op], " ".join(map(str, [sub.id for sub in b.args if isinstance(sub, Bit)])), ":", 47 | print "degree on r %d, full %d:" % (b.meta["anf"].degree(filter_func=lambda v: v.startswith("r")), b.meta["anf"].degree()), 48 | print b.meta["anf"] 49 | res.append(b.meta["anf"]) 50 | elif b.is_input(): 51 | print b.id, "=", b.name(), ":", b.meta["anf"] 52 | res.append(b.meta["anf"]) 53 | else: 54 | assert b.op in (b.OP.XOR, b.OP.NOT, b.OP.INPUT) 55 | 56 | print 57 | 58 | maxdeg = max(a.degree(filter_func=lambda v: v.startswith("r")) for a in res) 59 | from fractions import Fraction 60 | print "------------------------------------------" 61 | print "Maximum degree:", maxdeg 62 | bias = Fraction(1, 2)-Fraction(1, 2**maxdeg) 63 | print "Bias bound: bias <= %s" % bias 64 | e = -math.log(0.5 + bias, 2) 65 | print "Provable security:" 66 | try: 67 | print " 80 bit: R > %d" % (80 * (1 + 1/e)) 68 | print "128 bit: R > %d" % (128 * (1 + 1/e)) 69 | except: 70 | pass 71 | print "------------------------------------------" 72 | print 73 | 74 | def sbin(x, n): 75 | return "".join(map(str, tobin(x, n))) 76 | 77 | def tobin(x, n): 78 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 79 | 80 | if 1: # trace the circuit to perform linear algebra analysis in Sage 81 | input_order = x + y + rx + ry 82 | inputs = list(product(range(2), repeat=len(input_order))) 83 | 84 | trace = circuit_trace(outputs=target, input_order=input_order, inputs_list=inputs) 85 | 86 | ntraces = len(inputs) 87 | order = sorted(trace.keys(), key=lambda bit: bit.id) 88 | order = [b for b in order if b.op != b.OP.XOR and not b.is_const()] 89 | 90 | with open("traces/%s" % S.NAME, "w") as f: 91 | with open("traces/%s.names" % S.NAME, "w") as fi: 92 | cntr = 0 93 | for bit in order: 94 | if bit.is_input(): 95 | name = bit.name() 96 | else: 97 | name ="v%d" % cntr 98 | cntr += 1 99 | value = trace[bit] 100 | f.write("%s`%s\n" % (name, sbin(value, ntraces))) 101 | 102 | info = bit.meta.get("tag", "") + ":"# + str(bit) 103 | info = info.strip(":") 104 | fi.write("%s`%s\n" % (name, info)) 105 | 106 | print "Traces written" 107 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/tree/symbolic.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from itertools import chain 4 | 5 | class Term(object): pass 6 | class Const(Term, int): pass 7 | class Var(Term, str): pass 8 | 9 | class Bit(Term): 10 | def __init__(self, v): 11 | if isinstance(v, set): 12 | self.anf = v 13 | elif isinstance(v, Const) or isinstance(v, Var): 14 | self.anf = {(v,)} 15 | elif isinstance(v, int): 16 | self.anf = {(Const(v),)} 17 | elif isinstance(v, str): 18 | self.anf = {(Var(v),)} 19 | else: 20 | assert 0, "Bit(%s) = ?" % type(v) 21 | self.anf.discard((Const(0),)) 22 | 23 | def __eq__(self, other): 24 | return self.anf == other.anf 25 | 26 | def __xor__(self, other): 27 | if not isinstance(other, Bit): 28 | other = Bit(other) 29 | return Bit(self.anf ^ other.anf) 30 | __add__ = __xor__ 31 | 32 | def __and__(self, other): 33 | res = set() 34 | for t1 in self.anf: 35 | for t2 in other.anf: 36 | res ^= {self._merge_products(t1, t2)} 37 | return Bit(res) 38 | __mul__ = __and__ 39 | 40 | def __invert__(self): 41 | return self ^ Bit(1) 42 | 43 | def __or__(self, other): 44 | return ~((~self) & (~other)) 45 | 46 | def _merge_products(self, t1, t2): 47 | assert isinstance(t1, tuple) 48 | assert isinstance(t2, tuple) 49 | res = set() 50 | for t in chain(t1, t2): 51 | if isinstance(t, Const): 52 | if t == 0: 53 | return (t,) 54 | continue 55 | elif isinstance(t, Var): 56 | res.add(t) 57 | if not res: 58 | return (Const(1),) 59 | return tuple(sorted(res)) 60 | 61 | def __str__(self): 62 | res = [] 63 | for t in self.anf: 64 | res.append( "*".join(map(str, sorted(t))) ) 65 | return " ^ ".join(sorted(res)) or "0" 66 | 67 | def __repr__(self): 68 | return "Bit(%r)" % self.anf 69 | 70 | def degree(self, filter_func=None): 71 | if filter_func is None: 72 | return max(len(t) for t in self.anf) if self.anf else 0 73 | else: 74 | res = 0 75 | for t in self.anf: 76 | l = sum(1 for v in t if isinstance(v, Var) and filter_func(v)) 77 | res = max(res, l) 78 | return res 79 | 80 | def variables(self, filter_func=None): 81 | vs = set() 82 | for t in self.anf: 83 | for v in t: 84 | if isinstance(v, Var) and (not filter_func or filter_func(v)): 85 | vs.add(v) 86 | return vs 87 | 88 | def count(self, filter_func=None): 89 | return len(self.variables(filter_func=filter_func)) 90 | 91 | def subs(self, var, bit): 92 | if isinstance(var, str): 93 | var = Var(var) 94 | assert isinstance(var, Var) 95 | assert isinstance(bit, Bit) 96 | res = self.anf.copy() 97 | for t1 in self.anf: 98 | if var in t1: 99 | res ^= {t1} 100 | t1 = list(t1) 101 | del t1[t1.index(var)] 102 | t1 = tuple(t1) 103 | for t2 in bit.anf: 104 | res ^= {self._merge_products(t1, t2)} 105 | return Bit(res) 106 | 107 | 108 | ZERO = Bit(0) 109 | ONE = Bit(1) 110 | 111 | if __name__ == '__main__': 112 | print Bit(Const(0)) 113 | print Bit(Const(1)) 114 | print Bit(Var("x1")) 115 | print Bit(Var("x2")) 116 | print Bit(Var("x1")) & Bit(Var("x2")) 117 | print Bit(Var("x1")) ^ Bit(Var("x2")) 118 | print Bit(1) * Bit("x1") * Bit("x2") * Bit(1) + Bit(0) * Bit("x3") + Bit(1) 119 | print (Bit("a") * Bit("b") + Bit("R") * Bit("b")).subs("b", Bit(1) + Bit("Q") + Bit("c") * Bit("b")) 120 | quit() 121 | -------------------------------------------------------------------------------- /synthesis/attacks/reader.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import os 4 | from collections import deque 5 | 6 | class Reader(object): 7 | TRACE_FILENAME_FORMAT = "%04d.bin" 8 | PLAINTEXT_FILENAME_FORMAT = "%04d.pt" 9 | CIPHERTEXT_FILENAME_FORMAT = "%04d.ct" 10 | 11 | def __init__(self, ntraces, window, step=None, 12 | packed=True, reverse=False, dir="./traces"): 13 | 14 | self.packed = packed 15 | 16 | self.pts = [] 17 | self.cts = [] 18 | self.fds = [] 19 | 20 | self.trace_bytes = None 21 | self.ntraces = int(ntraces) 22 | assert self.ntraces >= 1 23 | for i in xrange(self.ntraces): 24 | f_trace = os.path.join(dir, self.TRACE_FILENAME_FORMAT % i) 25 | f_pt = os.path.join(dir, self.PLAINTEXT_FILENAME_FORMAT % i) 26 | f_ct = os.path.join(dir, self.CIPHERTEXT_FILENAME_FORMAT % i) 27 | 28 | self.pts.append(open(f_pt, "rb").read()) 29 | self.cts.append(open(f_ct, "rb").read()) 30 | 31 | new_size = os.stat(f_trace).st_size 32 | if self.trace_bytes is None: 33 | self.trace_bytes = new_size 34 | assert self.trace_bytes == new_size, "Trace files must have the same size" 35 | 36 | self.fds.append(open(f_trace, "rb")) 37 | 38 | self.reverse = reverse 39 | if reverse: 40 | raise NotImplementedError("Not supported yet") 41 | 42 | if step is None: 43 | step = window 44 | assert 0 < step <= window 45 | 46 | if self.packed: 47 | # round up to multiple of 8 48 | window += (8 - window % 8) % 8 49 | step += (8 - step % 8) % 8 50 | self.window_bytes = window // 8 51 | self.step_bytes = step // 8 52 | else: 53 | self.window_bytes = window 54 | self.step_bytes = step 55 | 56 | self.window_bytes = min(self.window_bytes, self.trace_bytes) 57 | self.step_bytes = min(self.step_bytes, self.trace_bytes) 58 | 59 | # may be ceil? not accurate! 60 | self.num_windows = (self.trace_bytes - self.window_bytes + self.step_bytes - 1) // self.step_bytes + 1 61 | 62 | # not sure if working with longs is faster than with arrays 63 | # or some other structure, may be even makes sense to write C backend 64 | 65 | def __iter__(self): 66 | self.vectors = deque() 67 | self.offset = 0 68 | self.advance(self.window_bytes) 69 | for v in self.new_vectors: 70 | self.vectors.append(v) 71 | yield self.vectors 72 | 73 | while True: 74 | if self.offset + self.window_bytes >= self.trace_bytes: 75 | # already covered the whole trace 76 | return 77 | 78 | self.advance(self.step_bytes) 79 | for v in self.new_vectors: 80 | self.vectors.append(v) 81 | self.vectors.popleft() 82 | self.offset += self.step_bytes 83 | 84 | yield self.vectors 85 | 86 | 87 | def advance(self, num_bytes): 88 | self.new_vectors = [0] * (num_bytes * 8) 89 | for fd in self.fds: 90 | data = fd.read(num_bytes) 91 | self.process_window(data) 92 | self.new_vectors = self.new_vectors[:len(data)*8] 93 | 94 | def process_window(self, data): 95 | vectors = self.new_vectors 96 | if not self.packed: 97 | # 1 bit in byte 98 | for i, b in enumerate(data): 99 | val = ord(b) & 1 100 | vectors[i] = (vectors[i] << 1) | val 101 | assert b in "\x00\x01", "sure not packed?" 102 | else: 103 | # 8 bits in byte (packed) 104 | for i, b in enumerate(data): 105 | b = ord(b) 106 | for j in xrange(8): 107 | id = (i << 3) | j 108 | bit = (b >> (7 - j)) & 1 109 | vectors[id] = (vectors[id] << 1) | bit 110 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/README.md: -------------------------------------------------------------------------------- 1 | # Attacks on White-box implementations 2 | 3 | This folder contains several attacks on white-box implementations. 4 | 5 | ### How to run: 6 | 7 | 1. Run `./0_build.sh` to compile the implementation in the ../implementation folder (possibly using DISABLE_NONLINEAR_MASKING=1 flag). 8 | 2. Run `./1_trace.sh 200`\ 9 | (200 is the number of traces) 10 | 3. Run one of the attack scripts (starting with 2_), e.g. `./2_DCA_linalg_1st.sh 200 100`\ 11 | (200 is the number of traces, 100 is the window size). 12 | 13 | The default key in the implementation is **"samplekey1234567"**, which in hex is 14 | 15 | `73 61 6d 70 6c 65 6b 65 79 31 32 33 34 35 36 37` 16 | 17 | Set environment variable `DISABLE_RANDOM=1` **during traces generation** to disable randomness in the AC18 implementation. 18 | 19 | Set environment variable `DISABLE_NONLINEAR_MASKING=1` **during compilation** to disable the quadratic masking scheme. 20 | 21 | The `1_trace.sh` script generates value traces of the implementation. They are saved in the `traces/` folder, e.g. `traces/0001.pt`, `traces/0001.ct`, `traces/0001.bin`. The latter file contains the recorded values, one bit per byte (uncompressed). The traces are used further by the attacks. 22 | 23 | Note that in these attack implementations windows are chosen using a sliding window on the trace file. A more advanced strategy would be to generate window coverage based on the circuit based on distances in the circuit graph. 24 | 25 | 26 | ## First-order linear algebra attack 27 | 28 | **Script:** 2_DCA_linalg_1st.sh\ 29 | **Code and configuration:** analyze_linalg_1st.py\ 30 | **Requirements:** [SageMath](http://www.sagemath.org/) 31 | 32 | An implementation of the 1-st order linear algebra attack in [Sage](http://www.sagemath.org/). 33 | 34 | The implementation with `DISABLE_NONLINEAR_MASKING=1` can be broken using this attack (or if `DISABLE_RANDOM=1` is set and the masking scheme becomes ineffective). Otherwise, the implementation is secure against this attack. 35 | 36 | ``` 37 | $ DISABLE_NONLINEAR_MASKING=1 ./0_build.sh 38 | $ ./1_trace.sh 200 39 | $ ./2_DCA_linalg_1st.sh 200 100 40 | ... (time ~5 seconds) 41 | Window 30 / 4050 offset 725-824 42 | 100 vectors 43 | 100 unique vectors 44 | 1536 target vectors 45 | MATCH: sbox #0, lin.mask 0x01, key 0x73='s', negated? True, indexes 733...823 (distance 90) [733, 734, 735, 739, 745, 757, 759, 761, 763, 767, 772, 783, 818, 822, 823] 46 | 47 | $ DISABLE_NONLINEAR_MASKING=0 ./0_build.sh 48 | $ ./1_trace.sh 200 49 | $ ./2_DCA_linalg_1st.sh 200 100 50 | ... (time ~4 hours) 51 | No matches. 52 | ``` 53 | 54 | ## Time-memory trade-off attack on linear masking 55 | 56 | **Script:** 2_DCA_exact_1st_2nd.sh\ 57 | **Code and configuration:** analyze_exact.py 58 | 59 | An implementation of the time-memory trade-off attack. The first-order attack searches for exact match of the predicted value in the traces (breaks unprotected implementations). The second order attack searches for a pair of shares that exactly match the predicted value (breaks first-order masked implementation). 60 | 61 | The implementation with `DISABLE_NONLINEAR_MASKING=1` can be broken with the second order attack. If `DISABLE_RANDOM=1` is set, then the masking schemes become ineffective and even the first order attack recovers the key. Without these options this attack is ineffective. 62 | 63 | 64 | ## First-order correlation attack (using Daredevil) 65 | 66 | **Script:** 2_DCA_Daredevil_1st.sh\ 67 | **Requirements:** [Daredevil](https://github.com/SideChannelMarvels/Daredevil) 68 | 69 | Here the implementation is tested using the [Daredevil tool]( https://github.com/SideChannelMarvels/Daredevil). The traces are collected and Daredevil attempts the first-order correlation attack. As the implementation is first-order protected by a linear masking scheme, the key is not recovered. However, the key can be recovered by disabling the randomness in the implementation. If `DISABLE_RANDOM` is set to 1, then the key can be recovered with just a few traces, e.g. 20. Note that the quadratic masking scheme by itself does not prevent this attack. 70 | 71 | The config file `daredevil.conf` is generated and can be modified and reused. For example, to run the second-order attack. 72 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/verification/README.md: -------------------------------------------------------------------------------- 1 | # Verification of Circuit Algebraic Security 2 | 3 | This folder contains an implementation of the **Algorithm 2** from the paper. It checks whether a small circuit (a gadget) is *algebraically secure*. 4 | 5 | ### How to run: 6 | 7 | 1. Define the target circuit in the file `gadget_trace.py`. The minimalist masking scheme from the paper is included and chosen by default. The circuit is defined using symbolic computations, see `minimalist3.py` for an example. 8 | 2. Run `./gadget_trace.py` with Python2/PyPy2. It reports **the maximum degree** and **the bias bound**. It also generates a "trace" taken on all possible inputs, i.e. truth tables of all computed functions. 9 | 3. Run `./gadget_check.py` with Sage and specify path to the trace file as an argument. It implements **Algorithm 2** from the paper. The algorithm checks whether the circuit is *1-AS-secure* or not. 10 | 11 | ### Example: 12 | 13 | ``` 14 | $ sage gadget_trace.py 15 | 16 | 17 | 4 = x0 : x0 18 | 5 = x1 : x1 19 | 6 = x2 : x2 20 | 7 = y0 : y0 21 | 8 = y1 : y1 22 | 9 = y2 : y2 23 | 10 = rx0 : rx0 24 | 11 = rx1 : rx1 25 | 12 = rx2 : rx2 26 | 13 = ry0 : ry0 27 | 14 = ry1 : ry1 28 | 15 = ry2 : ry2 29 | 18 = AND 10 17 : degree on r 2, full 2: rx0*rx2 ^ rx0*x1 30 | 19 = AND 11 16 : degree on r 2, full 2: rx1*rx2 ^ rx1*x0 31 | 23 = AND 21 22 : degree on r 2, full 2: rx0*rx1 ^ rx0*rx2 ^ rx1*rx2 ^ rx2 32 | 31 = AND 13 30 : degree on r 2, full 2: ry0*ry2 ^ ry0*y1 33 | 32 = AND 14 29 : degree on r 2, full 2: ry1*ry2 ^ ry1*y0 34 | 36 = AND 34 35 : degree on r 2, full 2: ry0*ry1 ^ ry0*ry2 ^ ry1*ry2 ^ ry2 35 | 42 = AND 26 40 : degree on r 2, full 2: rx0*ry1 ^ rx0*y1 ^ ry1*x0 ^ x0*y1 36 | 43 = AND 27 39 : degree on r 2, full 2: rx1*ry0 ^ rx1*y0 ^ ry0*x1 ^ x1*y0 37 | 52 = AND 10 51 : degree on r 2, full 2: rx0*rx2 ^ rx0*x1 38 | 53 = AND 11 50 : degree on r 2, full 2: rx1*rx2 ^ rx1*x0 39 | 57 = AND 55 56 : degree on r 2, full 2: rx0*rx1 ^ rx0*rx2 ^ rx1*rx2 ^ rx2 40 | 65 = AND 13 64 : degree on r 2, full 2: ry0*ry2 ^ ry0*y1 41 | 66 = AND 14 63 : degree on r 2, full 2: ry1*ry2 ^ ry1*y0 42 | 70 = AND 68 69 : degree on r 2, full 2: ry0*ry1 ^ ry0*ry2 ^ ry1*ry2 ^ ry2 43 | 76 = AND 60 74 : degree on r 2, full 2: rx0*ry1 ^ rx0*y1 ^ ry1*x0 ^ x0*y1 44 | 78 = AND 61 73 : degree on r 2, full 2: rx1*ry0 ^ rx1*y0 ^ ry0*x1 ^ x1*y0 45 | 80 = AND 62 74 : degree on r 3, full 3: rx0*rx1*ry1 ^ rx0*rx1*y1 ^ rx0*ry1*x1 ^ rx0*x1*y1 ^ rx1*ry1*x0 ^ rx1*x0*y1 ^ ry1*x2 ^ x2*y1 46 | 81 = AND 61 15 : degree on r 2, full 2: rx1*ry2 ^ ry2*x1 47 | 83 = AND 82 73 : degree on r 4, full 4: rx0*rx1*ry0*ry1 ^ rx0*rx1*ry0*y1 ^ rx0*rx1*ry1*y0 ^ rx0*rx1*y0*y1 ^ rx0*ry0*ry1*x1 ^ rx0*ry0*x1*y1 ^ rx0*ry1*x1*y0 ^ rx0*x1*y0*y1 ^ rx1*ry0*ry1*x0 ^ rx1*ry0*ry2 ^ rx1*ry0*x0*y1 ^ rx1*ry1*x0*y0 ^ rx1*ry2*y0 ^ rx1*x0*y0*y1 ^ ry0*ry1*x2 ^ ry0*ry2*x1 ^ ry0*x2*y1 ^ ry1*x2*y0 ^ ry2*x1*y0 ^ x2*y0*y1 48 | 84 = AND 61 75 : degree on r 3, full 3: rx1*ry0*ry1 ^ rx1*ry0*y1 ^ rx1*ry1*y0 ^ rx1*y2 ^ ry0*ry1*x1 ^ ry0*x1*y1 ^ ry1*x1*y0 ^ x1*y2 49 | 85 = AND 74 12 : degree on r 2, full 2: rx2*ry1 ^ rx2*y1 50 | 87 = AND 86 60 : degree on r 4, full 4: rx0*rx1*ry0*ry1 ^ rx0*rx1*ry0*y1 ^ rx0*rx1*ry1*y0 ^ rx0*rx1*y2 ^ rx0*rx2*ry1 ^ rx0*rx2*y1 ^ rx0*ry0*ry1*x1 ^ rx0*ry0*x1*y1 ^ rx0*ry1*x1*y0 ^ rx0*x1*y2 ^ rx1*ry0*ry1*x0 ^ rx1*ry0*x0*y1 ^ rx1*ry1*x0*y0 ^ rx1*x0*y2 ^ rx2*ry1*x0 ^ rx2*x0*y1 ^ ry0*ry1*x0*x1 ^ ry0*x0*x1*y1 ^ ry1*x0*x1*y0 ^ x0*x1*y2 51 | 88 = AND 62 75 : degree on r 4, full 4: rx0*rx1*ry0*ry1 ^ rx0*rx1*ry0*y1 ^ rx0*rx1*ry1*y0 ^ rx0*rx1*y2 ^ rx0*ry0*ry1*x1 ^ rx0*ry0*x1*y1 ^ rx0*ry1*x1*y0 ^ rx0*x1*y2 ^ rx1*ry0*ry1*x0 ^ rx1*ry0*x0*y1 ^ rx1*ry1*x0*y0 ^ rx1*x0*y2 ^ ry0*ry1*x2 ^ ry0*x2*y1 ^ ry1*x2*y0 ^ x2*y2 52 | 89 = AND 15 12 : degree on r 2, full 2: rx2*ry2 53 | 54 | ------------------------------------------ 55 | Maximum degree: 4 56 | Bias bound: bias <= 7/16 57 | Provable security: 58 | 80 bit: R > 939 59 | 128 bit: R > 1502 60 | ------------------------------------------ 61 | 62 | Traces written 63 | 64 | $ sage gadget_check.py traces/minimalist3 65 | Verifying 1-st order algebraic security 66 | main inputs C x0 x1 x2 y0 y1 y2 67 | rand inputs rx0 rx1 rx2 ry0 ry1 ry2 68 | all inputs C x0 x1 x2 y0 y1 y2 rx0 rx1 rx2 ry0 ry1 ry2 69 | all nodes C x0 x1 x2 y0 y1 y2 rx0 rx1 rx2 ry0 ry1 ry2 v0 v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 v21 v22 v23 70 | 71 | INPUTS 72 | - reduction: 7 -> 7 73 | 74 | NODES 75 | - reduction: 37 -> 37 76 | 77 | ... 78 | 79 | ------------------------------------------------ 80 | VERDICT: Gadget is 1-th order algebraically secure 81 | ------------------------------------------------ 82 | 83 | ``` 84 | -------------------------------------------------------------------------------- /synthesis/attacks/analyze_linalg_1st.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | #-*- coding:utf-8 -*- 3 | 4 | from sage.all import * 5 | 6 | import sys, os, string 7 | from itertools import product 8 | 9 | from sbox import sbox, rsbox 10 | from reader import Reader 11 | 12 | 13 | #== Configuration 14 | 15 | # go from the end of the traces if we attack last S-Boxes ? 16 | REVERSE = False # not supported yet 17 | 18 | NTRACES = int(sys.argv[1]) 19 | WINDOW = int(sys.argv[2]) 20 | STEP = WINDOW 21 | if len(sys.argv) >= 4: 22 | STEP = int(sys.argv[3]) 23 | 24 | R = Reader(ntraces=NTRACES, 25 | window=WINDOW, 26 | step=STEP, 27 | packed=True, 28 | reverse=REVERSE, 29 | dir="./traces") 30 | 31 | 32 | STOP_ON_FIRST_MATCH = 0 33 | 34 | # attack last S-Box? 35 | CT_SIDE = REVERSE 36 | 37 | # which/how many S-Boxes to attack 38 | BYTE_INDICES = range(16) # all 39 | # BYTE_INDICES = range(3) # only first 3 40 | 41 | # charset for key bytes 42 | KS = range(256) 43 | # KS = map(ord, string.printable) # only printable characters 44 | 45 | # linear masks to check after S-Box, for example 46 | # 0xff will try to match scalar_product(SBox(x xor k), 0b11111111) 47 | # 1 matches the last output bit 48 | LINS = (1,) 49 | 50 | 51 | 52 | def tobin(x, n): 53 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 54 | 55 | def scalar_bin(a, b): 56 | return int(Integer(a & b).popcount()) 57 | 58 | MASK = 2**R.ntraces - 1 59 | VECMASK = vector(GF(2), [1] * R.ntraces) 60 | 61 | print "Total traces:", R.ntraces, "of size", "%.1fK bits (%d)" % (R.trace_bytes / 1000.0, R.trace_bytes) 62 | 63 | #== Generate predicted vectors from plaintext/ciphertext and key guess 64 | 65 | targets = [] 66 | for si, lin, k in product(BYTE_INDICES, LINS, KS): 67 | target = [] 68 | for p, c in zip(R.pts, R.cts): 69 | if k is None: 70 | if CT_SIDE: 71 | x = ord(c[si]) 72 | else: 73 | x = ord(p[si]) 74 | else: 75 | if CT_SIDE: 76 | x = ord(c[si]) 77 | x = rsbox[x ^ k] 78 | else: 79 | x = ord(p[si]) 80 | x = sbox[x ^ k] 81 | target.append(scalar_bin(x, lin)) 82 | assert len(target) == NTRACES 83 | target = vector(GF(2), target) 84 | targets.append((target, (si, lin, k, 0))) 85 | targets.append((target + VECMASK, (si, lin, k, 1))) 86 | 87 | print "Generated %d target vectors" % len(targets) 88 | 89 | target_mat = matrix(GF(2), [target for target, kinfo in targets]) 90 | 91 | #== Read traces and analyze 92 | candidates = [set() for _ in xrange(16)] 93 | for i_window, vectors in enumerate(R): 94 | print "Window %d" % (i_window+1), "/", R.num_windows, 95 | 96 | print "offset %d-%d (of %d)" % (R.offset*8, R.offset*8 + len(vectors), R.trace_bytes*8) 97 | print " ", len(vectors), "vectors" 98 | 99 | vectors_rev = set(vectors) 100 | print " ", len(vectors_rev), "unique vectors" 101 | print " ", len(targets), "target vectors" 102 | 103 | key_found = False 104 | 105 | columns = [list(tobin(vec, NTRACES)) for vec in vectors_rev if vec not in (0, MASK)] 106 | mat = matrix(GF(2), columns) 107 | 108 | # trick to use kernel of M for quick verification of solution 109 | parity_checker = mat.right_kernel().matrix().transpose() 110 | check = target_mat * parity_checker 111 | check = map(bool, check.rows()) 112 | for parity, (target, kinfo) in zip(check, targets): 113 | if parity: 114 | continue 115 | 116 | # can be done more efficiently using LU factors of mat (shared with left_kernel) 117 | # but happens only when the key is found 118 | # so optimization is not necessary 119 | sol = mat.solve_left(target) 120 | # assert sol * mat == target 121 | 122 | si, lin, k, const1 = kinfo 123 | print "MATCH:", 124 | print "sbox #%d," % si, 125 | print "lin.mask 0x%02x," % lin, 126 | print "key 0x%02x=%r," % (k, chr(k)), 127 | print "negated? %s," % bool(const1), 128 | # linear combination indexes (may be non-unique) 129 | inds = [R.offset + i for i, take in enumerate(sol) if take] 130 | print "indexes", "%d...%d (distance %d)" % (min(inds), max(inds), max(inds)-min(inds)), inds, 131 | print 132 | 133 | candidates[si].add(k) 134 | key_found = True 135 | 136 | if key_found: 137 | print 138 | print "Key candidates found:" 139 | for si, cands in enumerate(candidates): 140 | if cands: 141 | print "S-Box #%d: %s" % (si, ",".join("0x%02x(%r)" % (c, chr(c)) for c in cands)) 142 | print 143 | 144 | if key_found and STOP_ON_FIRST_MATCH: 145 | quit() 146 | 147 | 148 | -------------------------------------------------------------------------------- /synthesis/whitebox/fastcircuit.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "fastcircuit.h" 8 | 9 | FILE *ftrace = NULL; 10 | 11 | int RANDOM_ENABLED = 1; 12 | 13 | // Randomness 14 | void __attribute__ ((constructor)) set_seed_time() { 15 | struct timespec spec; 16 | clock_gettime(CLOCK_REALTIME, &spec); 17 | srandom(spec.tv_sec); 18 | srandom(random() ^ spec.tv_nsec); 19 | } 20 | void set_seed(uint64_t seed) { 21 | srandom(seed); 22 | } 23 | WORD randbit() { 24 | if (RANDOM_ENABLED) 25 | return random() ^ (((uint64_t)random()) << 32); 26 | else 27 | return 0; 28 | } 29 | 30 | 31 | Circuit *load_circuit(char *fname) { 32 | assert(fname); 33 | FILE * fd = fopen(fname, "r"); 34 | assert(fd); 35 | 36 | Circuit *C = malloc(sizeof(Circuit)); 37 | CircuitInfo *I = &C->info; 38 | 39 | assert(sizeof(CircuitInfo) == fread(I, 1, sizeof(CircuitInfo), fd)); 40 | 41 | C->input_addr = malloc(sizeof(ADDR) * I->input_size); 42 | C->output_addr = malloc(sizeof(ADDR) * I->output_size); 43 | assert(C->input_addr); 44 | assert(C->output_addr); 45 | assert(I->input_size == fread(C->input_addr, sizeof(ADDR), I->input_size, fd)); 46 | assert(I->output_size == fread(C->output_addr, sizeof(ADDR), I->output_size, fd)); 47 | 48 | C->opcodes = malloc(sizeof(BYTE) * I->opcodes_size); 49 | assert(C->opcodes); 50 | assert(I->opcodes_size == fread(C->opcodes, sizeof(BYTE), I->opcodes_size, fd)); 51 | 52 | C->ram = malloc(sizeof(WORD) * I->memory); 53 | assert(C->ram); 54 | fclose(fd); 55 | return C; 56 | } 57 | 58 | void free_circuit(Circuit *C) { 59 | free(C->input_addr); 60 | free(C->output_addr); 61 | free(C->opcodes); 62 | free(C->ram); 63 | free(C); 64 | } 65 | 66 | /* 67 | Bits in bytes: MSB to LSB 68 | Bytes in word: LSB to MSB, because will be packed as Little Endian 69 | */ 70 | WORD io_bit(int bit) { 71 | int lo = bit & 7; 72 | bit -= lo; 73 | bit += 7 - lo; 74 | return bit; 75 | } 76 | void circuit_compute(Circuit *C, uint8_t *inp, uint8_t *out, char *trace_filename, int batch) { 77 | CircuitInfo *I = &C->info; 78 | WORD *ram = C->ram; 79 | bzero(ram, I->memory); 80 | 81 | WORD NOTMASK = 0; 82 | for (int j = 0; j < batch; j++) 83 | NOTMASK |= 1 << io_bit(j); 84 | 85 | FILE * ftrace = NULL; 86 | if (trace_filename) { 87 | ftrace = fopen(trace_filename, "w"); 88 | assert(ftrace); 89 | } 90 | int trace_item_bytes = 1; 91 | if (batch > 8) trace_item_bytes = 2; 92 | if (batch > 16) trace_item_bytes = 4; 93 | if (batch > 32) trace_item_bytes = 8; 94 | 95 | assert(1 <= batch && batch <= 64); 96 | int bytes_per_input = (I->input_size + 7) / 8; 97 | int bytes_per_output = (I->output_size + 7) / 8; 98 | 99 | // load input 100 | for (int j = 0; j < batch; j++) { 101 | for (int i = 0; i < I->input_size; i++) { 102 | int byte = i >> 3; 103 | int bit = 7 - (i & 7); 104 | WORD value = (inp[byte]>>bit) & 1; 105 | 106 | if (j == 0) ram[C->input_addr[i]] = 0; 107 | ram[C->input_addr[i]] |= value << io_bit(j); 108 | } 109 | inp += bytes_per_input; 110 | } 111 | 112 | // compute circuit 113 | BYTE *p = C->opcodes; 114 | for(int i = 0; i < I->num_opcodes; i++) { 115 | BYTE op = *p++; 116 | ADDR dst = *((ADDR *)p); p+=2; 117 | ADDR a, b; 118 | switch (op) { 119 | case XOR: 120 | a = *((ADDR *)p); p+=2; 121 | b = *((ADDR *)p); p+=2; 122 | ram[dst] = ram[a] ^ ram[b]; 123 | break; 124 | case AND: 125 | a = *((ADDR *)p); p+=2; 126 | b = *((ADDR *)p); p+=2; 127 | ram[dst] = ram[a] & ram[b]; 128 | break; 129 | case OR: 130 | a = *((ADDR *)p); p+=2; 131 | b = *((ADDR *)p); p+=2; 132 | ram[dst] = ram[a] | ram[b]; 133 | break; 134 | case NOT: 135 | a = *((ADDR *)p); p+=2; 136 | ram[dst] = NOTMASK ^ ram[a]; 137 | break; 138 | case RANDOM: 139 | ram[dst] = randbit(); 140 | break; 141 | default: 142 | printf("unknown opcode %d\n", op); 143 | exit(0); 144 | } 145 | 146 | if (ftrace) { 147 | fwrite(ram+dst, 1, trace_item_bytes, ftrace); 148 | } 149 | } 150 | 151 | // extract output 152 | for (int j = 0; j < batch; j++) { 153 | for (int i = 0; i < I->output_size; i++) { 154 | int byte = i >> 3; 155 | int bit = 7 - (i & 7); 156 | WORD value = (ram[C->output_addr[i]] >> io_bit(j)) & 1; 157 | 158 | if (bit == 7) out[byte] = 0; 159 | out[byte] |= value << bit; 160 | } 161 | out += bytes_per_output; 162 | } 163 | if (ftrace) fclose(ftrace); 164 | } 165 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/Simon/simon.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from cryptools.py.binary import rol, ror, frombin, tobin 4 | from struct import unpack, pack 5 | 6 | u = 0b1111101000100101011000011100110 7 | v = 0b1000111011111001001100001011010 8 | w = 0b1000010010110011111000110111010 9 | 10 | t = 0b01010101010101010101010101010101010101010101010101010101010101 11 | 12 | z0 = (u | (u << 31)) 13 | z1 = (v | (v << 31)) 14 | z2 = (u | (u << 31)) ^ t 15 | z3 = (v | (v << 31)) ^ t 16 | z4 = (w | (w << 31)) ^ t 17 | 18 | ZSEQ_NR = { 19 | (16, 4): (z0, 32), 20 | 21 | (24, 3): (z0, 36), 22 | (24, 4): (z1, 36), 23 | 24 | (32, 3): (z2, 42), 25 | (32, 4): (z3, 44), 26 | 27 | (48, 2): (z2, 52), 28 | (48, 3): (z3, 54), 29 | 30 | (64, 2): (z2, 68), 31 | (64, 3): (z3, 69), 32 | (64, 4): (z4, 72), 33 | } 34 | 35 | 36 | def zget(v, n): 37 | n %= 62 38 | return (v >> (62 - 1 - n)) & 1 39 | 40 | class Simon(object): 41 | W = None 42 | ROTS = 8, 1, 2 43 | INST = {} 44 | 45 | @classmethod 46 | def nrounds(self, M): 47 | return ZSEQ_NR[self.W,M][1] 48 | 49 | @classmethod 50 | def make(self, word_size): 51 | return self.INST[word_size]() 52 | 53 | def encrypt(self, l, r, ks): 54 | for k in ks: 55 | r ^= self.f(l) ^ k 56 | l, r = r, l 57 | return l, r 58 | 59 | def f(self, l): 60 | a = rol(l, self.ROTS[0], self.W) 61 | b = rol(l, self.ROTS[1], self.W) 62 | c = rol(l, self.ROTS[2], self.W) 63 | return (a & b) ^ c 64 | 65 | def key_schedule(self, k, z=None, nr=None): 66 | k = list(k) 67 | M = len(k) 68 | C = (2**self.W-1) ^ 3 69 | zj = z if z is not None else ZSEQ_NR[self.W,M][0] 70 | nr = nr if nr is not None else ZSEQ_NR[self.W,M][1] 71 | for i in xrange(M, nr): 72 | tmp = rol(k[i-1], -3, self.W) 73 | if M == 4: 74 | tmp ^= k[i-3] 75 | tmp ^= rol(tmp, -1, self.W) 76 | 77 | newk = C 78 | newk = newk ^ zget(zj, (i-M) % 62) 79 | newk = newk ^ k[i-M] ^ tmp 80 | 81 | k.append(newk) 82 | return k 83 | 84 | def key_schedule_linear(self, k, nr=None): 85 | k = list(k) 86 | M = len(k) 87 | nr = nr if nr is not None else ZSEQ_NR[self.W,M][1] 88 | for i in xrange(M, nr): 89 | tmp = rol(k[i-1], -3, self.W) 90 | if M == 4: 91 | tmp ^= k[i-3] 92 | tmp ^= rol(tmp, -1, self.W) 93 | 94 | newk = k[i-M] ^ tmp 95 | 96 | k.append(newk) 97 | return k 98 | 99 | 100 | class Simon32(Simon): W = 16 101 | class Simon48(Simon): W = 24 102 | class Simon64(Simon): W = 32 103 | class Simon96(Simon): W = 48 104 | class Simon128(Simon): W = 64 105 | 106 | Simon.INST[32/2] = Simon32 107 | Simon.INST[48/2] = Simon48 108 | Simon.INST[64/2] = Simon64 109 | Simon.INST[96/2] = Simon96 110 | Simon.INST[128/2] = Simon128 111 | 112 | if __name__ == '__main__': 113 | def _u(s): 114 | return tuple(int(w, 16) for w in s.split()) 115 | 116 | def test1(ks, pt, ct): 117 | k = _u(ks)[::-1] # why?! 118 | l, r = _u(pt) 119 | WS = len(pt.split()[0])*4 120 | 121 | class SimonTest(Simon): 122 | W = WS 123 | s = SimonTest() 124 | l, r = s.encrypt(l, r, s.key_schedule(k)) 125 | assert (l, r) == _u(ct), ("%04x:%04x vs %04x:%04x" % (l, r, _u(ct)[0], _u(ct)[1])) 126 | return True 127 | 128 | def test(): 129 | cnt = 0 130 | # Simon32/64 131 | cnt += test1("1918 1110 0908 0100", "6565 6877", "c69b e9bb") 132 | # Simon48/72 133 | cnt += test1("121110 0a0908 020100", "612067 6e696c", "dae5ac 292cac") 134 | # Simon48/96 135 | cnt += test1("1a1918 121110 0a0908 020100", "726963 20646e", "6e06a5 acf156") 136 | # Simon64/96 137 | cnt += test1("13121110 0b0a0908 03020100", "6f722067 6e696c63", "5ca2e27f 111a8fc8") 138 | # Simon64/128 139 | cnt += test1("1b1a1918 13121110 0b0a0908 03020100", "656b696c 20646e75", "44c8fc20 b9dfa07a") 140 | # Simon96/96 141 | cnt += test1("0d0c0b0a0908 050403020100", "2072616c6c69 702065687420", "602807a462b4 69063d8ff082") 142 | # Simon96/144 143 | cnt += test1("151413121110 0d0c0b0a0908 050403020100", "746168742074 73756420666f", "ecad1c6c451e 3f59c5db1ae9") 144 | # Simon128/128 145 | cnt += test1("0f0e0d0c0b0a0908 0706050403020100", "6373656420737265 6c6c657661727420", "49681b1e1e54fe3f 65aa832af84e0bbc") 146 | # Simon128/192 147 | cnt += test1("1716151413121110 0f0e0d0c0b0a0908 0706050403020100", "206572656874206e 6568772065626972", "c4ac61effcdc0d4f 6c9c8d6e2597b85b") 148 | # Simon128/256 149 | cnt += test1("1f1e1d1c1b1a1918 1716151413121110 0f0e0d0c0b0a0908 0706050403020100", "74206e69206d6f6f 6d69732061207369", "8d2b5579afc8a3a0 3bf72a87efe7b868") 150 | 151 | print "%d tests passed" % cnt 152 | 153 | test() 154 | -------------------------------------------------------------------------------- /synthesis/whitebox/masking.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from whitebox.orderer import Orderer, circuit_inputs 4 | from operator import xor 5 | 6 | class MaskingScheme(object): 7 | NOT = OR = XOR = AND = ZERO = ONE = NotImplemented 8 | 9 | def __init__(self, rand, nshares=2): 10 | """rand() -> random bit""" 11 | self.rand = rand 12 | self.nshares = int(nshares) 13 | assert nshares >= 2 # maybe 1 is useful for debugging purposes? 14 | 15 | def encode(self, x): 16 | raise NotImplementedError() 17 | 18 | def decode(self, x): 19 | raise NotImplementedError() 20 | 21 | def refresh(self, x): 22 | raise NotImplementedError() 23 | 24 | def __repr__(self): 25 | return "" % (type(self).__name__, self.nshares, self.rand) 26 | 27 | 28 | class DOM(MaskingScheme): 29 | def encode(self, s): 30 | x = [self.rand() for _ in xrange(self.nshares-1)] 31 | x.append(reduce(xor, x) ^ s) 32 | return tuple(x) 33 | 34 | def decode(self, x): 35 | return reduce(xor, x) 36 | 37 | def XOR(self, x, y): 38 | assert len(x) == len(y) == self.nshares 39 | return tuple(xx ^ yy for xx, yy in zip(x, y)) 40 | 41 | def AND(self, x, y): 42 | assert len(x) == len(y) == self.nshares 43 | matrix = [[xx & yy for yy in y] for xx in x] 44 | for i in xrange(1, self.nshares): 45 | for j in xrange(i + 1, self.nshares): 46 | r = self.rand() 47 | matrix[i][j] ^= r 48 | matrix[j][i] ^= r 49 | return tuple(reduce(xor, row) for row in matrix) 50 | 51 | def NOT(self, x): 52 | assert len(x) == self.nshares 53 | return (~x[0],) + tuple(x[1:]) 54 | 55 | def RANDOM(self): 56 | # more efficient random shares 57 | Bit = self.Bit 58 | return (Bit.const(0),) * (self.nshares - 1) + (Bit(Bit.OP.RANDOM),) 59 | 60 | def refresh(self, x): 61 | raise NotImplementedError() 62 | 63 | class MINQ(MaskingScheme): 64 | def __init__(self, rand): 65 | super(MINQ, self).__init__(rand=rand, nshares=3) 66 | 67 | def encode(self, s): 68 | a = self.rand() 69 | b = self.rand() 70 | c = (a & b) ^ s 71 | return a, b, c 72 | 73 | def decode(self, x): 74 | return (x[0] & x[1]) ^ x[2] 75 | 76 | def rand3(self): 77 | return (self.rand(), self.rand(), self.rand()) 78 | 79 | def refresh(self, x, rs=None): 80 | a, b, c = x 81 | if rs is None: 82 | rs = self.rand3() 83 | ra, rb, rc = rs 84 | ma = ra & (b ^ rc) 85 | mb = rb & (a ^ rc) 86 | rmul = (ra ^ rc) & (rb ^ rc) 87 | rc ^= ma ^ mb ^ rmul 88 | a ^= ra 89 | b ^= rb 90 | c ^= rc 91 | return a, b, c 92 | 93 | def XOR(self, x, y): 94 | rxs = ra, rb, rc = self.rand3() 95 | rys = rd, re, rf = self.rand3() 96 | a, b, c = self.refresh(x, rs=rxs) 97 | d, e, f = self.refresh(y, rs=rys) 98 | x = a ^ d 99 | y = b ^ e 100 | ae = a & e 101 | bd = b & d 102 | z = c ^ f ^ ae ^ bd 103 | return x, y, z 104 | 105 | def AND(self, x, y): 106 | rxs = ra, rb, rc = self.rand3() 107 | rys = rd, re, rf = self.rand3() 108 | a, b, c = self.refresh(x, rs=rxs) 109 | d, e, f = self.refresh(y, rs=rys) 110 | 111 | ma = (b & f) ^ (rc & e) 112 | md = (c & e) ^ (rf & b) 113 | x = rf ^ (a & e) 114 | y = rc ^ (b & d) 115 | ama = a & ma 116 | dmd = d & md 117 | rcrf = rc & rf 118 | cf = c & f 119 | z = ama ^ dmd ^ rcrf ^ cf 120 | return x, y, z 121 | 122 | def NOT(self, x): 123 | return x[0], x[1], ~x[2] 124 | 125 | def RANDOM(self): 126 | # more efficient random shares 127 | Bit = self.Bit 128 | return Bit.const(0), Bit.const(0), Bit(Bit.OP.RANDOM()) 129 | 130 | 131 | 132 | def mask_circuit(ybits, scheme, encode=True, decode=True): 133 | """ 134 | Mask a given circuit with a given masking scheme. 135 | WARNING: assumes absence of constant bits (e.g. using OptBitNode) 136 | """ 137 | scheme.Bit = Bit = type(ybits[0]) 138 | 139 | xbits = circuit_inputs(ybits) 140 | if encode: 141 | xbits_shares = [scheme.encode(xbit) for xbit in xbits] 142 | else: 143 | xbits_shares = [Bit.inputs(xbit.name(), tostr=False) for xbit in xbits] 144 | 145 | shares = dict(zip(xbits, xbits_shares)) # bit -> shares of bit 146 | 147 | for action, bit in Orderer(ybits, quiet=True).compile().code: 148 | if action != "compute": 149 | continue 150 | 151 | func = getattr(scheme, Bit.OP.name[bit.op]) 152 | args = [shares[arg] for arg in bit.args] 153 | res = func(*args) 154 | shares[bit] = res 155 | 156 | ybits_shares = tuple(shares[ybit] for ybit in ybits) 157 | if decode: 158 | return tuple(scheme.decode(yshares) for yshares in ybits_shares) 159 | else: 160 | return ybits_shares 161 | -------------------------------------------------------------------------------- /synthesis/whitebox/orderer.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | class Multiset(object): 4 | def __init__(self): 5 | self.data = {} 6 | 7 | def add(self, obj, num=1): 8 | self.data.setdefault(obj, 0) 9 | self.data[obj] += num 10 | 11 | def remove(self, obj, num=1): 12 | self.data[obj] -= num 13 | assert self.data[obj] >= 0 14 | if self.data[obj] == 0: 15 | del self.data[obj] 16 | 17 | def remove_all(self, obj): 18 | del self.data[obj] 19 | 20 | def items(self): 21 | return self.data.items() 22 | 23 | def __len__(self): 24 | return len(self.data) 25 | 26 | def __contains__(self, obj): 27 | return obj in self.data 28 | 29 | def __iter__(self): 30 | return self.data.__iter__() 31 | 32 | def __nonzero__(self): 33 | return bool(self.data) 34 | 35 | class ComputationOrder(object): 36 | ACTION_COMPUTE = "compute" 37 | ACTION_INPUT = "input" 38 | ACTION_FREE = "free" 39 | ACTION_OUTPUT = "output" 40 | 41 | def __init__(self, code, xbits, ybits): 42 | self.code = code 43 | self.xbits = tuple(xbits) 44 | self.ybits = tuple(ybits) 45 | 46 | def walk_by(self, visitor): 47 | methods = {} 48 | for k, v in type(self).__dict__.items(): 49 | if k.startswith("ACTION_"): 50 | method = getattr(visitor, "action_" + v, None) 51 | if method: 52 | methods[v] = method 53 | 54 | for action, bit in self.code: 55 | if action in methods: 56 | methods[action](bit) 57 | 58 | CO = ComputationOrder 59 | 60 | class Orderer(object): 61 | """ 62 | Orders circuit computations in a sequence. 63 | This class does it simply by Bit.id, can be done differently, e.g. with some randomization 64 | """ 65 | def __init__(self, ybits, quiet=False): 66 | self.xbits = list(circuit_inputs(ybits)) 67 | self.ybits = ybits 68 | 69 | self.quiet = quiet 70 | 71 | def log(self, *args): 72 | if not self.quiet: 73 | print "::", 74 | for arg in args: 75 | print arg, 76 | print 77 | 78 | def compile(self): 79 | self.log("circuit walk") 80 | 81 | visited = set() 82 | using = {} 83 | 84 | q = [] 85 | for b in self.ybits: 86 | q.append(b) 87 | visited.add(b) 88 | 89 | # output bits are used always 90 | using[b] = Multiset() 91 | using[b].add(None) 92 | 93 | while q: 94 | b = q.pop() 95 | for sub in b.args: 96 | if type(sub) == type(b): 97 | if sub not in visited: 98 | visited.add(sub) 99 | q.append(sub) 100 | 101 | if sub not in using: 102 | using[sub] = Multiset() 103 | using[sub].add(b) 104 | 105 | self.log("ordering", len(visited), "nodes") 106 | 107 | order = sorted(visited, key=lambda b: b.id) 108 | ready = set() 109 | freed = set() 110 | code = [] 111 | for b in order: 112 | if b.is_input(): 113 | code.append((CO.ACTION_INPUT, b)) 114 | ready.add(b) 115 | continue 116 | if b.is_const(): 117 | ready.add(b) 118 | continue 119 | 120 | code.append((CO.ACTION_COMPUTE, b)) 121 | ready.add(b) 122 | 123 | for sub in b.args: 124 | if type(sub) != type(b): 125 | continue 126 | assert sub in ready 127 | assert sub not in freed 128 | assert b in using[sub] 129 | using[sub].remove(b) 130 | # using[sub].remove_all(b) 131 | # do not free output bits immediately 132 | if not using[sub]: 133 | code.append((CO.ACTION_FREE, sub)) 134 | freed.add(sub) 135 | 136 | for b in self.ybits: 137 | code.append((CO.ACTION_OUTPUT, b)) 138 | code.append((CO.ACTION_FREE, b)) 139 | 140 | code = tuple(code) 141 | 142 | self.log("code size: %d operations" % len(code)) 143 | 144 | return ComputationOrder(xbits=self.xbits, ybits=self.ybits, code=code) 145 | 146 | def circuit_filter(ybits, filter=None): 147 | visited = set() 148 | q = [] 149 | for b in ybits: 150 | q.append(b) 151 | visited.add(b) 152 | while q: 153 | b = q.pop() 154 | if filter(b): 155 | yield b 156 | for sub in b.args: 157 | if type(sub) == type(b) and sub not in visited: 158 | visited.add(sub) 159 | q.append(sub) 160 | 161 | def circuit_filter_op(ybits, op): 162 | return circuit_filter(ybits, lambda b: b.op == op) 163 | 164 | def circuit_inputs(ybits): 165 | res = list(circuit_filter(ybits, filter=lambda b: b.is_input())) 166 | res.sort(key=lambda b: b.id) 167 | return tuple(res) 168 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/bit.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from ops import OP 4 | 5 | class Bit(object): 6 | COUNTER = 0 7 | 8 | meta = {} 9 | 10 | def __init__(self, op, *args, **kwargs): 11 | self.op = op 12 | self.args = list(args) 13 | self.meta = self.meta.copy() 14 | self.meta.update(kwargs) 15 | self._h = None 16 | 17 | if self.op in (OP.ONE, OP.ZERO): 18 | self.id = float("+inf") 19 | else: 20 | self.id = self.COUNTER 21 | type(self).COUNTER += 1 22 | 23 | def make_func(op): 24 | if op in OP.symmetric: 25 | def f(a, b): 26 | if isinstance(b, int) or isinstance(b, long): 27 | if b & 1 == OP.neutral.get(op, None): 28 | # print "NEUT" 29 | return a 30 | if b & 1 == 1 and op == OP.XOR: 31 | # print "XOR 1 = NOT" 32 | return ~a 33 | b = Bit(OP.ONE) if b & 1 else Bit(OP.ZERO) 34 | return Bit(op, a, b) 35 | elif b.is_const() and b.const() == OP.neutral.get(op, None): return a 36 | elif a.is_const() and a.const() == OP.neutral.get(op, None): return b 37 | 38 | # WARNING: added later.. 39 | elif a.op == OP.ZERO and op == OP.AND: return Bit.ZERO 40 | elif b.op == OP.ZERO and op == OP.AND: return Bit.ZERO 41 | elif a.op == OP.ONE and op == OP.OR: return Bit.ONE 42 | elif b.op == OP.ONE and op == OP.OR: return Bit.ONE 43 | 44 | # WARNING: too optimizing? 45 | elif op == OP.XOR and a.is_const() and b.is_const(): return Bit.make_const(a.const() ^ b.const()) 46 | elif op == OP.AND and a.is_const() and b.is_const(): return Bit.make_const(a.const() & b.const()) 47 | elif op == OP.OR and a.is_const() and b.is_const(): return Bit.make_const(a.const() | b.const()) 48 | elif op == OP.XOR and b.op == OP.ONE: return ~a 49 | elif op == OP.XOR and a.op == OP.ONE: return ~b 50 | return Bit(op, a, b) 51 | else: 52 | def f(a, b): 53 | return Bit(op, a, b) 54 | return f 55 | Xor = __xor__ = __rxor__ = make_func(OP.XOR) 56 | And = __and__ = __rand__ = make_func(OP.AND) 57 | Or = __or__ = __ror__ = make_func(OP.OR) 58 | Nxor = make_func(OP.NXOR) 59 | Nand = make_func(OP.NAND) 60 | Nor = make_func(OP.NOR) 61 | 62 | def __invert__(self): 63 | if self.op == OP.NOT: 64 | return self.args[0] 65 | if self.is_const(): 66 | if self.op == OP.ONE: 67 | return Bit(OP.ZERO) 68 | else: 69 | return Bit(OP.ONE) 70 | return Bit(OP.NOT, self) 71 | 72 | def __str__(self): 73 | if self.op == OP.INPUT: return str(self.args[0]) 74 | if self.op == OP.ONE: return "1" 75 | if self.op == OP.ZERO: return "0" 76 | if self.args: 77 | return "(" + (" " + OP.name[self.op] + " ").join(map(str, self.args)) + ")" 78 | return OP.name[self.op] 79 | 80 | def eval(self, input, acc=None): 81 | if acc is None: 82 | acc = {} 83 | if self not in acc: 84 | if self.op == OP.INPUT: 85 | acc[self] = input[self.args[0]] 86 | else: 87 | acc[self] = OP.eval(self.op, [v.eval(input, acc=acc) for v in self.args]) 88 | return acc[self] 89 | 90 | def flatten(self, out=None): 91 | if out is None: 92 | out = set() 93 | if self in out: 94 | return 95 | if self.op != OP.INPUT: 96 | for bit in self.args: 97 | bit.flatten(out=out) 98 | out.add(self) 99 | return out 100 | 101 | def __hash__(self): 102 | return hash(id(self)) 103 | return hash(self.id) 104 | if self._h is None: 105 | self._h = hash((self.op,) + tuple(hash(v) for v in self.args)) 106 | return self._h 107 | 108 | def function_hash(self): 109 | return hash(self) 110 | # def __eq__(self, other): 111 | # return hash(self) == hash(other) 112 | 113 | def is_const(self): 114 | return self.op in (OP.ONE, OP.ZERO) 115 | 116 | def is_input(self): 117 | return self.op == OP.INPUT 118 | 119 | def is_primitive(self): 120 | return self.op in OP.primitive 121 | 122 | def const(self): 123 | assert self.op in (OP.ONE, OP.ZERO) 124 | return int(self.op == OP.ONE) 125 | 126 | @classmethod 127 | def make_const(self, v): 128 | return Bit(OP.ONE) if v else Bit(OP.ZERO) 129 | 130 | Bit.ONE = Bit(OP.ONE) 131 | Bit.ZERO = Bit(OP.ZERO) 132 | 133 | def BitVar(name): 134 | return Bit(OP.INPUT, name) 135 | 136 | def VarVec(name, n): 137 | # return [BitVar(name % i) for i in xrange(n)] 138 | return [BitVar((name, i)) for i in xrange(n)] 139 | 140 | if __name__ == '__main__': 141 | x = [Bit(OP.INPUT, i) for i in xrange(8)] 142 | y = ~(x[0] ^ x[1] & x[2]) 143 | for v in y.flatten(): 144 | print v 145 | 146 | y = ~(x[0] ^ x[1] & x[2]) 147 | assert 1 == y.eval({0: 1, 1: 1, 2: 1}) 148 | y = (x[0] ^ x[1] & x[2]) 149 | assert 0 == y.eval({0: 1, 1: 1, 2: 1}) 150 | -------------------------------------------------------------------------------- /synthesis/attacks/analyze_exact.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys, os, string 5 | from itertools import product 6 | from collections import defaultdict 7 | 8 | from sbox import sbox, rsbox 9 | from reader import Reader 10 | 11 | 12 | #== Configuration 13 | 14 | # go from the end of the traces if we attack last S-Boxes ? 15 | REVERSE = False # not supported yet 16 | 17 | NTRACES = int(sys.argv[1]) 18 | WINDOW = int(sys.argv[2]) 19 | STEP = WINDOW 20 | if len(sys.argv) >= 4: 21 | STEP = int(sys.argv[3]) 22 | 23 | R = Reader(ntraces=NTRACES, 24 | window=WINDOW, 25 | step=STEP, 26 | packed=True, 27 | reverse=REVERSE, 28 | dir="./traces") 29 | 30 | 31 | STOP_ON_FIRST_MATCH = 0 32 | ONE_CANDIDATE_PER_SBOX = True 33 | 34 | # second order should break 1-st order linear masking 35 | ENABLE_SECOND_ORDER = 0 36 | 37 | # attack last S-Box? 38 | CT_SIDE = REVERSE 39 | 40 | # which/how many S-Boxes to attack 41 | BYTE_INDICES = range(16) # all 42 | # BYTE_INDICES = range(3) # only first 3 43 | 44 | # charset for key bytes 45 | KS = range(256) 46 | # KS = map(ord, string.printable) # only printable characters 47 | 48 | # linear masks to check after S-Box, for example 49 | # 0xff will try to match scalar_product(SBox(x xor k), 0b11111111) 50 | # 1 matches the last output bit 51 | LINS = (1, 2, 4, 8, 16, 32, 64, 127, 255) 52 | LINS = (1,) 53 | # LINS = range(1, 256) 54 | 55 | 56 | def scalar_bin(a, b): 57 | v = a & b 58 | res = 0 59 | while v: 60 | res ^= v & 1 61 | v >>= 1 62 | return res 63 | 64 | MASK = 2**R.ntraces - 1 65 | 66 | print "Total traces:", R.ntraces, "of size", "%.1fK bits (%d)" % (R.trace_bytes / 1000.0, R.trace_bytes) 67 | 68 | #== Generate predicted vectors from plaintext/ciphertext and key guess 69 | 70 | targets = [] 71 | for si, lin, k in product(BYTE_INDICES, LINS, KS): 72 | target = 0 73 | for p, c in zip(R.pts, R.cts): 74 | if k is None: 75 | if CT_SIDE: 76 | x = ord(c[si]) 77 | else: 78 | x = ord(p[si]) 79 | else: 80 | if CT_SIDE: 81 | x = ord(c[si]) 82 | x = rsbox[x ^ k] 83 | else: 84 | x = ord(p[si]) 85 | x = sbox[x ^ k] 86 | target = (target << 1) | scalar_bin(x, lin) 87 | 88 | targets.append((target, (si, lin, k, 0))) 89 | targets.append((target ^ MASK, (si, lin, k, 1))) 90 | 91 | print "Generated %d target vectors" % len(targets) 92 | 93 | #== Read traces and analyze 94 | for i_window, vectors in enumerate(R): 95 | print "Window %d" % (i_window+1), "/", R.num_windows, 96 | 97 | print "offset %d-%d (of %d)" % (R.offset*8, R.offset*8 + len(vectors), R.trace_bytes*8) 98 | print " ", len(vectors), "vectors" 99 | 100 | vectors_rev = defaultdict(list) 101 | for off, v in enumerate(vectors): 102 | vectors_rev[v].append(R.offset + off) 103 | 104 | print " ", len(vectors_rev), "unique vectors" 105 | print " ", len(targets), "target vectors" 106 | 107 | candidates = [set() for _ in xrange(16)] 108 | key_found = False 109 | 110 | for target, kinfo in targets: 111 | si, lin, k, const1 = kinfo 112 | if ONE_CANDIDATE_PER_SBOX and candidates[si]: 113 | continue 114 | 115 | # single value 116 | if target in vectors_rev: 117 | print "MATCH (SINGLE):", 118 | print "sbox #%d," % si, 119 | print "lin.mask 0x%02x," % lin, 120 | print "key 0x%02x=%r," % (k, chr(k)), 121 | print "negated? %s," % bool(const1), 122 | # linear combination indexes (may be non-unique) 123 | inds = vectors_rev[target][:10] 124 | print "indexes", "(%d total)" % len(vectors_rev[target]), inds, 125 | print 126 | 127 | candidates[si].add(k) 128 | key_found = True 129 | 130 | if ENABLE_SECOND_ORDER: 131 | # shared in 2 shares 132 | for v1 in vectors_rev: 133 | if v1 in (0, MASK): 134 | continue 135 | v2 = target ^ v1 136 | if v2 in vectors_rev: 137 | print "MATCH (DOUBLE):", 138 | print "sbox #%d," % si, 139 | print "lin.mask 0x%02x," % lin, 140 | print "key 0x%02x=%r," % (k, chr(k)), 141 | print "negated? %s," % bool(const1), 142 | # linear combination indexes (may be non-unique) 143 | inds1 = vectors_rev[v1][:5] 144 | inds2 = vectors_rev[v2][:5] 145 | print "indexes", "(%d and %d total)" % (len(vectors_rev[v1]), len(vectors_rev[v2])), inds1, inds2 146 | print 147 | 148 | candidates[si].add(k) 149 | key_found = True 150 | 151 | print " ", " ", [divmod(v, 8) for v in vectors_rev[v1][:10]] 152 | print " ", " ", [divmod(v, 8) for v in vectors_rev[v2][:10]] 153 | print 154 | 155 | candidates[si].add(k) 156 | key_found = True 157 | 158 | if key_found: 159 | print 160 | print "Key candidates found:" 161 | for si, cands in enumerate(candidates): 162 | if cands: 163 | print "S-Box #%d: %s" % (si, ",".join("0x%02x(%r)" % (c, chr(c)) for c in cands)) 164 | print 165 | 166 | if key_found and STOP_ON_FIRST_MATCH: 167 | quit() 168 | 169 | 170 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/analyze_linalg_1st.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sage 2 | #-*- coding:utf-8 -*- 3 | 4 | from sage.all import * 5 | 6 | import sys, os, string 7 | from itertools import product 8 | 9 | from sbox import sbox, rsbox 10 | from reader import Reader 11 | 12 | 13 | #== Configuration 14 | 15 | NTRACES = int(sys.argv[1]) 16 | WINDOW = int(sys.argv[2]) 17 | assert NTRACES > WINDOW, "window should be smaller than number of traces to avoid false-positives" 18 | 19 | STEP = WINDOW / 4 20 | 21 | STOP_ON_FIRST_MATCH = 0 22 | 23 | # attack last S-Box? 24 | CT_SIDE = 0 25 | # go from the end of the traces if we attack last S-Boxes 26 | REVERSE = CT_SIDE 27 | 28 | 29 | # which/how many S-Boxes to attack 30 | # BYTE_INDICES = range(16) # all 31 | BYTE_INDICES = range(3) # only first 3 32 | 33 | # charset for key bytes 34 | KS = range(256) 35 | # KS = map(ord, string.printable) # only printable characters 36 | 37 | # linear masks to check after S-Box, for example 38 | # 0xff will try to match scalar_product(SBox(x xor k), 0b11111111) 39 | # 1 matches the last output bit 40 | LINS = (1,) 41 | 42 | 43 | 44 | def tobin(x, n): 45 | return tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 46 | 47 | def scalar_bin(a, b): 48 | return int(Integer(a & b).popcount()) 49 | 50 | MASK = 2**NTRACES - 1 51 | VECMASK = vector(GF(2), [1] * NTRACES) 52 | 53 | 54 | #== Read plaintexts/ciphertexts and prepare trace readers 55 | 56 | pts = [] 57 | cts = [] 58 | readers = [] 59 | 60 | for i in xrange(NTRACES): 61 | f_trace = "./traces/%04d.bin" % i 62 | f_pt = "./traces/%04d.pt" % i 63 | f_ct = "./traces/%04d.ct" % i 64 | pt = open(f_pt).read() 65 | ct = open(f_ct).read() 66 | pts.append(pt) 67 | cts.append(ct) 68 | readers.append(Reader(f_trace, reverse=REVERSE, window=WINDOW, step=STEP)) 69 | 70 | traces_size = os.stat(f_trace).st_size 71 | print "Total traces:", NTRACES, "of size", "%.1fK (%d)" % (traces_size / 1000.0, traces_size) 72 | 73 | 74 | #== Generate predicted vectors from plaintext/ciphertext and key guess 75 | 76 | targets = [] 77 | for si, lin, k in product(BYTE_INDICES, LINS, KS): 78 | target = [] 79 | for p, c in zip(pts, cts): 80 | if k is None: 81 | if CT_SIDE: 82 | x = ord(c[si]) 83 | else: 84 | x = ord(p[si]) 85 | else: 86 | if CT_SIDE: 87 | x = ord(c[si]) 88 | x = rsbox[x ^ k] 89 | else: 90 | x = ord(p[si]) 91 | x = sbox[x ^ k] 92 | target.append(scalar_bin(x, lin)) 93 | assert len(target) == NTRACES 94 | target = vector(GF(2), target) 95 | targets.append((target, (si, lin, k, 0))) 96 | targets.append((target + VECMASK, (si, lin, k, 1))) 97 | 98 | print "Generated %d target vectors" % len(targets) 99 | 100 | target_mat = matrix(GF(2), [target for target, kinfo in targets]) 101 | 102 | #== Read traces and analyze 103 | 104 | n_windows = (traces_size - WINDOW - 1) / STEP 105 | i_window = 0 106 | while 1: 107 | i_window += 1 108 | if REVERSE: 109 | print "Window %d" % (n_windows - i_window + 1), "/", n_windows, 110 | else: 111 | print "Window %d" % i_window, "/", n_windows, 112 | 113 | vectors = [] 114 | for i_reader, reader in enumerate(readers): 115 | try: 116 | wnd_off, wnd = next(reader) 117 | except StopIteration: 118 | print "No window %d for reader %d (Finished?)" % (i_window, i_reader) 119 | quit() 120 | assert wnd 121 | 122 | # not sure if working with longs is faster than with arrays or Sage's vectors.. 123 | if not vectors: 124 | vectors = [0 for _ in xrange(len(wnd))] 125 | for i, b in enumerate(wnd): 126 | val = ord(b) & 1 127 | vectors[i] = (vectors[i] << 1) | val 128 | 129 | print "offset %d-%d" % (wnd_off, wnd_off+WINDOW-1) 130 | print " ", len(vectors), "vectors" 131 | 132 | vectors_rev = set(vectors) 133 | print " ", len(vectors_rev), "unique vectors" 134 | print " ", len(targets), "target vectors" 135 | 136 | candidates = [set() for _ in xrange(16)] 137 | key_found = False 138 | 139 | columns = [list(tobin(vec, NTRACES)) for vec in vectors_rev if vec not in (0, MASK)] 140 | mat = matrix(GF(2), columns) 141 | 142 | # trick to use kernel of M for quick verification of solution 143 | parity_checker = mat.right_kernel().matrix().transpose() 144 | check = target_mat * parity_checker 145 | check = map(bool, check.rows()) 146 | for parity, (target, kinfo) in zip(check, targets): 147 | if parity: 148 | continue 149 | 150 | # can be done more efficiently using LU factors of mat (shared with left_kernel) 151 | # but happens only when the key is found 152 | # so optimization is not necessary 153 | sol = mat.solve_left(target) 154 | # assert sol * mat == target 155 | 156 | si, lin, k, const1 = kinfo 157 | print "MATCH:", 158 | print "sbox #%d," % si, 159 | print "lin.mask 0x%02x," % lin, 160 | print "key 0x%02x=%r," % (k, chr(k)), 161 | print "negated? %s," % bool(const1), 162 | # linear combination indexes (may be non-unique) 163 | inds = [wnd_off + i for i, take in enumerate(sol) if take] 164 | print "indexes", "%d...%d (distance %d)" % (min(inds), max(inds), max(inds)-min(inds)), inds, 165 | print 166 | 167 | candidates[si].add(k) 168 | key_found = True 169 | 170 | if key_found: 171 | print 172 | print "Key candidates found:" 173 | for si, cands in enumerate(candidates): 174 | if cands: 175 | print "S-Box #%d: %s" % (si, ",".join("0x%02x(%r)" % (c, chr(c)) for c in cands)) 176 | print 177 | 178 | if key_found and STOP_ON_FIRST_MATCH: 179 | quit() 180 | 181 | 182 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/containers.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import operator 4 | 5 | class Vector(list): 6 | ZERO = 0 7 | WIDTH = None 8 | 9 | @classmethod 10 | def make(cls, lst): 11 | lst = list(lst) 12 | if cls.WIDTH is not None: 13 | assert len(lst) == cls.WIDTH 14 | return cls(lst) 15 | 16 | def split(self, n=2): 17 | assert len(self) % n == 0 18 | w = len(self) // n 19 | return Vector(self.make(self[i:i+w]) for i in xrange(0, len(self), w)) 20 | 21 | def concat(self, *lst): 22 | v = list(self) 23 | for t in lst: 24 | v += list(t) 25 | return self.make(v) 26 | 27 | def rol(self, n=1): 28 | n %= len(self) 29 | return self.make(self[n:] + self[:n]) 30 | 31 | def ror(self, n=1): 32 | return self.rol(-n) 33 | 34 | def shl(self, n=1): 35 | assert n >= 0 36 | n = min(n, len(self)) 37 | return self.make(list(self[n:]) + [self._zero() for i in xrange(n)]) 38 | 39 | def shr(self, n=1): 40 | assert n >= 0 41 | n = min(n, len(self)) 42 | return self.make([self._zero() for i in xrange(n)] + list(self[:-n])) 43 | 44 | def _zero(self): 45 | """method because sometimes need different instances""" 46 | return self.ZERO 47 | 48 | def __repr__(self): 49 | return "" % (len(self), list(self)) 50 | 51 | def flatten(self): 52 | if isinstance(self[0], Vector): 53 | return self[0].concat(*self[1:]) 54 | return reduce(operator.add, list(self)) 55 | 56 | def permute(self, perm, inverse=False): 57 | """ 58 | Perm contains indexes in original vector 59 | Example: 60 | vec = [0, 1, 2, 3] 61 | perm = [1, 2, 3, 0] 62 | res = [1, 2, 3, 0] 63 | """ 64 | if not inverse: 65 | lst = [self[i] for i in perm] 66 | else: 67 | lst = [None] * len(self) 68 | for i, j in enumerate(perm): 69 | lst[j] = self[i] 70 | return self.make(lst) 71 | 72 | def map(self, f, with_coord=False): 73 | if with_coord: 74 | return self.make(f(i, v) for i, v in enumerate(self)) 75 | else: 76 | return self.make(f(v) for v in self) 77 | 78 | def __xor__(self, other): 79 | assert isinstance(other, Vector) 80 | assert len(self) == len(other) 81 | return self.make(a ^ b for a, b in zip(self, other)) 82 | 83 | def __or__(self, other): 84 | assert isinstance(other, Vector) 85 | assert len(self) == len(other) 86 | return self.make(a | b for a, b in zip(self, other)) 87 | 88 | def __and__(self, other): 89 | assert isinstance(other, Vector) 90 | assert len(self) == len(other) 91 | return self.make(a & b for a, b in zip(self, other)) 92 | 93 | def set(self, x, val): 94 | return self.make(v if i != x else val for i, v in enumerate(self)) 95 | 96 | 97 | class Rect(object): 98 | def __init__(self, vec, h=None, w=None): 99 | assert h or w 100 | if h: 101 | w = len(vec) // h 102 | elif w: 103 | h = len(vec) // w 104 | assert w * h == len(vec) 105 | self.w, self.h = w, h 106 | 107 | self.lst = [] 108 | for i in xrange(0, len(vec), w): 109 | self.lst.append(list(vec[i:i+w])) 110 | 111 | @classmethod 112 | def from_rect(cls, rect): 113 | self = object.__new__(cls) 114 | self.lst = rect 115 | self.h = len(rect) 116 | self.w = len(rect[0]) 117 | return self 118 | 119 | def __getitem__(self, pos): 120 | y, x = pos 121 | return self.lst[y][x] 122 | 123 | def __setitem__(self, pos, val): 124 | y, x = pos 125 | self.lst[y][x] = val 126 | 127 | def row(self, i): 128 | return Vector(self.lst[i]) 129 | 130 | def col(self, i): 131 | return Vector(self.lst[y][i] for y in xrange(self.h)) 132 | 133 | def diag(self, x): 134 | assert self.w == self.h 135 | return Vector(self.lst[i][(x+i) % self.w] for i in xrange(self.h)) 136 | 137 | def set_row(self, y, vec): 138 | for x in xrange(self.w): 139 | self.lst[y][x] = vec[x] 140 | return self 141 | 142 | def set_col(self, x, vec): 143 | for y in xrange(self.h): 144 | self.lst[y][x] = vec[y] 145 | return self 146 | 147 | def set_diag(self, x, vec): 148 | assert self.w == self.h 149 | for i in xrange(self.h): 150 | self.lst[i][(x+i) % self.w] = vec[i] 151 | return self 152 | 153 | def apply(self, f, with_coord=False): 154 | for y in xrange(self.h): 155 | if with_coord: 156 | self.lst[y] = [f(y, x, v) for x, v in enumerate(self.lst[y])] 157 | else: 158 | self.lst[y] = map(f, self.lst[y]) 159 | return self 160 | 161 | def apply_row(self, x, func): 162 | return self.set_row(x, func(self.row(x))) 163 | 164 | def apply_col(self, x, func): 165 | return self.set_col(x, func(self.col(x))) 166 | 167 | def apply_diag(self, x, func): 168 | assert self.w == self.h 169 | return self.set_diag(x, func(self.diag(x))) 170 | 171 | def flatten(self): 172 | lst = [] 173 | for v in self.lst: 174 | lst += v 175 | return Vector(lst) 176 | 177 | def zipwith(self, f, other): 178 | assert isinstance(other, Rect) 179 | assert self.h == other.h 180 | assert self.w == other.w 181 | return Rect( 182 | [f(a, b) for a, b in zip(self.flatten(), other.flatten())], 183 | h=self.h, w=self.w 184 | ) 185 | 186 | 187 | def transpose(self): 188 | rect = [[self.lst[y][x] for y in xrange(self.h)] for x in xrange(self.w)] 189 | return Rect.from_rect(rect=rect) 190 | 191 | def __repr__(self): 192 | return "" % (self.h, self.w) 193 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/attacks/analyze_exact.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | #-*- coding:utf-8 -*- 3 | 4 | import sys, os, string 5 | from itertools import product 6 | from collections import defaultdict 7 | 8 | from sbox import sbox, rsbox 9 | from reader import Reader 10 | 11 | 12 | #== Configuration 13 | 14 | NTRACES = int(sys.argv[1]) 15 | WINDOW = int(sys.argv[2]) 16 | 17 | STEP = WINDOW 18 | 19 | STOP_ON_FIRST_MATCH = 0 20 | 21 | # second order should break 1-st order linear masking 22 | ENABLE_SECOND_ORDER = False 23 | 24 | # attack last S-Box? 25 | CT_SIDE = 0 26 | # go from the end of the traces if we attack last S-Boxes 27 | REVERSE = CT_SIDE 28 | 29 | 30 | # which/how many S-Boxes to attack 31 | BYTE_INDICES = range(16) # all 32 | # BYTE_INDICES = range(3) # only first 3 33 | 34 | # charset for key bytes 35 | KS = range(256) 36 | # KS = map(ord, string.printable) # only printable characters 37 | 38 | # linear masks to check after S-Box, for example 39 | # 0xff will try to match scalar_product(SBox(x xor k), 0b11111111) 40 | # 1 matches the last output bit 41 | LINS = (1, 2, 4, 8, 16, 32, 64, 127, 255) 42 | 43 | 44 | 45 | def scalar_bin(a, b): 46 | v = a & b 47 | res = 0 48 | while v: 49 | res ^= v & 1 50 | v >>= 1 51 | return res 52 | 53 | MASK = 2**NTRACES - 1 54 | 55 | 56 | #== Read plaintexts/ciphertexts and prepare trace readers 57 | 58 | pts = [] 59 | cts = [] 60 | readers = [] 61 | 62 | for i in xrange(NTRACES): 63 | f_trace = "./traces/%04d.bin" % i 64 | f_pt = "./traces/%04d.pt" % i 65 | f_ct = "./traces/%04d.ct" % i 66 | pt = open(f_pt).read() 67 | ct = open(f_ct).read() 68 | pts.append(pt) 69 | cts.append(ct) 70 | readers.append(Reader(f_trace, reverse=REVERSE, window=WINDOW, step=STEP)) 71 | 72 | traces_size = os.stat(f_trace).st_size 73 | print "Total traces:", NTRACES, "of size", "%.1fK (%d)" % (traces_size / 1000.0, traces_size) 74 | 75 | 76 | #== Generate predicted vectors from plaintext/ciphertext and key guess 77 | 78 | targets = [] 79 | for si, lin, k in product(BYTE_INDICES, LINS, KS): 80 | target = 0 81 | for p, c in zip(pts, cts): 82 | if k is None: 83 | if CT_SIDE: 84 | x = ord(c[si]) 85 | else: 86 | x = ord(p[si]) 87 | else: 88 | if CT_SIDE: 89 | x = ord(c[si]) 90 | x = rsbox[x ^ k] 91 | else: 92 | x = ord(p[si]) 93 | x = sbox[x ^ k] 94 | target = (target << 1) | scalar_bin(x, lin) 95 | 96 | targets.append((target, (si, lin, k, 0))) 97 | targets.append((target ^ MASK, (si, lin, k, 1))) 98 | 99 | print "Generated %d target vectors" % len(targets) 100 | 101 | 102 | #== Read traces and analyze 103 | 104 | n_windows = (traces_size - WINDOW - 1) / STEP 105 | i_window = 0 106 | while 1: 107 | i_window += 1 108 | if REVERSE: 109 | print "Window %d" % (n_windows - i_window + 1), "/", n_windows, 110 | else: 111 | print "Window %d" % i_window, "/", n_windows, 112 | 113 | vectors = [] 114 | for i_reader, reader in enumerate(readers): 115 | try: 116 | wnd_off, wnd = next(reader) 117 | except StopIteration: 118 | print "No window %d for reader %d (Finished?)" % (i_window, i_reader) 119 | quit() 120 | assert wnd 121 | 122 | # not sure if working with longs is faster than with arrays 123 | if not vectors: 124 | vectors = [0 for _ in xrange(len(wnd))] 125 | for i, b in enumerate(wnd): 126 | val = ord(b) & 1 127 | vectors[i] = (vectors[i] << 1) | val 128 | 129 | print "offset %d-%d" % (wnd_off, wnd_off+WINDOW-1) 130 | print " ", len(vectors), "vectors" 131 | 132 | vectors_rev = defaultdict(list) 133 | for off, v in enumerate(vectors): 134 | vectors_rev[v].append(wnd_off + off) 135 | 136 | print " ", len(vectors_rev), "unique vectors" 137 | print " ", len(targets), "target vectors" 138 | 139 | candidates = [set() for _ in xrange(16)] 140 | key_found = False 141 | 142 | 143 | for target, kinfo in targets: 144 | 145 | # single value 146 | if target in vectors_rev: 147 | si, lin, k, const1 = kinfo 148 | print "MATCH (SINGLE):", 149 | print "sbox #%d," % si, 150 | print "lin.mask 0x%02x," % lin, 151 | print "key 0x%02x=%r," % (k, chr(k)), 152 | print "negated? %s," % bool(const1), 153 | # linear combination indexes (may be non-unique) 154 | inds = vectors_rev[target][:10] 155 | print "indexes", "(%d total)" % len(vectors_rev[target]), inds, 156 | print 157 | 158 | candidates[si].add(k) 159 | key_found = True 160 | 161 | if ENABLE_SECOND_ORDER: 162 | # shared in 2 shares 163 | for v1 in vectors_rev: 164 | if v1 in (0, MASK): 165 | continue 166 | v2 = target ^ v1 167 | if v2 in vectors_rev: 168 | print "MATCH (DOUBLE):", 169 | print "sbox #%d," % si, 170 | print "lin.mask 0x%02x," % lin, 171 | print "key 0x%02x=%r," % (k, chr(k)), 172 | print "negated? %s," % bool(const1), 173 | # linear combination indexes (may be non-unique) 174 | inds1 = vectors_rev[v1][:5] 175 | inds2 = vectors_rev[v2][:5] 176 | print "indexes", "(%d and %d total)" % (len(vectors_rev[v1]), len(vectors_rev[v2])), inds1, inds2 177 | print 178 | 179 | candidates[si].add(k) 180 | key_found = True 181 | 182 | print " ", " ", [divmod(v, 8) for v in vectors_rev[v1][:10]] 183 | print " ", " ", [divmod(v, 8) for v in vectors_rev[v2][:10]] 184 | print 185 | 186 | candidates[si].add(k) 187 | key_found = True 188 | 189 | if key_found: 190 | print 191 | print "Key candidates found:" 192 | for si, cands in enumerate(candidates): 193 | if cands: 194 | print "S-Box #%d: %s" % (si, ",".join("0x%02x(%r)" % (c, chr(c)) for c in cands)) 195 | print 196 | 197 | if key_found and STOP_ON_FIRST_MATCH: 198 | quit() 199 | 200 | 201 | -------------------------------------------------------------------------------- /synthesis/whitebox/tree/orderer.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | from collections import Counter, defaultdict 4 | from Queue import PriorityQueue 5 | from random import randint, shuffle 6 | 7 | from node import OP 8 | 9 | 10 | class Multiset(object): 11 | def __init__(self): 12 | self.data = {} 13 | 14 | def add(self, obj, num=1): 15 | self.data.setdefault(obj, 0) 16 | self.data[obj] += num 17 | 18 | def remove(self, obj, num=1): 19 | self.data[obj] -= num 20 | assert self.data[obj] >= 0 21 | if self.data[obj] == 0: 22 | del self.data[obj] 23 | 24 | def remove_all(self, obj): 25 | del self.data[obj] 26 | 27 | def items(self): 28 | return self.data.items() 29 | 30 | def __len__(self): 31 | return len(self.data) 32 | 33 | def __contains__(self, obj): 34 | return obj in self.data 35 | 36 | def __iter__(self): 37 | return self.data.__iter__() 38 | 39 | class ComputationOrder(object): 40 | ACTION_COMPUTE = "compute" 41 | ACTION_FREE = "free" 42 | # ACTION_ALLOC = "alloc" 43 | 44 | def __init__(self, code, xbits, ybits): 45 | self.code = code 46 | self.xbits = tuple(xbits) 47 | self.ybits = tuple(ybits) 48 | 49 | def max_state(self): 50 | mx = 0 51 | counter = 0 52 | for action, _ in self.code: 53 | if action == self.ACTION_FREE: 54 | counter -= 1 55 | else: 56 | counter += 1 57 | mx = max(mx, counter) 58 | return mx 59 | 60 | CO = ComputationOrder 61 | 62 | class Orderer(object): 63 | def __init__(self, xbits, ybits, quiet=False): 64 | self.ybits = ybits 65 | self.xbits = list(xbits) 66 | 67 | self.using = {} 68 | self.indeg = Counter() 69 | self.queue = [] 70 | self.sequential = PriorityQueue() 71 | self.ready = PriorityQueue() 72 | self.quiet = quiet 73 | 74 | def compile(self, max_id_rush): 75 | self.composite = set() 76 | 77 | if not self.quiet: 78 | print ":: CIRCUIT WALK START" 79 | self.visited = set() 80 | for b in self.ybits: 81 | self.dfs(b) 82 | del self.visited 83 | 84 | if not self.quiet: 85 | print ":: ORDERING START" 86 | code = tuple(self.generate(max_id_rush=max_id_rush)) 87 | # print ":: CODE SIZE %d OPERATIONS" % len(code) 88 | return ComputationOrder(xbits=self.xbits, ybits=self.ybits, code=code) 89 | 90 | def dfs(self, b): 91 | if b in self.visited: 92 | return 93 | self.sequential.put((b.id, b)) 94 | self.visited.add(b) 95 | 96 | if b.is_primitive(): 97 | self.indeg[b] = 0 98 | self.ready.put((b.id, b)) 99 | if b.is_const(): 100 | # assert not b.is_const(), "not really needed assert, by better not to have consts for good orderings" 101 | print "WARNING", "not really needed assert, by better not to have consts for good orderings" 102 | for par in self.using[b]: 103 | print OP.name[par.op], OP.name[b.op] 104 | quit(1) 105 | 106 | 107 | if b.is_input(): 108 | assert b.args[0] in [bb.args[0] for bb in self.xbits], b.args[0] 109 | assert b in self.xbits, b.args[0] 110 | return 111 | 112 | for sub in b.args: 113 | if sub not in self.using: 114 | self.using[sub] = Multiset() 115 | self.using[sub].add(b) 116 | self.dfs(sub) 117 | 118 | self.indeg[b] = len(b.args) 119 | assert len(b.args) > 0 120 | self.composite.add(b) 121 | 122 | def generate(self, max_id_rush): 123 | # outputs are used and never can be freed 124 | for b in self.ybits: 125 | if b not in self.using: 126 | self.using[b] = Multiset() 127 | self.using[b].add(None) 128 | 129 | self.visited = set() 130 | 131 | while True: 132 | b = self.pop_queue(max_id_rush=max_id_rush) 133 | if b is None: 134 | break 135 | self.visited.add(b) 136 | assert self.indeg[b] == 0 137 | 138 | for sub, cnt in self.using[b].items(): 139 | self.indeg[sub] -= cnt 140 | if self.indeg[sub] == 0: 141 | self.ready.put((sub.id, sub)) 142 | 143 | if b.op == OP.OUTSOURCE or b.op == OP.OUTSOURCE_CLONE: 144 | yield CO.ACTION_COMPUTE, b 145 | continue 146 | 147 | if b not in self.composite: 148 | # simple bit (input/const) 149 | continue 150 | 151 | # complex bit, compute 152 | # yield "alloc", b 153 | # alloc included in compute? 154 | 155 | yield CO.ACTION_COMPUTE, b 156 | 157 | for sub in b.args: 158 | # repeated bit usage? 159 | if b not in self.using[sub]: 160 | continue 161 | self.using[sub].remove_all(b) 162 | if len(self.using[sub]) == 0: 163 | if sub.is_const(): 164 | continue 165 | # disallow reuse immutable vars 166 | # if sub in self.immutable: 167 | # continue 168 | yield CO.ACTION_FREE, sub 169 | 170 | def pop_queue(self, max_id_rush): 171 | while self.sequential.qsize(): 172 | _, b_first = item = self.sequential.get() 173 | if b_first not in self.visited: 174 | self.sequential.put(item) 175 | break 176 | if self.sequential.qsize() == 0: 177 | return 178 | 179 | while self.ready.qsize(): 180 | _, b = item = self.ready.get() 181 | if b_first.id + max_id_rush < b.id: 182 | self.ready.put(item) 183 | break 184 | self.queue.append(b) 185 | 186 | # print self.sequential.qsize(), self.ready.qsize(), len(self.queue) 187 | if not self.queue: 188 | print "ERROR, no computable bits inside max_id_rush=%d" % max_id_rush 189 | print b_first.id, OP.name[b_first.op] 190 | print b.id, OP.name[b.op] 191 | quit(1) 192 | 193 | q = self.queue 194 | i = randint(0, len(q)-1) 195 | b = q[i] 196 | assert b.id <= b_first.id + max_id_rush 197 | q[i], q[-1] = q[-1], q[i] 198 | q.pop() 199 | return b 200 | -------------------------------------------------------------------------------- /synthesis/whitebox/serialize.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | import sys 4 | 5 | from whitebox.orderer import ComputationOrder, Orderer 6 | 7 | from whitebox.tree.node.op import BitOP 8 | OP = BitOP() 9 | 10 | 11 | class CodeSerializer(object): 12 | """ 13 | Base class for serialization (but is not necessary to use). 14 | Minimizes RAM usage by reusing memory cells. 15 | """ 16 | reuse_memory = True 17 | ignore_ops = () 18 | 19 | def __init__(self, **kwargs): 20 | for k in kwargs: 21 | assert hasattr(self, k), "unknown option %s" % k 22 | self.__dict__.update(kwargs) 23 | 24 | def serialize(self, circuit): 25 | """ 26 | Circuit is tuple/list of bits, 27 | or ComputationOrder 28 | """ 29 | if isinstance(circuit, ComputationOrder): 30 | co = circuit 31 | else: 32 | assert isinstance(circuit, (tuple, list)) and circuit[0].OP # is Bit class 33 | co = Orderer(circuit).compile() 34 | assert isinstance(co, ComputationOrder) 35 | 36 | self.co = co 37 | 38 | self.free = [] 39 | self.bit_id = {} 40 | self.ram_size = 0 41 | 42 | self.header = [] 43 | self.code = [] 44 | 45 | self.serialize_start() 46 | co.walk_by(self) 47 | self.serialize_end() 48 | return self.header, self.code 49 | 50 | def serialize_start(self, co): 51 | pass 52 | 53 | def serialize_end(self, co): 54 | pass 55 | 56 | def alloc(self, bit): 57 | if not self.free: 58 | self.free.append(self.ram_size) 59 | self.ram_size += 1 60 | self.bit_id[bit] = self.free.pop() 61 | 62 | def action_input(self, bit): 63 | self.alloc(bit) 64 | if bit.OP.INPUT not in self.ignore_ops: 65 | self.serialize_input(bit) 66 | 67 | def action_output(self, bit): 68 | if bit.OP.OUTPUT not in self.ignore_ops: 69 | self.serialize_output(bit) 70 | 71 | def action_compute(self, bit): 72 | self.alloc(bit) 73 | if bit.op not in self.ignore_ops: 74 | self.serialize_bit(bit) 75 | 76 | def action_free(self, bit): 77 | if self.reuse_memory: 78 | assert bit in self.bit_id, "double free?" 79 | self.free.append(self.bit_id[bit]) 80 | if bit.OP.FREE not in self.ignore_ops: 81 | self.serialize_free(bit) 82 | 83 | 84 | from struct import pack, unpack 85 | FORMATS = {1: "B", 2: "H", 4: "I", 8: "Q"} # uint8, uint16, uint32, uint64 86 | 87 | class RawSerializer(CodeSerializer): 88 | """ 89 | Basic raw serialization. Not optimal, ops can be encoded by fewer bits, etc. 90 | """ 91 | # these options can be overriden by initialization 92 | bytes_op = 1 93 | bytes_input = 1 94 | bytes_output = 1 95 | bytes_addr = 2 96 | endian = "<" 97 | 98 | # preserve BitOP ordering? 99 | # opmap = lambda op: op 100 | # or explicitly map to what is implemented in the C side 101 | opmap = { 102 | OP.XOR: 1, 103 | OP.AND: 2, 104 | OP.OR: 3, 105 | OP.NOT: 4, 106 | OP.RANDOM: 5, 107 | }.__getitem__ 108 | ignore_ops = OP.FREE, 109 | 110 | def __init__(self, **kwargs): 111 | super(RawSerializer, self).__init__(**kwargs) 112 | self.format_op = FORMATS[self.bytes_op] 113 | self.format_input = FORMATS[self.bytes_input] 114 | self.format_output = FORMATS[self.bytes_output] 115 | self.format_addr = FORMATS[self.bytes_addr] 116 | 117 | def pack(self, format, *args): 118 | if not args: 119 | return "" 120 | fmt = self.endian 121 | if len(args) > 1: 122 | fmt += str(len(args)) 123 | fmt += format 124 | return pack(fmt, *args) 125 | 126 | def action_free(self, bit): 127 | # we process output bits manually, 128 | # so don't free them 129 | if bit in self.outputs: return 130 | return super(RawSerializer, self).action_free(bit) 131 | 132 | def serialize_start(self): 133 | self.outputs = set(self.co.ybits) 134 | 135 | def serialize_end(self): 136 | self.info = len(self.co.xbits), len(self.co.ybits), len(self.code), sum(map(len, self.code)), self.ram_size 137 | self.input_addr = [self.bit_id[xbit] for xbit in self.co.xbits] 138 | self.output_addr = [self.bit_id[ybit] for ybit in self.co.ybits] 139 | 140 | # some bug regression 141 | assert len(set(self.input_addr)) == len(self.co.xbits) 142 | assert len(set(self.output_addr)) == len(self.co.ybits) 143 | 144 | self.header.append( 145 | self.pack(FORMATS[8], *self.info) 146 | ) 147 | self.header.append( 148 | self.pack(self.format_addr, *self.input_addr) 149 | ) 150 | self.header.append( 151 | self.pack(self.format_addr, *self.output_addr) 152 | ) 153 | 154 | def serialize_input(self, bit): 155 | # information is saved in the header, so no need to do anything here 156 | pass 157 | 158 | def serialize_output(self, bit): 159 | # information is saved in the header, so no need to do anything here 160 | pass 161 | 162 | def serialize_bit(self, bit): 163 | for arg in bit.args: 164 | assert isinstance(arg, type(bit)), "Not implemented serialization of non-Bit arguments" 165 | 166 | res = ( 167 | self.pack(self.format_op, self.opmap(bit.op)) 168 | + self.pack(self.format_addr, self.bit_id[bit]) 169 | ) 170 | if bit.args: 171 | args = [self.bit_id[bit] for bit in bit.args] 172 | res += self.pack(self.format_addr, *args) 173 | self.code.append(res) 174 | 175 | def serialize_to_file(self, circuit, filename): 176 | header, opcodes = self.serialize(circuit) 177 | header_data = "".join(header) 178 | opcodes_data = "".join(opcodes) 179 | with open(filename, "w") as f: 180 | f.write(header_data) 181 | f.write(opcodes_data) 182 | 183 | 184 | class CompactRawSerializer(RawSerializer): 185 | """ 186 | Compact raw serialization. opcode byte contains part of the destination address. 187 | """ 188 | opmap = { 189 | OP.XOR: 0, 190 | OP.AND: 1, 191 | OP.OR: 2, 192 | OP.NOT: 3, 193 | }.__getitem__ 194 | op_bits = 2 195 | free_bits = 6 196 | 197 | def serialize_bit(self, bit): 198 | for arg in bit.args: 199 | assert isinstance(arg, type(bit)), "Not implemented serialization of non-Bit arguments" 200 | 201 | dst = self.bit_id[bit] 202 | dst_hi = dst >> 8 203 | if dst_hi >= (1 << self.free_bits): 204 | raise ValueError("Too much RAM usage for compact serializer!") 205 | dst_lo = dst & 0xff 206 | 207 | byte1 = self.opmap(bit.op) | (dst_hi << self.op_bits) 208 | byte2 = dst_lo 209 | 210 | res = ( 211 | self.pack(self.format_op, byte1) 212 | + self.pack(self.format_op, byte2) 213 | ) 214 | if bit.args: 215 | args = [self.bit_id[bit] for bit in bit.args] 216 | res += self.pack(self.format_addr, *args) 217 | self.code.append(res) 218 | -------------------------------------------------------------------------------- /synthesis/whitebox/ciphers/AES/sbox.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | ''' 4 | Based on 5 | 6 | D. Canright. A Very Compact S-Box for AES. In J. R. Rao and B. Sunar, editors, 7 | Cryptographic Hardware and Embedded Systems – CHES 2005, Proceedings, volume 3659 8 | of Lecture Notes in Computer Science, pages 441–455. Springer, 2005. 9 | ''' 10 | 11 | def GF_SQ_2(A): return A[1], A[0] 12 | def GF_SCLW_2(A): return A[1], A[1] ^ A[0] 13 | def GF_SCLW2_2(A): return A[1] ^ A[0], A[0] 14 | 15 | # To support bitsliced calls. 16 | # Otherwise just do & 1 on bits 17 | MASK = 2**64-1 # 0xffffffffffffffff 18 | def Not(x): 19 | return MASK^x 20 | 21 | def GF_MULS_2(A, ab, B, cd): 22 | abcd = (ab & cd) 23 | p = ((A[1] & B[1])) ^ abcd 24 | q = ((A[0] & B[0])) ^ abcd 25 | return q, p 26 | 27 | def GF_MULS_SCL_2(A, ab, B, cd): 28 | t = (A[0] & B[0]) 29 | p = ((ab & cd)) ^ t 30 | q = ((A[1] & B[1])) ^ t 31 | return q, p 32 | 33 | def XOR_LIST(a, b): 34 | return [a ^ b for a, b in zip(a, b)] 35 | 36 | def NotOr(a, b): 37 | # return Not(a | b) 38 | return Not(a) & Not(b) 39 | 40 | def GF_INV_4(A): 41 | a = A[2:4] 42 | b = A[0:2] 43 | sa = a[1] ^ a[0] 44 | sb = b[1] ^ b[0] 45 | 46 | ab = GF_MULS_2(a, sa, b, sb) 47 | ab2 = GF_SQ_2(XOR_LIST(a, b)) 48 | ab2N = GF_SCLW2_2(ab2) 49 | d = GF_SQ_2(XOR_LIST(ab, ab2N)) 50 | 51 | c = [ 52 | NotOr(sa, sb) ^ (Not(a[0] & b[0])), 53 | NotOr(a[1], b[1]) ^ (Not(sa & sb)), 54 | ] 55 | 56 | sd = d[1] ^ d[0] 57 | p = GF_MULS_2(d, sd, b, sb) 58 | q = GF_MULS_2(d, sd, a, sa) 59 | return q + p 60 | 61 | def GF_SQ_SCL_4(A): 62 | a = A[2:4] 63 | b = A[0:2] 64 | ab2 = GF_SQ_2(a ^ b) 65 | b2 = GF_SQ_2(b) 66 | b2N2 = GF_SCLW_2(b2) 67 | return b2N2 + ab2 68 | 69 | def GF_MULS_4(A, a, Al, Ah, aa, B, b, Bl, Bh, bb): 70 | ph = GF_MULS_2(A[2:4], Ah, B[2:4], Bh) 71 | pl = GF_MULS_2(A[0:2], Al, B[0:2], Bl) 72 | p = GF_MULS_SCL_2(a, aa, b, bb) 73 | return XOR_LIST(pl, p) + XOR_LIST(ph, p) #(pl ^ p), (ph ^ p) 74 | 75 | def GF_INV_8(A): 76 | a = A[4:8] 77 | b = A[0:4] 78 | sa = XOR_LIST(a[2:4], a[0:2]) 79 | sb = XOR_LIST(b[2:4], b[0:2]) 80 | al = a[1] ^ a[0] 81 | ah = a[3] ^ a[2] 82 | aa = sa[1] ^ sa[0] 83 | bl = b[1] ^ b[0] 84 | bh = b[3] ^ b[2] 85 | bb = sb[1] ^ sb[0] 86 | 87 | c1 = (ah & bh) 88 | c2 = (sa[0] & sb[0]) 89 | c3 = (aa & bb) 90 | 91 | c = [ 92 | (NotOr(a[0] , b[0] ) ^ ((al & bl))) ^ ((sa[1] & sb[1])) ^ Not(c2), #0 93 | (NotOr(al , bl ) ^ (Not(a[1] & b[1]))) ^ c2 ^ c3 , #1 94 | (NotOr(sa[1], sb[1]) ^ (Not(a[2] & b[2]))) ^ c1 ^ c2 , #2 95 | (NotOr(sa[0], sb[0]) ^ (Not(a[3] & b[3]))) ^ c1 ^ c3 , #3 96 | ] 97 | d = GF_INV_4(c) 98 | 99 | sd = XOR_LIST(d[2:4], d[0:2]) 100 | dl = d[1] ^ d[0] 101 | dh = d[3] ^ d[2] 102 | dd = sd[1] ^ sd[0] 103 | p = GF_MULS_4(d, sd, dl, dh, dd, b, sb, bl, bh, bb) 104 | q = GF_MULS_4(d, sd, dl, dh, dd, a, sa, al, ah, aa) 105 | return q + p 106 | 107 | def MUX21I(A, B, s): #return ((~A & s) ^ (~B & ~s) 108 | return Not(A if s else B) 109 | 110 | def SELECT_NOT_8( A, B, s): 111 | Q = [None] * 8 112 | for i in xrange(8): 113 | Q[i] = MUX21I(A[i], B[i], s) 114 | return Q 115 | 116 | def bSbox(A, encrypt): 117 | R1 = A[7] ^ A[5] 118 | R2 = A[7] ^ Not(A[4]) 119 | R3 = A[6] ^ A[0] 120 | R4 = A[5] ^ Not(R3) 121 | R5 = A[4] ^ R4 122 | R6 = A[3] ^ A[0] 123 | R7 = A[2] ^ R1 124 | R8 = A[1] ^ R3 125 | R9 = A[3] ^ R8 126 | 127 | B = [None] * 8 128 | B[7] = R7 ^ Not(R8) 129 | B[6] = R5 130 | B[5] = A[1] ^ R4 131 | B[4] = R1 ^ Not(R3) 132 | B[3] = A[1]^ R2 ^ R6 133 | B[2] = Not( A[0]) 134 | B[1] = R4 135 | B[0] = A[2] ^ Not(R9) 136 | 137 | Y = [None] * 8 138 | Y[7] = R2 139 | Y[6] = A[4] ^ R8 140 | Y[5] = A[6] ^ A[4] 141 | Y[4] = R9 142 | Y[3] = A[6] ^ Not(R2) 143 | Y[2] = R7 144 | Y[1] = A[4] ^ R6 145 | Y[0] = A[1] ^ R5 146 | 147 | Z = SELECT_NOT_8(B, Y, encrypt) 148 | C = GF_INV_8(Z) 149 | 150 | T1 = C[7] ^ C[3] 151 | T2 = C[6] ^ C[4] 152 | T3 = C[6] ^ C[0] 153 | T4 = C[5] ^ Not(C[3]) 154 | T5 = C[5] ^ Not(T1) 155 | T6 = C[5] ^ Not(C[1]) 156 | T7 = C[4] ^ Not(T6) 157 | T8 = C[2] ^ T4 158 | T9 = C[1] ^ T2 159 | T10 = T3 ^ T5 160 | 161 | D = [None] * 8 162 | D[7] = T4 163 | D[6] = T1 164 | D[5] = T3 165 | D[4] = T5 166 | D[3] = T2 ^ T5 167 | D[2] = T3 ^ T8 168 | D[1] = T7 169 | D[0] = T9 170 | 171 | X = [None] * 8 172 | X[7] = C[4] ^ Not(C[1]) 173 | X[6] = C[1] ^ T10 174 | X[5] = C[2] ^ T10 175 | X[4] = C[6] ^ Not(C[1]) 176 | X[3] = T8 ^ T9 177 | X[2] = C[7] ^ Not(T7) 178 | X[1] = T6 179 | X[0] = Not(C[2]) 180 | return SELECT_NOT_8(D, X, encrypt) 181 | 182 | 183 | def bitSbox(A, inverse=False): 184 | res = bSbox(A[::-1], encrypt=1-inverse)[::-1] 185 | return res 186 | 187 | if __name__ == '__main__': 188 | tobin = lambda x, n: tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 189 | frombin = lambda v: int("".join(map(str, v)), 2 ) 190 | 191 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 192 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 193 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 194 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 195 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 196 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 197 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 198 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 199 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 200 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 201 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 202 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 203 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 204 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 205 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 206 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 207 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 208 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 209 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 210 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 211 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 212 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 213 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 214 | 0x54, 0xbb, 0x16] 215 | 216 | for x in xrange(256): 217 | xv = list(tobin(x, 8)) 218 | 219 | # s-box check 220 | yv = bitSbox(xv) 221 | y = frombin(v & 1 for v in yv) 222 | assert sbox[x] == y, (x, y, sbox[x]) 223 | 224 | # check inverse is good 225 | assert xv == bitSbox(yv, inverse=True) 226 | 227 | print x, sbox[x], y 228 | 229 | print "Sbox test OK" 230 | -------------------------------------------------------------------------------- /algebraic_security_AC2018/implementation/libwb/gates/sbox.py: -------------------------------------------------------------------------------- 1 | #-*- coding:utf-8 -*- 2 | 3 | ''' 4 | Based on 5 | 6 | D. Canright. A Very Compact S-Box for AES. In J. R. Rao and B. Sunar, editors, 7 | Cryptographic Hardware and Embedded Systems – CHES 2005, Proceedings, volume 3659 8 | of Lecture Notes in Computer Science, pages 441–455. Springer, 2005. 9 | ''' 10 | 11 | def GF_SQ_2(A): return A[1], A[0] 12 | def GF_SCLW_2(A): return A[1], A[1] ^ A[0] 13 | def GF_SCLW2_2(A): return A[1] ^ A[0], A[0] 14 | 15 | # To support bitsliced calls. 16 | # Otherwise just do & 1 on bits 17 | MASK = 2**64-1 # 0xffffffffffffffff 18 | def Not(x): 19 | return MASK^x 20 | 21 | def GF_MULS_2(A, ab, B, cd): 22 | abcd = (ab & cd) 23 | p = ((A[1] & B[1])) ^ abcd 24 | q = ((A[0] & B[0])) ^ abcd 25 | return q, p 26 | 27 | def GF_MULS_SCL_2(A, ab, B, cd): 28 | t = (A[0] & B[0]) 29 | p = ((ab & cd)) ^ t 30 | q = ((A[1] & B[1])) ^ t 31 | return q, p 32 | 33 | def XOR_LIST(a, b): 34 | return [a ^ b for a, b in zip(a, b)] 35 | 36 | def NotOr(a, b): 37 | # return Not(a | b) 38 | return Not(a) & Not(b) 39 | 40 | def GF_INV_4(A): 41 | a = A[2:4] 42 | b = A[0:2] 43 | sa = a[1] ^ a[0] 44 | sb = b[1] ^ b[0] 45 | 46 | ab = GF_MULS_2(a, sa, b, sb) 47 | ab2 = GF_SQ_2(XOR_LIST(a, b)) 48 | ab2N = GF_SCLW2_2(ab2) 49 | d = GF_SQ_2(XOR_LIST(ab, ab2N)) 50 | 51 | c = [ 52 | NotOr(sa, sb) ^ (Not(a[0] & b[0])), 53 | NotOr(a[1], b[1]) ^ (Not(sa & sb)), 54 | ] 55 | 56 | sd = d[1] ^ d[0] 57 | p = GF_MULS_2(d, sd, b, sb) 58 | q = GF_MULS_2(d, sd, a, sa) 59 | return q + p 60 | 61 | def GF_SQ_SCL_4(A): 62 | a = A[2:4] 63 | b = A[0:2] 64 | ab2 = GF_SQ_2(a ^ b) 65 | b2 = GF_SQ_2(b) 66 | b2N2 = GF_SCLW_2(b2) 67 | return b2N2 + ab2 68 | 69 | def GF_MULS_4(A, a, Al, Ah, aa, B, b, Bl, Bh, bb): 70 | ph = GF_MULS_2(A[2:4], Ah, B[2:4], Bh) 71 | pl = GF_MULS_2(A[0:2], Al, B[0:2], Bl) 72 | p = GF_MULS_SCL_2(a, aa, b, bb) 73 | return XOR_LIST(pl, p) + XOR_LIST(ph, p) #(pl ^ p), (ph ^ p) 74 | 75 | def GF_INV_8(A): 76 | a = A[4:8] 77 | b = A[0:4] 78 | sa = XOR_LIST(a[2:4], a[0:2]) 79 | sb = XOR_LIST(b[2:4], b[0:2]) 80 | al = a[1] ^ a[0] 81 | ah = a[3] ^ a[2] 82 | aa = sa[1] ^ sa[0] 83 | bl = b[1] ^ b[0] 84 | bh = b[3] ^ b[2] 85 | bb = sb[1] ^ sb[0] 86 | 87 | c1 = (ah & bh) 88 | c2 = (sa[0] & sb[0]) 89 | c3 = (aa & bb) 90 | 91 | c = [ 92 | (NotOr(a[0] , b[0] ) ^ ((al & bl))) ^ ((sa[1] & sb[1])) ^ Not(c2), #0 93 | (NotOr(al , bl ) ^ (Not(a[1] & b[1]))) ^ c2 ^ c3 , #1 94 | (NotOr(sa[1], sb[1]) ^ (Not(a[2] & b[2]))) ^ c1 ^ c2 , #2 95 | (NotOr(sa[0], sb[0]) ^ (Not(a[3] & b[3]))) ^ c1 ^ c3 , #3 96 | ] 97 | d = GF_INV_4(c) 98 | 99 | sd = XOR_LIST(d[2:4], d[0:2]) 100 | dl = d[1] ^ d[0] 101 | dh = d[3] ^ d[2] 102 | dd = sd[1] ^ sd[0] 103 | p = GF_MULS_4(d, sd, dl, dh, dd, b, sb, bl, bh, bb) 104 | q = GF_MULS_4(d, sd, dl, dh, dd, a, sa, al, ah, aa) 105 | return q + p 106 | 107 | def MUX21I(A, B, s): #return ((~A & s) ^ (~B & ~s) 108 | return Not(A if s else B) 109 | 110 | def SELECT_NOT_8( A, B, s): 111 | Q = [None] * 8 112 | for i in xrange(8): 113 | Q[i] = MUX21I(A[i], B[i], s) 114 | return Q 115 | 116 | def bSbox(A, encrypt): 117 | R1 = A[7] ^ A[5] 118 | R2 = A[7] ^ Not(A[4]) 119 | R3 = A[6] ^ A[0] 120 | R4 = A[5] ^ Not(R3) 121 | R5 = A[4] ^ R4 122 | R6 = A[3] ^ A[0] 123 | R7 = A[2] ^ R1 124 | R8 = A[1] ^ R3 125 | R9 = A[3] ^ R8 126 | 127 | B = [None] * 8 128 | B[7] = R7 ^ Not(R8) 129 | B[6] = R5 130 | B[5] = A[1] ^ R4 131 | B[4] = R1 ^ Not(R3) 132 | B[3] = A[1]^ R2 ^ R6 133 | B[2] = Not( A[0]) 134 | B[1] = R4 135 | B[0] = A[2] ^ Not(R9) 136 | 137 | Y = [None] * 8 138 | Y[7] = R2 139 | Y[6] = A[4] ^ R8 140 | Y[5] = A[6] ^ A[4] 141 | Y[4] = R9 142 | Y[3] = A[6] ^ Not(R2) 143 | Y[2] = R7 144 | Y[1] = A[4] ^ R6 145 | Y[0] = A[1] ^ R5 146 | 147 | Z = SELECT_NOT_8(B, Y, encrypt) 148 | C = GF_INV_8(Z) 149 | 150 | T1 = C[7] ^ C[3] 151 | T2 = C[6] ^ C[4] 152 | T3 = C[6] ^ C[0] 153 | T4 = C[5] ^ Not(C[3]) 154 | T5 = C[5] ^ Not(T1) 155 | T6 = C[5] ^ Not(C[1]) 156 | T7 = C[4] ^ Not(T6) 157 | T8 = C[2] ^ T4 158 | T9 = C[1] ^ T2 159 | T10 = T3 ^ T5 160 | 161 | D = [None] * 8 162 | D[7] = T4 163 | D[6] = T1 164 | D[5] = T3 165 | D[4] = T5 166 | D[3] = T2 ^ T5 167 | D[2] = T3 ^ T8 168 | D[1] = T7 169 | D[0] = T9 170 | 171 | X = [None] * 8 172 | X[7] = C[4] ^ Not(C[1]) 173 | X[6] = C[1] ^ T10 174 | X[5] = C[2] ^ T10 175 | X[4] = C[6] ^ Not(C[1]) 176 | X[3] = T8 ^ T9 177 | X[2] = C[7] ^ Not(T7) 178 | X[1] = T6 179 | X[0] = Not(C[2]) 180 | return SELECT_NOT_8(D, X, encrypt) 181 | 182 | 183 | def bitSbox(A, inverse=False): 184 | res = bSbox(A[::-1], encrypt=1-inverse)[::-1] 185 | return res 186 | 187 | if __name__ == '__main__': 188 | tobin = lambda x, n: tuple(map(int, bin(x).lstrip("0b").rjust(n, "0"))) 189 | frombin = lambda v: int("".join(map(str, v)), 2 ) 190 | 191 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 192 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 193 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 194 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 195 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 196 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 197 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 198 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 199 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 200 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 201 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 202 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 203 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 204 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 205 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 206 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 207 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 208 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 209 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 210 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 211 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 212 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 213 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 214 | 0x54, 0xbb, 0x16] 215 | 216 | for x in xrange(256): 217 | xv = list(tobin(x, 8)) 218 | 219 | # s-box check 220 | yv = bitSbox(xv) 221 | y = frombin(v & 1 for v in yv) 222 | assert sbox[x] == y, (x, y, sbox[x]) 223 | 224 | # check inverse is good 225 | assert xv == bitSbox(yv, inverse=True) 226 | 227 | print x, sbox[x], y 228 | 229 | print "Sbox test OK" 230 | -------------------------------------------------------------------------------- /synthesis/README.md: -------------------------------------------------------------------------------- 1 | # Synthesis Tools for White-box Implementations 2 | 3 | This repository contains a framework for creating and analysing circuit-based white-box implementations. It was presented at the [WhibOx 2019](https://www.cryptoexperts.com/whibox2019/) workshop and is based on the code from the [White-box Tools](https://github.com/cryptolu/whitebox) repository of the paper [Attacks and Countermeasures for White-box Designs](https://eprint.iacr.org/2018/049) by Alex Biryukov and Aleksei Udovenko ([ASIACRYPT 2018](https://www.springer.com/gp/book/9783030033286)). 4 | 5 | [Slides](./slides.pdf) from the workshop are available. 6 | 7 | This is an early version and may contain bugs. It is also not documented. I plan to add some examples to show basic usage. 8 | 9 | Note that circuit-based implementations are generally slower and this framework is more targeting research on practical circuit obfuscation for cryptographic purposes. It is well suited for proof-of-concept implementations, for example to be submitted to the [WhibOx 2019](https://whibox.cyber-crypt.com/) competition (see also [WhibOx 2017](https://whibox-contest.github.io/)). 10 | 11 | **Requirements**: Python2, [SageMath](http://www.sagemath.org/), [PyPy2](https://pypy.org/) (recommended). Python 3 support may be added soon. 12 | 13 | Run `$ make` to compile a C library for fast simulation. 14 | 15 | ## Workflow example 16 | 17 | ### 1. Minimal Example for Masked AES 18 | 19 | The following code creates AES circuit with *configurable* masking (quadratic MINQ + linear DOM-indep). The masking uses pseudorandomness generated from the plaintext using an LFSR initialised after two-round AES. It is also pooled to save on pseudorandomness generation as it is quite expensive. 20 | 21 | ```python 22 | from whitebox import Bit 23 | from whitebox.utils import str2bin 24 | NR = 10 25 | KEY = "MySecretKey!2019" 26 | 27 | from whitebox.ciphers.AES import BitAES 28 | pt = Bit.inputs("pt", 128) 29 | ct, k10 = BitAES(pt, Bit.consts(str2bin(KEY)), rounds=NR) 30 | 31 | from whitebox.prng import LFSR, Pool 32 | prng = LFSR(taps=[0, 2, 5, 18, 39, 100, 127], 33 | state=BitAES(pt, pt, rounds=2)[0]) 34 | rand = Pool(n=128, prng=prng).step 35 | 36 | from whitebox.masking import MINQ, DOM, mask_circuit 37 | ct = mask_circuit(ct, MINQ(rand=rand)) 38 | ct = mask_circuit(ct, DOM(rand=rand, nshares=2)) 39 | ``` 40 | 41 | Next, the circuit is compiled to a standalone C code that can be submitted to the WhibOx competition (if it fits the resource limitations of course). It uses a default method to encode and simulate the circuit. 42 | 43 | ```python 44 | from whitebox.whibox import whibox_generate 45 | whibox_generate(ct, "build/submit.c", "Hello, world!") 46 | ``` 47 | 48 | For local analysis and attacks the circuit can be serialized in a compact binary format to a file. 49 | 50 | ```python 51 | from whitebox.serialize import RawSerializer 52 | RawSerializer().serialize_to_file(ct, "circuits/aes10.bin") 53 | ``` 54 | 55 | This is example is placed in [./examples/minimal.py](). It can be run straightforwardly as follows. You can at first comment lines with masking first to make the generation faster and to see how attacks can recover the key. 56 | 57 | ```bash 58 | $ pypy examples/minimal.py 59 | ``` 60 | 61 | ### 2. Tracing the Circuit 62 | 63 | Serialized circuit can be simulated by fast C simulator available from Python API. It is especially efficient in batch mode, where 64 inputs can be processed in one run (assuming 64-bit architecture). 64 | 65 | ```python 66 | from whitebox.fastcircuit import FastCircuit 67 | C = FastCircuit("circuits/aes10.bin") 68 | ciphertext = C.compute_one("my_plaintext_abc") 69 | ciphertexts = C.compute_batch(["my_plaintext_abc", "anotherPlaintext"]) 70 | ``` 71 | 72 | For DCA-style attacks, we need to collect traces of the values computed in the circuit. The `FastCircuit` class supports tracing. For this we only need to specify filename to save the trace. 73 | 74 | ```python 75 | import os 76 | from whitebox.utils import chunks 77 | plaintexts = os.urandom(16 * 128) # 128 traces 78 | ciptertexts = C.compute_batches( 79 | inputs=chunks(plaintexts, 16), 80 | trace_filename_format="./traces/mytrace.%d" 81 | ) 82 | ``` 83 | 84 | The trace files contain compactly packed data, 64 encryptions per file. Each 8-byte block in a file corresponds to 64 bits recorded in one node of the circuit for all 64 different encryptions. We can further split such file into 64 separate files, one trace per encryption. 85 | 86 | ```python 87 | from whitebox.tracing import trace_split_batch 88 | 89 | trace_split_batch( 90 | filename="./traces/mytrace.0", 91 | make_output_filename=lambda j: "./traces/%04d.bin" % j, 92 | ntraces=64, 93 | packed=True 94 | ) 95 | ``` 96 | 97 | To simplify this procedure, a simple tool is available in [./tools/trace.py](). For example, 128 traces can be generated as follows: 98 | 99 | ```bash 100 | $ pypy tools/trace.py circuits/aes10.bin 128 101 | ``` 102 | 103 | It generates traces to files of the form `./traces/0000.bin`. The corresponding plaintext and ciphertext are placed respectively in `./traces/0000.pt` and `./traces/0000.ct`. 104 | 105 | ### 3. DCA-Style Attacks 106 | 107 | This framework includes 3 attacks based on analyzing the traces. The common idea is to guess a byte of the key in the first round of AES, to compute an S-Box over all plaintexts, and to "trace" an output bit (or a linear combination) of the S-Box. Then the attacks attempt to find a relation between this "predicted" vector with vectors from the trace. 108 | 109 | 1. The first attack tries to find an exact version of the vector. It is very fast by applies only to unprotected versions of AES. It also includes a second-order variant which searches for a pair of vectors that XOR to a predicted vector. It can break AES protected with a first-order linear masking scheme. 110 | 111 | The switch between first-order and the second-order attack is currently inside the script, along with many other configuration options. It can be ran as follows, to run the attack on the first 128 traces with a sliding window of 10000 nodes and step 2500. 112 | 113 | ```bash 114 | $ pypy attacks/analyze_exact.py 128 10000 2500 115 | ``` 116 | 117 | 2. The second attack tries to correlate each vector from the trace to a predicted vector. For this purpose we use the amazing tool [Daredevil](https://github.com/SideChannelMarvels/Daredevil) from the [SideChannelMarvels](https://github.com/SideChannelMarvels) collection. We only need to transform the traces to the right format and to generate a config file for it. The config can be modified after generation or right in the script [./attacks/combine4daredevil.py](). 118 | 119 | ```bash 120 | $ pypy attacks/combine4daredevil.py 128 121 | $ daredevil -c daredevil.config 122 | ``` 123 | 124 | 3. The third attack tries to find a linear combination of vectors from traces that XOR to a predicted vector (in a reasonably sized window). It breaks any order of linear masking, if all the shares indeed are contained in the analyzed window. 125 | 126 | It has to be run with [SageMath](http://www.sagemath.org/). The following example runs it on 128 traces with window of 100 nodes and window step 25. Note that in order to avoid false positives, the number of traces should be larger than the window size by a constant amount. 127 | 128 | ```bash 129 | $ sage attacks/analyze_linalg_1st.py 128 100 25 130 | ``` 131 | 132 | See the brief summary on the masking schemes and applicable attacks: 133 | 134 | ![](./attacks_summary.png) 135 | 136 | Note that fault attacks are not implemented yet in the framework and there are no protections against them as well. 137 | --------------------------------------------------------------------------------