├── .gitignore ├── CMakeLists.txt ├── pyproject.toml ├── src ├── bluegalaxyenergy │ ├── Exceptions.py │ ├── __init__.py │ ├── WhiteBoxedAESTest.py │ ├── WhiteBoxedAES.py │ ├── ByteOrder.py │ ├── BGEGenInput.py │ ├── __main__.py │ ├── WhiteBoxedUnshuffledProxy.py │ ├── Encoding.py │ ├── FinalizeUnshuffled.py │ ├── AESEncoded.py │ ├── BGE.py │ └── AES.py ├── utils.hpp ├── encodingkey.hpp ├── outputwb.hpp ├── relationencoding.hpp ├── affineencoding.hpp ├── precomputeBetaTable.sage ├── approximateencoding.hpp ├── baseencoding.hpp ├── inputReader.hpp ├── main.cpp ├── matrix.hpp └── computeQPtilde.hpp ├── README.md ├── LICENSE.txt └── implementation_details.md /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__/ 2 | build/ 3 | dist/ 4 | *.sage.py 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | set(CMAKE_BUILD_TYPE Debug CACHE STRING "Used default build type" FORCE) 4 | 5 | project(${SKBUILD_PROJECT_NAME} VERSION ${SKBUILD_PROJECT_VERSION}) 6 | 7 | set(CMAKE_CXX_FLAGS "-g -O3 -Wall -Wextra") 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | find_package(pybind11 CONFIG REQUIRED) 11 | find_library(PTHREAD_LIB pthread REQUIRED) 12 | find_library(GMP_LIB gmp REQUIRED) 13 | find_library(NTL_LIB ntl REQUIRED) 14 | 15 | pybind11_add_module(_core MODULE src/main.cpp) 16 | 17 | target_compile_definitions(_core PRIVATE VERSION_INFO=${PROJECT_VERSION}) 18 | target_link_libraries(_core PRIVATE ${NTL_LIB} ${GMP_LIB} ${PTHREAD_LIB}) 19 | 20 | install(TARGETS _core DESTINATION bluegalaxyenergy) 21 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | # cf https://github.com/scikit-build/scikit-build-core 3 | requires = ["scikit-build-core", "pybind11"] 4 | build-backend = "scikit_build_core.build" 5 | 6 | [project] 7 | name = "bluegalaxyenergy" 8 | version = "2.0.1" 9 | description="A tool to perform the so-called BGE attack" 10 | readme = "README.md" 11 | requires-python = ">=3.7" 12 | license = {file = "LICENSE.txt"} 13 | authors = [ 14 | {name = "Laurent Grémy"}, 15 | {name = "Nicolas Surbayrole"}, 16 | {name = "Philippe Teuwen"}, 17 | ] 18 | 19 | [project.urls] 20 | repository = "https://github.com/SideChannelMarvels/BlueGalaxyEnergy" 21 | 22 | [tool.scikit-build] 23 | wheel.expand-macos-universal-tags = true 24 | ninja.make-fallback = true 25 | strict-config = true 26 | install.strip = false 27 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/Exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | class InvalidRound(Exception): 17 | pass 18 | 19 | 20 | class SaveLoadError(Exception): 21 | pass 22 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef UTILS_HPP 15 | #define UTILS_HPP 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | #define LEN_ARRAY 256 22 | #define AES_ROUND 14 23 | 24 | #define MAYBE_UNUSED __attribute__((unused)) 25 | 26 | #endif /* UTILS_H */ 27 | 28 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .BGEGenInput import genBGEInput 17 | from .BGE import BGE 18 | from .WhiteBoxedAES import WhiteBoxedAES 19 | from ._core import __doc__, __version__ 20 | 21 | __all__ = ["__doc__", "__version__", "genBGEInput", "WhiteBoxedAES", "BGE"] 22 | -------------------------------------------------------------------------------- /src/encodingkey.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef ENCODINGKEY_HPP 15 | #define ENCODINGKEY_HPP 16 | 17 | #include "baseencoding.hpp" 18 | 19 | // Correspond to Ptilde in the article 20 | class EncodingKey : public BaseEncoding { 21 | public: 22 | EncodingKey() : BaseEncoding() {} 23 | explicit EncodingKey(const BaseEncoding &arr) : BaseEncoding(arr) {} 24 | explicit EncodingKey(const std::array &arr) : BaseEncoding(arr) {} 25 | }; 26 | 27 | #endif /* ENCODINGKEY_HPP */ 28 | 29 | -------------------------------------------------------------------------------- /src/outputwb.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef OUTPUTWB_HPP 15 | #define OUTPUTWB_HPP 16 | 17 | #include "baseencoding.hpp" 18 | #include "utils.hpp" 19 | 20 | class OutputWB : public BaseEncoding { 21 | public: 22 | OutputWB(const std::array &arr) : BaseEncoding(arr) {} 23 | 24 | static std::unique_ptr from_array(const std::array &arr) { 25 | return BaseEncoding::from_array(arr); 26 | } 27 | 28 | }; 29 | 30 | #endif /* OUTPUTWB_HPP */ 31 | 32 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/WhiteBoxedAESTest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .WhiteBoxedAES import WhiteBoxedAES 17 | 18 | 19 | class WhiteBoxedAESTest(WhiteBoxedAES): 20 | 21 | def __init__(self, aesEncoded, isEncrypt=True): 22 | self.aesEncoded = aesEncoded 23 | self.encrypt = isEncrypt 24 | 25 | def isEncrypt(self): 26 | return self.encrypt 27 | 28 | def getRoundNumber(self): 29 | return self.aesEncoded.r 30 | 31 | def applyRound(self, data, roundN): 32 | if self.encrypt: 33 | return self.aesEncoded.encrypt_round_encode(data, roundN) 34 | else: 35 | return self.aesEncoded.decrypt_round_encode(data, self.getRoundNumber() - (roundN + 1)) 36 | 37 | -------------------------------------------------------------------------------- /src/relationencoding.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef RELATIONENCODING_HPP 15 | #define RELATIONENCODING_HPP 16 | 17 | #include "matrix.hpp" 18 | #include "outputwb.hpp" 19 | 20 | class RelationEncoding { 21 | private: 22 | Matrix A; 23 | uint8_t c; 24 | 25 | public: 26 | // Find a relationship such that yi = yj * A + c 27 | RelationEncoding(const BaseEncoding &yi, const BaseEncoding &yj) { 28 | c = yi[yj.getInv(0)]; 29 | std::array row; 30 | for (unsigned int i = 0; i < 8; i++) { 31 | uint8_t a = (1 << (7 - i)); 32 | row[i] = yi[yj.getInv(a)] ^ c; 33 | } 34 | 35 | A = Matrix(row); 36 | }; 37 | 38 | Matrix getA() const { return A; } 39 | uint8_t getC() const { return c; } 40 | 41 | bool assert_computation(const BaseEncoding &yi, const BaseEncoding &yj) { 42 | for (unsigned int i = 0; i < LEN_ARRAY; i++) 43 | if (yi[i] != static_cast(A.multiply(yj[i]) ^ c)) 44 | return false; 45 | 46 | return true; 47 | } 48 | }; 49 | 50 | #endif /* RELATIONENCODING_HPP */ 51 | 52 | -------------------------------------------------------------------------------- /src/affineencoding.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef AFFINEENCODING_HPP 15 | #define AFFINEENCODING_HPP 16 | 17 | #include "baseencoding.hpp" 18 | 19 | class AffineEncoding : public BaseEncoding { 20 | public: 21 | AffineEncoding() : BaseEncoding() {} 22 | explicit AffineEncoding(const BaseEncoding &arr) : BaseEncoding(arr) {} 23 | explicit AffineEncoding(const std::array &arr) : BaseEncoding(arr) {} 24 | 25 | AffineEncoding(const BaseEncoding &arr, uint8_t q) : BaseEncoding() { 26 | construct(arr.getEncodingArray(), q); 27 | } 28 | 29 | AffineEncoding(const std::array &arr, uint8_t q) : BaseEncoding() { 30 | construct(arr, q); 31 | } 32 | 33 | protected: 34 | void construct(const std::array &arr, uint8_t q) { 35 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 36 | val[i] = arr[i] ^ q; 37 | inv[arr[i] ^ q] = i; 38 | } 39 | if (! isvalid()) { 40 | std::cerr << "BaseEncoding::isvalid : False, abort!" << std::endl; 41 | abort(); 42 | } 43 | } 44 | }; 45 | #endif /* AFFINEENCODING_HPP */ 46 | 47 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/WhiteBoxedAES.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | # This class is the interface with the whitebox 17 | # A child class must be implemented for a whitebox with the same interface 18 | 19 | import abc 20 | 21 | 22 | class WhiteBoxedAES(abc.ABC): 23 | 24 | @abc.abstractmethod 25 | def isEncrypt(self): 26 | # return True if the whitebox is an encryption whitebox, False otherwise 27 | pass 28 | 29 | @abc.abstractmethod 30 | def getRoundNumber(self): 31 | # return the number of rounds of the whitebox (10 for AES128, 32 | # 12 for AES192 and 14 for AES256) 33 | pass 34 | 35 | @abc.abstractmethod 36 | def applyRound(self, data, roundN): 37 | # Apply a round of the whitebox on a buffer. In case of decrypting 38 | # whitebox, the round 0 refers to the first round of decryption. 39 | # [param] data a buffer of 16 bytes (type bytes) 40 | # [param] roundN the round number to apply (int in the range [0, self.getRoundNumber()) ) 41 | # return 16 bytes of the encrypted data by the round 42 | pass 43 | 44 | def newThread(self): 45 | # When BGE is used with multiprocess, this method is call once at the 46 | # beginning of each new thread 47 | pass 48 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/ByteOrder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .AES import _AesShiftRow, _AesInvShiftRow 17 | from .Exceptions import InvalidRound 18 | 19 | 20 | def computeImpactByte(ref, fault): 21 | return tuple([index for index, (x, y) in enumerate(zip(ref, fault)) if x != y]) 22 | 23 | 24 | def getRoundColumn(wb, roundN): 25 | inColumn = [] 26 | outColumn = [] 27 | 28 | ref = wb.applyRound(bytes([0] * 16), roundN) 29 | 30 | colOrder = _AesShiftRow if wb.isEncrypt() else _AesInvShiftRow 31 | for pos in colOrder: 32 | 33 | data = bytes([0 if p != pos else 1 for p in range(16)]) 34 | 35 | impact = computeImpactByte(ref, wb.applyRound(data, roundN)) 36 | if len(impact) != 4: 37 | raise InvalidRound(f"Wrong number of impacts for round {roundN} byte {pos}:" 38 | f" got {len(impact)} impacts but expected 4") 39 | 40 | try: 41 | index = outColumn.index(impact) 42 | inColumn[index] = inColumn[index] + (pos, ) 43 | if len(inColumn) > 4: 44 | raise InvalidRound("Too many positions create an impact on the same column") 45 | except ValueError: 46 | if len(outColumn) >= 4: 47 | raise InvalidRound("Too many impacts possible") 48 | outColumn.append(impact) 49 | inColumn.append((pos, )) 50 | 51 | return inColumn, outColumn 52 | 53 | 54 | def isValidRound(wb, roundN): 55 | try: 56 | getRoundColumn(wb, roundN) 57 | return True 58 | except InvalidRound: 59 | return False 60 | 61 | 62 | def verifyRoundPermutation(wb, roundN): 63 | 64 | inColumn, outColumn = getRoundColumn(wb, roundN) 65 | 66 | res = (outColumn == [(0, 1, 2, 3), (4, 5, 6, 7), 67 | (8, 9, 10, 11), (12, 13, 14, 15)]) 68 | if wb.isEncrypt(): 69 | res &= (inColumn == [(0, 5, 10, 15), (4, 9, 14, 3), 70 | (8, 13, 2, 7), (12, 1, 6, 11)]) 71 | else: 72 | res &= (inColumn == [(0, 13, 10, 7), (4, 1, 14, 11), 73 | (8, 5, 2, 15), (12, 9, 6, 3)]) 74 | 75 | return res 76 | 77 | 78 | def verifyRoundsPermutation(wb, rounds): 79 | 80 | return all([verifyRoundPermutation(wb, r) for r in rounds]) 81 | -------------------------------------------------------------------------------- /src/precomputeBetaTable.sage: -------------------------------------------------------------------------------- 1 | 2 | Q. = GF(2)[] 3 | P. = GF(2^8, name='x', modulus=y^8+y^4+y^3+y+1) 4 | assert (x^6+x^4+x^2+x+1) * (x^7+x+1) == x^7+x^6+1 5 | 6 | # p is a polynomial of P 7 | def poly_to_int(p): 8 | return sum([1 << power for power, value in enumerate(p.polynomial().coefficients(sparse=False)) if value]) 9 | 10 | # s is an integer 11 | def int_to_poly(s): 12 | L = Integer(s, 16) 13 | k = 0 14 | p = P(0) 15 | while L != 0: 16 | p = p + GF(2)(L & 1) * x^k 17 | k += 1 18 | L >>= 1 19 | return p 20 | 21 | P2 = [P(0)] + [P(1)] 22 | P4 = [P(0)] + [int_to_poly(0xbc)^i for i in range(1, 4)] 23 | P16 = [P(0)] + [int_to_poly(0x0d)^i for i in range(1, 16)] 24 | 25 | # Assert that all the elements of B are not in a subfield of P 26 | def assert_not_in_any_subfield(B): 27 | for b in B: 28 | assert (b not in [poly_to_int(k) for k in P2]) 29 | assert (b not in [poly_to_int(k) for k in P4]) 30 | assert (b not in [poly_to_int(k) for k in P16]) 31 | 32 | # h is a integer 33 | def get_matrice_multiplication(h): 34 | M = [] 35 | ph = int_to_poly(h) 36 | 37 | for i in range(7, -1, -1): 38 | p = int_to_poly(1 << i) 39 | r = poly_to_int(p * ph) 40 | M.append([GF(2)((r >> k) & 1) for k in range(7, -1, -1)]) 41 | return matrix(M) 42 | 43 | 44 | def convertResult(B): 45 | res = [] 46 | for b, coeff in B: 47 | p = get_matrice_multiplication(b).characteristic_polynomial().coefficients(sparse=False) 48 | v = sum([1 << power for power, value in enumerate(p) if value]) 49 | res.append((v, b, coeff)) 50 | return res 51 | 52 | def generateCombinaison(isEncrypt=True): 53 | 54 | if isEncrypt: 55 | pos = ((2, 3, 1, 1), (1, 2, 3, 1), (1, 1, 2, 3), (3, 1, 1, 2)) 56 | else: 57 | pos = ((14, 11, 13, 9), (9, 14, 11, 13), (13, 9, 14, 11), (11, 13, 9, 14)) 58 | 59 | comb = [] 60 | for i0 in range(4): 61 | for i1 in range(4): 62 | if i0 == i1: 63 | continue 64 | d = sorted([(pos[0][i0], pos[0][i1]), (pos[1][i0], pos[1][i1]), 65 | (pos[2][i0], pos[2][i1]), (pos[3][i0], pos[3][i1])]) 66 | if d not in comb: 67 | comb.append(d) 68 | comb2 = [] 69 | for c in comb: 70 | for b1 in range(3): 71 | b0 = 3 72 | b2 = ((b1 + 1) % 3) 73 | b3 = ((b1 + 2) % 3) 74 | r = sorted([sorted([c[b0], c[b1]]), sorted([c[b2], c[b3]])]) 75 | 76 | if r not in comb2: 77 | comb2.append(r) 78 | return comb2 79 | 80 | def generateCoeffList(isEncrypt): 81 | 82 | existingCars = {} 83 | for comb in generateCombinaison(isEncrypt): 84 | currentCoeff = [] 85 | for c in comb: 86 | ((a00, a01), (a10, a11)) = c 87 | c0 = sorted([a00, a11]) 88 | c1 = sorted([a10, a01]) 89 | a0 = int_to_poly(a00) * int_to_poly(a11) 90 | a1 = int_to_poly(a01) * int_to_poly(a10) 91 | currentCoeff.append(( poly_to_int(a0 / a1), (c0, c1))) 92 | currentCoeff.append(( poly_to_int(a1 / a0), (c1, c0))) 93 | assert_not_in_any_subfield([x for x, _ in currentCoeff]) 94 | currentCoeff = sorted(convertResult(currentCoeff)) 95 | 96 | req = tuple([c[0] for c in currentCoeff]) 97 | res = tuple([c[1] for c in currentCoeff]) 98 | coeff = tuple([c[2] for c in currentCoeff]) 99 | if req in existingCars: 100 | assert existingCars[req] == (res, coeff), f"{req}, {res}, {coeff}" 101 | else: 102 | existingCars[req] = (res, coeff) 103 | print(f"{req}, {res}, {coeff}") 104 | 105 | generateCoeffList(True) 106 | generateCoeffList(False) 107 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/BGEGenInput.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | import multiprocessing as mp 17 | from .ByteOrder import verifyRoundsPermutation 18 | import os 19 | 20 | 21 | def detectCPU(): 22 | try: 23 | return len(os.sched_getaffinity(0)) 24 | except: 25 | pass 26 | nCPU = os.cpu_count() 27 | if nCPU is not None: 28 | return nCPU 29 | else: 30 | return 0 31 | 32 | 33 | def encodeResult(x0, x2, x3): 34 | 35 | return [ 36 | [[o[b] for o in x0[y]] for y in range(256)] + 37 | [[o[b] for o in x2], [o[b] for o in x3]] 38 | for b in range(16)] 39 | 40 | 41 | def genBGEInputRound(wb, roundN): 42 | x0 = [] 43 | x2 = [] 44 | x3 = [] 45 | 46 | # type r:y(x,y,0,0,x,y,0,0,x,y,0,0,x,y,0,0)= 47 | for y in range(256): 48 | out = [] 49 | for x in range(256): 50 | data = bytes([x, y, 0, 0, x, y, 0, 0, x, y, 0, 0, x, y, 0, 0]) 51 | out.append(wb.applyRound(data, roundN)) 52 | 53 | assert len(out) == 256 54 | assert all([len(o) == 16 for o in out]) 55 | x0.append(out) 56 | 57 | assert len(x0) == 256 58 | 59 | # type r:y(0,0,x,0,0,0,x,0,0,0,x,0,0,0,x,0)= 60 | for x in range(256): 61 | data = bytes([0, 0, x, 0, 0, 0, x, 0, 0, 0, x, 0, 0, 0, x, 0]) 62 | x2.append(wb.applyRound(data, roundN)) 63 | 64 | assert len(x2) == 256 65 | assert all([len(o) == 16 for o in x2]) 66 | 67 | # type r:y(0,0,0,x,0,0,0,x,0,0,0,x,0,0,0,x)= 68 | for x in range(256): 69 | data = bytes([0, 0, 0, x, 0, 0, 0, x, 0, 0, 0, x, 0, 0, 0, x]) 70 | x3.append(wb.applyRound(data, roundN)) 71 | 72 | assert len(x3) == 256 73 | assert all([len(o) == 16 for o in x3]) 74 | 75 | # generate x0, x2 and x3 76 | # note: we export the result as roundN+1 (needed by the tools) 77 | return {(roundN+1): encodeResult(x0, x2, x3)} 78 | 79 | 80 | def init_localWB(wb): 81 | global localWB 82 | wb.newThread() 83 | localWB = wb 84 | 85 | 86 | def genBGEInputRoundProxy(roundN): 87 | global localWB 88 | return genBGEInputRound(localWB, roundN) 89 | 90 | 91 | # [param] wb instance of WhiteBoxedAES 92 | # [param] roundList list of rounds in [0, wb.getRoundNumber() - 1) 93 | # [param] nmultiProcess nprocess in the poll (0 to not use multiprocessing) 94 | def genBGEInput(wb, roundList, nmultiProcess=None): 95 | if nmultiProcess is None: 96 | nmultiProcess = detectCPU() 97 | 98 | assert verifyRoundsPermutation(wb, roundList), "Detected impact on the wrong column, use 'run(..., shuffle=True)'" 99 | 100 | result = {} 101 | 102 | if nmultiProcess == 0: 103 | for rnd in roundList: 104 | result.update(genBGEInputRound(wb, rnd)) 105 | else: 106 | assert nmultiProcess >= 1 107 | with mp.Pool(processes=nmultiProcess, initializer=init_localWB, initargs=(wb, )) as pool: 108 | res = [] 109 | for rnd in roundList: 110 | res.append(pool.apply_async(genBGEInputRoundProxy, (rnd, ))) 111 | for r in res: 112 | result.update(r.get()) 113 | return result 114 | 115 | -------------------------------------------------------------------------------- /src/approximateencoding.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef APPROXIMATEENCODING_HPP 15 | #define APPROXIMATEENCODING_HPP 16 | 17 | #include "baseencoding.hpp" 18 | #include "outputwb.hpp" 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | class ApproximateEncoding : public BaseEncoding { 25 | public: 26 | ApproximateEncoding() : BaseEncoding() {} 27 | 28 | bool build(const std::array, LEN_ARRAY> &owbs) { 29 | if (not std::all_of(owbs.cbegin(), owbs.cend(), [](const std::unique_ptr &o) { return bool(o); })) { 30 | std::cerr << "ApproximateEncoding::build: Missing OutputWB for x0" << std::endl; 31 | return false; 32 | } 33 | 34 | std::array functionSet; 35 | std::array valid{}; 36 | 37 | const OutputWB &outwb0 = *owbs[0]; 38 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 39 | BaseEncoding b = outwb0.compose(owbs[i]->inverse()); 40 | functionSet[b[0]] = b; 41 | valid[b[0]] = true; 42 | } 43 | 44 | if (not std::all_of(valid.cbegin(), valid.cend(), [](bool v) { return v; })) { 45 | std::cerr << "ApproximateEncoding::build: Invalid OutputWB for x0" << std::endl; 46 | return false; 47 | } 48 | 49 | // Tolhuizen algorithm 50 | std::array map {}; 51 | std::array mapInv {}; 52 | std::array inR {}; 53 | 54 | map[0] = 0; 55 | mapInv[0] = 0; 56 | inR[0] = true; 57 | 58 | unsigned int i = 1; 59 | for (unsigned int k = 0; k < 8; k++) { 60 | while (inR[i]) { 61 | i++; 62 | } 63 | 64 | map[i] = static_cast(1 << k); 65 | mapInv[1 << k] = i; 66 | inR[i] = true; 67 | for (unsigned int j = 1; j < LEN_ARRAY; j++) { 68 | if (inR[j]) { 69 | uint8_t fogid = functionSet[i][j]; 70 | uint8_t value = map[i] ^ map[j]; 71 | if (inR[fogid]) { 72 | if (map[fogid] != value) { 73 | std::cerr << "ApproximateEncoding::build: Unexpected value during the Tolhuizen algorithm" << std::endl; 74 | return false; 75 | } 76 | } else if (mapInv[value] != 0 or value == 0) { 77 | std::cerr << "ApproximateEncoding::build: Unexpected duplicated value during the Tolhuizen algorithm" << std::endl; 78 | return false; 79 | } else { 80 | map[fogid] = value; 81 | mapInv[value] = fogid; 82 | inR[fogid] = true; 83 | } 84 | } 85 | } 86 | } 87 | 88 | if (not std::all_of(inR.cbegin(), inR.cend(), [](bool v) { return v; })) { 89 | std::cerr << "ApproximateEncoding::build: Invalid result of the Tolhuizen algorithm" << std::endl; 90 | return false; 91 | } 92 | this->val = mapInv; 93 | this->inv = map; 94 | return true; 95 | } 96 | }; 97 | 98 | #endif /* APPROXIMATEENCODING_HPP */ 99 | 100 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .BGE import BGE 17 | from .AESEncoded import AESEncoded 18 | from .AES import RoundType 19 | from .WhiteBoxedAESTest import WhiteBoxedAESTest 20 | import argparse 21 | import secrets 22 | import time 23 | 24 | 25 | def test(isEncrypt, isShuffle): 26 | key_len = 32 27 | key = secrets.token_bytes(key_len) 28 | seed = secrets.randbits(64) 29 | # Note : roundType is chosen here to check the permutation and 30 | # encoding found by the attack. With a real whitebox, you cannot 31 | # choose which encoding state you can provide. The tools supports any 32 | # encoding intermediary state between two MixColumns. The functions 33 | # getShuffle() and getEncoding() will return the permutation and 34 | # encoding to transform the encoding position provided to the clear 35 | # intermediary state just after a MixColumns (or InvMixColumns). 36 | roundType = RoundType.RoundEncType0 if isEncrypt else RoundType.RoundDecType0 37 | aesEncoded = AESEncoded(key, roundType=roundType, encodingSeed=seed, 38 | byteSwap=isShuffle) 39 | 40 | print("=== {} {} ===".format( 41 | "Shuffle" if isShuffle else "Unshuffle", 42 | "Encrypt" if isEncrypt else "Decrypt")) 43 | print(f"Key = {key.hex()}") 44 | print(f"Seed = {seed}") 45 | 46 | start = time.time() 47 | bge = BGE(WhiteBoxedAESTest(aesEncoded, isEncrypt)) 48 | 49 | if False: 50 | bge.generateInput(shuffle=isShuffle) 51 | bge.saveTo("/tmp/bge_test.json") 52 | 53 | bge = BGE.loadFrom("/tmp/bge_test.json") 54 | bge.resolve() 55 | else: 56 | bge.run(shuffle=isShuffle) 57 | 58 | assert key == bge.computeKey() 59 | stop = time.time() 60 | 61 | print(f"Duration : {stop - start}") 62 | print("Key: OK") 63 | for i, enc in enumerate(bge.getEncoding()): 64 | if enc is not None: 65 | assert enc == aesEncoded.encoding[i] 66 | # Did we recover all expected encodings? 67 | if all([bge.roundkey[i] is not None for i, x in enumerate(bge.roundkey) if x]): 68 | print("Encodings: OK") 69 | for i, perm in bge.getShuffle().items(): 70 | assert perm == aesEncoded.roundPerm[i] 71 | print("Permutation: OK") 72 | 73 | 74 | def run(): 75 | parser = argparse.ArgumentParser() 76 | parser.add_argument('--selftest', action='store_true', help="Perform all self tests") 77 | parser.add_argument('--enctest', action='store_true', help="Perform enc self tests") 78 | parser.add_argument('--dectest', action='store_true', help="Perform dec self tests") 79 | parser.add_argument('--encShuffletest', action='store_true', help="Perform enc self tests") 80 | parser.add_argument('--decShuffletest', action='store_true', help="Perform enc self tests") 81 | 82 | args = parser.parse_args() 83 | 84 | # from .AESEncoded import test as testAESEncoded 85 | # testAESEncoded() 86 | 87 | if args.selftest or args.enctest: 88 | test(True, False) 89 | 90 | if args.selftest or args.dectest: 91 | test(False, False) 92 | 93 | if args.selftest or args.encShuffletest: 94 | test(True, True) 95 | 96 | if args.selftest or args.decShuffletest: 97 | test(False, True) 98 | 99 | 100 | if __name__ == "__main__": 101 | run() 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Blue Galaxy Energy 2 | 3 | *Hologram: Shut up! You do not know the power of the Blue Galaxy Energy! Also known as the "B.G.E"* 4 | *Mr. Whereabout: The Loss, Part III, Volume I* 5 | 6 | BlueGalaxyEnergy is a tool to perform the so-called BGE attack described in 7 | 8 | - *Cryptanalysis of a White Box AES Implementation*, Olivier Billet, Henri Gilbert, Charaf Ech-Chatbi 9 | 10 | with the optimizations proposed in: 11 | 12 | - *Improved cryptanalysis of an AES implementation*, Ludo Tolhuizen 13 | - *Revisiting the BGE Attack on a White-Box AES Implementation*, Yoni De Mulder, Peter Roelse, Bart Preneel 14 | 15 | ## Compile 16 | 17 | To compile and install the project, install gmp and ntl libraries and development headers (available in your OS package manager), e.g. 18 | 19 | ```bash 20 | $ sudo apt install libgmp-dev libntl-dev 21 | ``` 22 | or on Arch 23 | ```bash 24 | $ sudo pacman -S gmp ntl 25 | ``` 26 | or on macOS 27 | ```bash 28 | $ brew install gmp ntl 29 | $ export CPATH=/opt/homebrew/opt/gmp/include:/opt/homebrew/opt/ntl/include 30 | ``` 31 | 32 | then compile and install the package locally: 33 | 34 | ```bash 35 | $ pip install bluegalaxyenergy 36 | ``` 37 | 38 | ## Test 39 | 40 | ```bash 41 | $ python3 -m bluegalaxyenergy --selftest 42 | ``` 43 | 44 | ## Run the attack 45 | 46 | ```python 47 | from bluegalaxyenergy import WhiteBoxedAES, BGE 48 | 49 | class MyWhiteBoxedAES(WhiteBoxedAES): 50 | 51 | def __init__(self, ...): 52 | # TODO 53 | 54 | def isEncrypt(self): 55 | # return True if the whitebox is an encryption whitebox, False otherwise 56 | return True 57 | 58 | def getRoundNumber(self): 59 | # return the number of rounds of the whitebox (10 for AES128, 60 | # 12 for AES192 and 14 for AES256) 61 | return 10 62 | 63 | def applyRound(self, data, roundN): 64 | # Apply a round of the whitebox on a buffer 65 | # [param] data a buffer of 16 bytes (type bytes) 66 | # [param] roundN the round number to apply (int in the range [0, self.getRoundNumber()) ) 67 | # return 16 bytes of the encrypted data by the round 68 | return ... # TODO 69 | 70 | mywb = MyWhiteBoxedAES(...) 71 | 72 | # run the attack 73 | bge = BGE(mywb) 74 | bge.run() 75 | 76 | # extract the key from the available roundKey 77 | key = bge.computeKey() 78 | #if key is None: 79 | # # Note, depending of your implementation of the whitebox, 80 | # # the applyRound may not have the same numbering as the keyschedule 81 | # # (ie, we expect to have a MixColumn during the round 0). 82 | # # In this case, you can try to shift the key schedule to verify if a key can 83 | # # be found 84 | # key = bge.computeKey(offset=1) 85 | 86 | if key is None: 87 | print("Key not found") 88 | elif type(key) is dict: 89 | for k, v in key.items(): 90 | print(f"key {k}: {v.hex()}") 91 | else: 92 | print("key:", key.hex()) 93 | ``` 94 | 95 | By default, the method `run` extracts all rounds except for the last one, which 96 | lacks a MixColumns operation. You can restrict the rounds used for the attack 97 | using the `roundList` option: `run(roundList = [4, 5, 6, 7, 8])`. 98 | However, the attack requires a minimum of three consecutive rounds to extract a single round key. 99 | 100 | - AES128 necessitates a minimum of three consecutive rounds for key extraction. 101 | - AES192 and AES256 require a minimum of four consecutive rounds. 102 | 103 | If the whitebox's intermediary state is shuffled, specify `shuffle=True` in the `run` method. 104 | When shuffled, the number of required rounds increases to four for AES128 and AES192, and five for AES256. 105 | Insufficient rounds will result in no key being found or a dictionary containing 16 possible keys. 106 | 107 | The bge class can additionally extract the encoding of the intermediary round (with `bge.getEncoding()`) 108 | and the permutation of the intermediary round if the whitebox is shuffled (with `bge.getShuffle()`). 109 | For more information on these elements, refer to the test implemented in [`__main__.py`](src/bluegalaxyenergy/__main__.py). 110 | 111 | ## About 112 | 113 | ### Authors and Contributors 114 | 115 | Initial Authors and Contributors: 116 | 117 | - Laurent Grémy 118 | - Nicolas Surbayrole 119 | - Philippe Teuwen 120 | 121 | For subsequent contributions, see the git projet history. 122 | 123 | ### Copyright 124 | 125 | [Quarkslab](https://www.quarkslab.com) 126 | 127 | ### License 128 | 129 | BlueGalaxyEnergy is provided under the [Apache 2.0 license](LICENSE.txt). 130 | -------------------------------------------------------------------------------- /src/baseencoding.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef BASEENCODING_HPP 15 | #define BASEENCODING_HPP 16 | 17 | #include "utils.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | // This class is an encoding, i.e. a bijective function on integer in [0,256) 25 | class BaseEncoding { 26 | protected: 27 | std::array val = {}; 28 | std::array inv = {}; 29 | 30 | constexpr BaseEncoding(const std::array &arr, 31 | const std::array &invarr) 32 | : val(arr), inv(invarr) {} 33 | 34 | constexpr void computeInv() { 35 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 36 | inv[val[i]] = static_cast(i); 37 | } 38 | } 39 | 40 | template 41 | static std::unique_ptr from_array(const std::array &val) { 42 | std::array used = {}; 43 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 44 | if (used[val[i]]) { 45 | return {}; 46 | } else { 47 | used[val[i]] = true; 48 | } 49 | } 50 | 51 | std::unique_ptr e = std::make_unique(val); 52 | if (e->isvalid()) { 53 | return e; 54 | } 55 | return {}; 56 | } 57 | 58 | constexpr bool isvalid() const { 59 | bool valid = false; 60 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 61 | if (inv[i] == 0) { 62 | if (valid) { 63 | return false; 64 | } else { 65 | valid = true; 66 | } 67 | } 68 | } 69 | return valid; 70 | } 71 | 72 | public: 73 | 74 | // an encoding should always be valid. 75 | // The default constructor is the identity function 76 | constexpr BaseEncoding() { 77 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 78 | val[i] = static_cast(i); 79 | inv[i] = static_cast(i); 80 | } 81 | } 82 | 83 | constexpr BaseEncoding(const std::array &arr) : val(arr) { 84 | computeInv(); 85 | if (! isvalid()) { 86 | std::cerr << "BaseEncoding::isvalid : False, abort!" << std::endl; 87 | abort(); 88 | } 89 | } 90 | 91 | constexpr uint8_t getVal(unsigned int i) const { return val[i]; } 92 | constexpr uint8_t operator[](unsigned int i) const { return val[i]; } 93 | 94 | constexpr uint8_t getInv(unsigned int i) const { return inv[i]; } 95 | 96 | bool operator==(const BaseEncoding &other) const { 97 | return this->val == other.val; 98 | }; 99 | 100 | bool operator!=(const BaseEncoding &other) const { 101 | return this->val != other.val; 102 | }; 103 | 104 | constexpr BaseEncoding inverse() const { 105 | return BaseEncoding(inv, val); 106 | } 107 | 108 | // do this o other == this(other(...)) 109 | constexpr BaseEncoding compose(const BaseEncoding &other) const { 110 | std::array n_val = {}; 111 | std::array n_inv = {}; 112 | for (unsigned int i = 0; i < LEN_ARRAY; i++) { 113 | n_val[i] = val[other[i]]; 114 | n_inv[n_val[i]] = i; 115 | } 116 | return BaseEncoding(n_val, n_inv); 117 | } 118 | 119 | const std::array &getEncodingArray() const { 120 | return val; 121 | } 122 | }; 123 | 124 | MAYBE_UNUSED static std::ostream &operator<<(std::ostream &os, 125 | const BaseEncoding &e) { 126 | os << static_cast(e[0]); 127 | for (unsigned int r = 1; r < LEN_ARRAY; r++) { 128 | os << ", " << static_cast(e[r]); 129 | } 130 | 131 | return os; 132 | } 133 | 134 | #endif /* BASEENCODING_HPP */ 135 | 136 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/WhiteBoxedUnshuffledProxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .WhiteBoxedAES import WhiteBoxedAES 17 | from .AES import InvShiftRow, ShiftRow, _AesShiftRow, _AesInvShiftRow 18 | from .ByteOrder import verifyRoundsPermutation, getRoundColumn 19 | import itertools 20 | 21 | 22 | class WhiteBoxedUnshuffledProxy(WhiteBoxedAES): 23 | 24 | def __init__(self, basewb, targetRound): 25 | assert isinstance(basewb, WhiteBoxedAES) 26 | self.basewb = basewb 27 | self.targetRound = targetRound 28 | self.roundPerm = [] 29 | self.reverseRoundPerm = [] 30 | self.preColumn = {} 31 | self.postColumn = {} 32 | 33 | self._isEncrypt = self.basewb.isEncrypt() 34 | self._roundNumber = self.basewb.getRoundNumber() 35 | 36 | for i in range(self.getRoundNumber()): 37 | self.roundPerm.append(None) 38 | self.reverseRoundPerm.append(None) 39 | self.setRoundPermutation(i, list(range(16))) 40 | 41 | def isEncrypt(self): 42 | return self._isEncrypt 43 | 44 | def getRoundNumber(self): 45 | return self._roundNumber 46 | 47 | def newThread(self): 48 | self.basewb.newThread() 49 | 50 | def applyRound(self, data, roundN): 51 | assert roundN in self.targetRound 52 | if roundN != 0: 53 | data = self.applyReversePermutation(data, roundN - 1, True) 54 | data = self.basewb.applyRound(data, roundN) 55 | if roundN + 1 != self.getRoundNumber(): 56 | data = self.applyPermutation(data, roundN, True) 57 | return data 58 | 59 | def applyPermutation(self, data, roundN, isBytes=True): 60 | res = [data[x] for x in self.roundPerm[roundN]] 61 | return bytes(res) if isBytes else res 62 | 63 | def applyReversePermutation(self, data, roundN, isBytes=True): 64 | res = [data[x] for x in self.reverseRoundPerm[roundN]] 65 | return bytes(res) if isBytes else res 66 | 67 | def setRoundPermutation(self, roundN, perm): 68 | assert len(set(perm)) == 16 69 | assert set(perm) == set(range(16)) 70 | self.roundPerm[roundN] = perm 71 | self.reverseRoundPerm[roundN] = [perm.index(i) for i in range(16)] 72 | 73 | # First step : organize a whitebox based on round column 74 | # During this phase, we want to emulate a good propagation of bytes. 75 | # i.e., for each round in encrypt, any change in input bytes 0, 5, 10 or 15 76 | # must have an impact on the first column of the output 77 | # Without this, the method genBGEInput will not generate good data and BGE 78 | # cannot work 79 | def computeSimplePermutation(self): 80 | assert len(self.preColumn.keys()) == 0 81 | assert len(self.postColumn.keys()) == 0 82 | 83 | for roundN in sorted(self.targetRound): 84 | self.detectConstraint(roundN) 85 | 86 | assert verifyRoundsPermutation(self, self.targetRound), "Fail to construct valid permutation" 87 | 88 | def detectConstraint(self, roundN): 89 | self.preColumn[roundN], self.postColumn[roundN] = getRoundColumn(self.basewb, roundN) 90 | 91 | self.setRoundPermutation(roundN, list(itertools.chain.from_iterable(self.postColumn[roundN]))) 92 | 93 | if roundN == 0: 94 | return 95 | elif (roundN - 1) not in self.targetRound: 96 | perm1 = list(itertools.chain.from_iterable(self.preColumn[roundN])) 97 | else: 98 | prevPermut = self.postColumn[roundN - 1] 99 | curPermut = self.preColumn[roundN] 100 | possibleByte = [set(prevPermut[x >> 2]).intersection( 101 | set(curPermut[pos >> 2])) 102 | for pos, x in enumerate(_AesShiftRow if 103 | self.isEncrypt() else 104 | _AesInvShiftRow)] 105 | 106 | assert all([len(x) == 1 for x in possibleByte]) 107 | perm1 = list(itertools.chain.from_iterable(possibleByte)) 108 | assert len(set(perm1)) == 16 109 | 110 | assert all([set(self.preColumn[roundN][i]) == set(perm1[i*4:(i+1)*4]) for i in range(4)]) 111 | self.preColumn[roundN] = [tuple(perm1[i*4:(i+1)*4]) for i in range(4)] 112 | 113 | if self.isEncrypt(): 114 | perm2 = list(InvShiftRow(perm1)) 115 | else: 116 | perm2 = list(ShiftRow(perm1)) 117 | 118 | self.setRoundPermutation(roundN - 1, perm2) 119 | 120 | if (roundN - 1) in self.targetRound: 121 | assert all([set(self.postColumn[roundN - 1][i]) == set(perm2[i*4:(i+1)*4]) for i in range(4)]) 122 | self.postColumn[roundN - 1] = [tuple(perm2[i*4:(i+1)*4]) for i in range(4)] 123 | 124 | def exctractPermutation(self): 125 | return [self.preColumn, self.postColumn, self.roundPerm] 126 | 127 | -------------------------------------------------------------------------------- /src/inputReader.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef INPUT_READER_HPP 15 | #define INPUT_READER_HPP 16 | 17 | #include "outputwb.hpp" 18 | #include "utils.hpp" 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | class InputReader { 30 | private: 31 | 32 | // type (x,y,0,0,x,y,0,0,x,y,0,0,x,y,0,0) with y in [0,256) 33 | std::array, LEN_ARRAY>, 16>, AES_ROUND> x0 = {}; 34 | // type (0,x,0,0,0,x,0,0,0,x,0,0,0,x,0,0) 35 | std::array, 16>, AES_ROUND> x1 = {}; 36 | // type (0,0,x,0,0,0,x,0,0,0,x,0,0,0,x,0) 37 | std::array, 16>, AES_ROUND> x2 = {}; 38 | // type (0,0,0,x,0,0,0,x,0,0,0,x,0,0,0,x) 39 | std::array, 16>, AES_ROUND> x3 = {}; 40 | 41 | std::array roundValid = {0}; 42 | 43 | public: 44 | InputReader() {} 45 | 46 | void readData(const std::map < uint8_t, std::array < std::array < std::array, LEN_ARRAY + 2 >, 16 >> & data) { 47 | for (auto const &el : data) { 48 | addRoundData(el.first - 1, el.second); 49 | } 50 | } 51 | 52 | bool isRoundAvailable(int round) const { 53 | if (round < 0 or round >= AES_ROUND) { 54 | return false; 55 | } 56 | return roundValid[round]; 57 | } 58 | 59 | const std::array, LEN_ARRAY> &getx0_all(int round, int column, int row) const { 60 | return x0[round][column * 4 + row]; 61 | } 62 | 63 | const OutputWB &getx(int x, int round, int column, int row) const { 64 | 65 | switch (x) { 66 | case 0: 67 | if (! x0[round][column * 4 + row][0]) { 68 | std::cerr << "InputReader::getx : x0[round][column*4+row][0] is NULL, abort!" << std::endl; 69 | abort(); 70 | } 71 | return *x0[round][column * 4 + row][0]; 72 | case 1: 73 | if (! x1[round][column * 4 + row]) { 74 | std::cerr << "InputReader::getx : x1[round][column*4+row] is NULL, abort!" << std::endl; 75 | abort(); 76 | } 77 | return *x1[round][column * 4 + row]; 78 | case 2: 79 | if (! x2[round][column * 4 + row]) { 80 | std::cerr << "InputReader::getx : x2[round][column*4+row] is NULL, abort!" << std::endl; 81 | abort(); 82 | } 83 | return *x2[round][column * 4 + row]; 84 | case 3: 85 | if (! x3[round][column * 4 + row]) { 86 | std::cerr << "InputReader::getx : x3[round][column*4+row] is NULL, abort!" << std::endl; 87 | abort(); 88 | } 89 | return *x3[round][column * 4 + row]; 90 | default: 91 | abort(); 92 | } 93 | } 94 | 95 | private: 96 | void addRoundData(unsigned int round, const std::array < std::array < std::array, LEN_ARRAY + 2 >, 16 > & arr) { 97 | 98 | if (round >= AES_ROUND) { 99 | std::cerr << "Invalid round " << (round + 1) << std::endl; 100 | return; 101 | } 102 | 103 | if (isRoundAvailable(round)) { 104 | std::cerr << "Round " << (round + 1) << " already imported" << std::endl; 105 | return; 106 | } 107 | 108 | for (int pos = 0; pos < 16; pos++) { 109 | for (int y = 0; y < LEN_ARRAY; y++) { 110 | if (not addLine(x0[round][pos][y], arr[pos][y])) { 111 | std::cerr << "Fail import x0 round " << (round + 1) 112 | << ", byte " << pos 113 | << ", y " << y << std::endl; 114 | return; 115 | } 116 | } 117 | if (not computeX1(round, pos)) { 118 | std::cerr << "Fail compute x1 round " << (round + 1) 119 | << ", byte " << pos << std::endl; 120 | return; 121 | } 122 | if (not addLine(x2[round][pos], arr[pos][256])) { 123 | std::cerr << "Fail import x2 round " << (round + 1) 124 | << ", byte " << pos << std::endl; 125 | return; 126 | } 127 | if (not addLine(x3[round][pos], arr[pos][257])) { 128 | std::cerr << "Fail import x3 round " << (round + 1) 129 | << ", byte " << pos << std::endl; 130 | return; 131 | } 132 | } 133 | roundValid[round] = true; 134 | } 135 | 136 | inline bool addLine(std::unique_ptr &dest, const std::array &v) { 137 | 138 | std::unique_ptr p = OutputWB::from_array(v); 139 | 140 | if (p) { 141 | dest = std::move(p); 142 | return true; 143 | } else { 144 | return false; 145 | } 146 | } 147 | 148 | inline bool computeX1(int round, int pos) { 149 | if (not std::all_of(x0[round][pos].cbegin(), x0[round][pos].cend(), 150 | [](const std::unique_ptr &o) { return bool(o); })) { 151 | return false; 152 | } 153 | 154 | std::array v; 155 | for (size_t i = 0; i < LEN_ARRAY; i++) { 156 | v[i] = x0[round][pos][i]->getVal(0); 157 | } 158 | 159 | std::unique_ptr e = OutputWB::from_array(v); 160 | if (e) { 161 | x1[round][pos] = std::move(e); 162 | return true; 163 | } else { 164 | return false; 165 | } 166 | } 167 | }; 168 | 169 | #endif 170 | 171 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/Encoding.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from enum import Enum, auto 17 | import random 18 | 19 | 20 | def isPermutation(perm): 21 | assert len(perm) == 256 22 | s = set() 23 | for val in perm: 24 | if val in s or val < 0 or val > 255: 25 | return False 26 | else: 27 | s.add(val) 28 | return True 29 | 30 | 31 | class Encoding8: 32 | 33 | def __init__(self, permutation): 34 | assert isPermutation(permutation) 35 | self.encoded = list(permutation) 36 | self._genInverse() 37 | 38 | def _genInverse(self): 39 | self.plain = [None for i in range(256)] 40 | for i, n in enumerate(self.encoded): 41 | self.plain[n] = i 42 | 43 | def getInverseEncoding(self): 44 | return Encoding8(self.plain) 45 | 46 | def getEncodeTable(self): 47 | return self.encoded 48 | 49 | def getDecodeTable(self): 50 | return self.plain 51 | 52 | def __getitem__(self, x): 53 | return self.encoded[x] 54 | 55 | def encode(self, data): 56 | return bytes([self.encoded[x] for x in data]) 57 | 58 | def decode(self, data): 59 | return bytes([self.plain[x] for x in data]) 60 | 61 | def encodeOne(self, data): 62 | return self.encoded[data] 63 | 64 | def decodeOne(self, data): 65 | return self.plain[data] 66 | 67 | # compute the table for self(other(x)) ( self o other ) 68 | def combine(self, other): 69 | return Encoding8(self.encode(other.getEncodeTable())) 70 | 71 | def __eq__(self, other): 72 | return all([x == y for x, y in zip(self.encoded, other.getEncodeTable())]) 73 | 74 | def __repr__(self): 75 | return "Encoding (" + ", ".join([f"0x{x:02x}" for x in self.getEncodeTable()]) + ")" 76 | 77 | 78 | class Encoding8Xor(Encoding8): 79 | 80 | def __init__(self, value): 81 | tab = [x ^ value for x in range(256)] 82 | super().__init__(tab) 83 | 84 | 85 | class Encoding8Random(Encoding8): 86 | 87 | def __init__(self, seed=None): 88 | if seed is not None: 89 | state = random.getstate() 90 | random.seed(seed) 91 | 92 | tab = list(range(256)) 93 | random.shuffle(tab) 94 | super().__init__(tab) 95 | 96 | if seed is not None: 97 | random.setstate(state) 98 | 99 | 100 | class Encoding8Identity(Encoding8): 101 | 102 | def __init__(self): 103 | super().__init__(list(range(256))) 104 | 105 | 106 | class Encoding: 107 | 108 | def __init__(self, encodingList): 109 | self.encodingList = encodingList 110 | self.length = len(self.encodingList) 111 | 112 | @classmethod 113 | def fromTable(cls, tables): 114 | return cls([Encoding8(e) for e in tables]) 115 | 116 | def toTable(self): 117 | return [x.getEncodeTable() for x in self.encodingList] 118 | 119 | def __getitem__(self, x): 120 | return self.encodingList[x] 121 | 122 | # compute the table for self(other(x)) ( self o other ) 123 | def combine(self, other): 124 | return Encoding([x.combine(y) for x, y in zip(self.encodingList, other.encodingList)]) 125 | 126 | def getInverseEncoding(self): 127 | return Encoding([e.getInverseEncoding() for e in self.encodingList]) 128 | 129 | def encode(self, data): 130 | assert len(data) % self.length == 0 131 | r = b"" 132 | for i in range(0, len(data), self.length): 133 | r += bytes([t.encodeOne(x) for t, x in zip(self.encodingList, data[i:i+self.length])]) 134 | return r 135 | 136 | def decode(self, data): 137 | assert len(data) % self.length == 0 138 | r = b"" 139 | for i in range(0, len(data), self.length): 140 | r += bytes([t.decodeOne(x) for t, x in zip(self.encodingList, data[i:i+self.length])]) 141 | return r 142 | 143 | def __eq__(self, other): 144 | return all([x == y for x, y in zip(self.encodingList, other.encodingList)]) 145 | 146 | 147 | class EncodingXor(Encoding): 148 | 149 | def __init__(self, value): 150 | tab = [Encoding8Xor(x) for x in value] 151 | super().__init__(tab) 152 | 153 | 154 | class EncodeType(Enum): 155 | RANDOM = auto() 156 | IDENTITY = auto() 157 | 158 | 159 | class EncodingGenerator(Encoding): 160 | 161 | encClass = { 162 | EncodeType.RANDOM: Encoding8Random, 163 | EncodeType.IDENTITY: Encoding8Identity, 164 | } 165 | 166 | def __init__(self, length, encodeType, seed=None): 167 | self.encodeType = encodeType 168 | self.seed = seed 169 | super().__init__(self.regenerate(length)) 170 | 171 | def setType(self, encodeType): 172 | if self.encodeType != encodeType: 173 | self.encodeType = encodeType 174 | self.encodingList = self.regenerate(self.length) 175 | 176 | def regenerate(self, length): 177 | c = self.encClass[self.encodeType] 178 | if self.seed is None or self.encodeType == EncodeType.IDENTITY: 179 | return [c() for _ in range(length)] 180 | elif isinstance(self.seed, int): 181 | return [c(self.seed+i) for i in range(length)] 182 | elif isinstance(self.seed, bytes): 183 | return [c(self.seed+bytes(i)) for i in range(length)] 184 | else: 185 | return [c(self.seed) for _ in range(length)] 186 | 187 | 188 | def test(): 189 | for _ in range(32): 190 | obj = Encoding8Random() 191 | enc = obj.getEncodeTable() 192 | dec = obj.getDecodeTable() 193 | for i in range(256): 194 | assert dec[enc[i]] == i, "Encoding8Random encodage error" 195 | print("[OK] Encoding8Random") 196 | 197 | for _ in range(32): 198 | obj1 = Encoding8Random() 199 | obj2 = Encoding8Random() 200 | obj12 = obj1.combine(obj2) 201 | assert obj1.encode(obj2.encode(list(range(256)))) == obj12.encode(list(range(256))) 202 | print("[OK] Encoding8 combine") 203 | 204 | for _ in range(32): 205 | obj1 = EncodingGenerator(16, EncodeType.RANDOM) 206 | obj2 = EncodingGenerator(16, EncodeType.RANDOM) 207 | obj12 = obj1.combine(obj2) 208 | for _ in range(32): 209 | data = random.randbytes(16) 210 | assert obj1.encode(obj2.encode(data)) == obj12.encode(data) 211 | print("[OK] Encoding combine") 212 | 213 | 214 | if __name__ == '__main__': 215 | test() 216 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/FinalizeUnshuffled.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .AES import InvShiftRow, ShiftRow, _AesShiftRow, _AesInvShiftRow 17 | import itertools 18 | 19 | 20 | class FinalizeUnshuffled: 21 | 22 | def __init__(self, isEncrypt, preColumn, postColumn, roundPerm): 23 | self._isEncrypt = isEncrypt 24 | self.coeffRound = [] 25 | self.roundPerm = [] 26 | self.reverseRoundPerm = [] 27 | self.preColumn = preColumn 28 | self.postColumn = postColumn 29 | 30 | for i, perm in enumerate(roundPerm): 31 | self.roundPerm.append(None) 32 | self.reverseRoundPerm.append(None) 33 | if perm is None: 34 | self.setRoundPermutation(i, list(range(16))) 35 | else: 36 | self.setRoundPermutation(i, perm) 37 | 38 | def isEncrypt(self): 39 | return self._isEncrypt 40 | 41 | def applyPermutation(self, data, roundN, isBytes=True): 42 | res = [data[x] for x in self.roundPerm[roundN]] 43 | return bytes(res) if isBytes else res 44 | 45 | def applyReversePermutation(self, data, roundN, isBytes=True): 46 | res = [data[x] for x in self.reverseRoundPerm[roundN]] 47 | return bytes(res) if isBytes else res 48 | 49 | def setRoundPermutation(self, roundN, perm): 50 | assert len(set(perm)) == 16 51 | assert set(perm) == set(range(16)) 52 | self.roundPerm[roundN] = perm 53 | self.reverseRoundPerm[roundN] = [perm.index(i) for i in range(16)] 54 | 55 | # Second step : After performing BGE, we know the coefficient of the 56 | # MixColumns for every round columns. During this step, we change the order 57 | # of each column in order to match genuine coefficient. 58 | def reorderWithCoeff(self, columnCoeff): 59 | assert len(self.coeffRound) == 0 60 | assert len(self.preColumn.keys()) != 0 61 | assert len(self.postColumn.keys()) != 0 62 | 63 | for roundN, roundCoeffs in columnCoeff.items(): 64 | for colN, coeffs in enumerate(roundCoeffs): 65 | self.applyCoeff(roundN, colN, coeffs) 66 | self.coeffRound.append(roundN) 67 | 68 | self.coeffRound.sort() 69 | assert self.coeffRound == list(range(self.coeffRound[0], 70 | self.coeffRound[0] + 71 | len(self.coeffRound))) 72 | 73 | def applyCoeff(self, roundN, colN, coeffs): 74 | v0 = 3 if self.isEncrypt() else 11 75 | v1 = 2 if self.isEncrypt() else 14 76 | 77 | inList = [] 78 | outList = [] 79 | p = 3 80 | for _ in range(4): 81 | s = None 82 | for s_, t in enumerate(coeffs): 83 | if t[p] == v0: 84 | s = s_ 85 | p = t.index(v1) 86 | break 87 | assert s is not None 88 | assert s not in inList 89 | inList.append(s) 90 | outList.append(p) 91 | assert p == 3 92 | 93 | assert roundN in self.preColumn 94 | assert roundN in self.postColumn 95 | 96 | preCol = self.preColumn[roundN][colN] 97 | postCol = self.postColumn[roundN][colN] 98 | 99 | self.preColumn[roundN][colN] = tuple([preCol[x] for x in inList]) 100 | self.postColumn[roundN][colN] = tuple([postCol[x] for x in outList]) 101 | # coeff = [[coeffs[x][y] for y in outList] for x in inList] 102 | 103 | # Third step : After the second step, we only have 16 possibilities. 104 | # We only need to choose which byte should be put at the first one. 105 | # This function reorganizes the proxy in order to provide the only 106 | # possibility with the byte N as the first input of the first supported round 107 | def setFirstByte(self, N): 108 | assert len(self.coeffRound) >= 2 109 | assert self.coeffRound == list(range(self.coeffRound[0], 110 | self.coeffRound[0] + 111 | len(self.coeffRound))) 112 | 113 | firstCoeff = self.coeffRound[0] 114 | self._setByteAt(firstCoeff, N, 0) 115 | 116 | for roundN in range(firstCoeff + 1, 117 | firstCoeff + len(self.coeffRound)): 118 | 119 | for i in range(4): 120 | dest = _AesInvShiftRow[i] if self.isEncrypt() else _AesShiftRow[i] 121 | self._setByteAt(roundN, self.postColumn[roundN-1][0][i], dest) 122 | 123 | for i in range(1, 4): 124 | dest = _AesShiftRow[i] if self.isEncrypt() else _AesInvShiftRow[i] 125 | self._setByteAt(firstCoeff, self.preColumn[firstCoeff+1][0][i], dest, pre=False) 126 | 127 | if firstCoeff != 0: 128 | perm1 = list(itertools.chain.from_iterable(self.preColumn[firstCoeff])) 129 | 130 | if self.isEncrypt(): 131 | perm1 = list(InvShiftRow(perm1)) 132 | else: 133 | perm1 = list(ShiftRow(perm1)) 134 | 135 | self.setRoundPermutation(firstCoeff - 1, perm1) 136 | 137 | for roundN in self.coeffRound: 138 | self.setRoundPermutation(roundN, list(itertools.chain.from_iterable(self.postColumn[roundN]))) 139 | 140 | def _setByteAt(self, roundN, byteValue, target, pre=True): 141 | assert 0 <= target and target < 16 142 | assert 0 <= byteValue and byteValue < 16 143 | 144 | targetCol = (target >> 2) 145 | targetRow = (target & 3) 146 | 147 | currentCol = None 148 | for col, t in enumerate(self.preColumn[roundN] if pre else self.postColumn[roundN]): 149 | try: 150 | currentRow = t.index(byteValue) 151 | currentCol = col 152 | break 153 | except ValueError: 154 | continue 155 | 156 | assert currentCol is not None 157 | 158 | if currentCol != targetCol: 159 | self.preColumn[roundN][currentCol], self.preColumn[roundN][targetCol] = \ 160 | self.preColumn[roundN][targetCol], self.preColumn[roundN][currentCol] 161 | self.postColumn[roundN][currentCol], self.postColumn[roundN][targetCol] = \ 162 | self.postColumn[roundN][targetCol], self.postColumn[roundN][currentCol] 163 | 164 | if targetRow != currentRow: 165 | shift = ((currentRow + 4) - targetRow) % 4 166 | self.preColumn[roundN][targetCol] = \ 167 | self.preColumn[roundN][targetCol][shift:] + self.preColumn[roundN][targetCol][:shift] 168 | self.postColumn[roundN][targetCol] = \ 169 | self.postColumn[roundN][targetCol][shift:] + self.postColumn[roundN][targetCol][:shift] 170 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/AESEncoded.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .AES import AES, RoundType 17 | from .Encoding import EncodeType, EncodingGenerator 18 | import random 19 | 20 | 21 | class AESEncoded(AES): 22 | 23 | AES_KEY_ROUND = {16: 10, 24: 12, 32: 14} 24 | 25 | def __init__(self, key, encodingType=EncodeType.RANDOM, encodingSeed=None, 26 | byteSwap=False, roundType=RoundType.RoundEncType0): 27 | 28 | super().__init__(key, self.AES_KEY_ROUND[len(key)], roundType=roundType) 29 | 30 | if encodingSeed is None: 31 | self.encoding = [EncodingGenerator(16, encodingType) 32 | for _ in range(self.AES_KEY_ROUND[len(key)] + 1)] 33 | elif isinstance(encodingSeed, int): 34 | self.encoding = [EncodingGenerator(16, encodingType, seed=encodingSeed+i*16) 35 | for i in range(self.AES_KEY_ROUND[len(key)] + 1)] 36 | elif isinstance(encodingSeed, bytes): 37 | self.encoding = [EncodingGenerator(16, encodingType, seed=encodingSeed+bytes([i])) 38 | for i in range(self.AES_KEY_ROUND[len(key)] + 1)] 39 | else: 40 | self.encoding = [EncodingGenerator(16, encodingType, seed=encodingSeed) 41 | for i in range(self.AES_KEY_ROUND[len(key)] + 1)] 42 | 43 | if not byteSwap: 44 | self.roundPerm = [list(range(16)) for _ in range(self.r)] 45 | elif encodingSeed is None: 46 | self.roundPerm = [random.sample(list(range(16)), 16) for _ in range(self.r)] 47 | else: 48 | state = random.getstate() 49 | random.seed(encodingSeed) 50 | self.roundPerm = [random.sample(list(range(16)), 16) for _ in range(self.r)] 51 | random.setstate(state) 52 | 53 | self.reverseRoundPerm = [[p.index(i) for i in range(16)] for p in self.roundPerm] 54 | 55 | def applyPermutation(self, data, roundN): 56 | return bytes([data[x] for x in self.roundPerm[roundN]]) 57 | 58 | def applyReversePermutation(self, data, roundN): 59 | return bytes([data[x] for x in self.reverseRoundPerm[roundN]]) 60 | 61 | def encrypt_round_encode(self, state, roundN): 62 | if roundN != 0: 63 | state = self.applyReversePermutation(state, roundN-1) 64 | state = self.encoding[roundN].decode(state) 65 | state = self.encrypt_round(state, roundN) 66 | state = self.encoding[roundN + 1].encode(state) 67 | if roundN + 1 != self.r: 68 | state = self.applyPermutation(state, roundN) 69 | return state 70 | 71 | def encrypt_encode(self, state): 72 | assert len(state) == 16 73 | assert len(self.extendedKey) == self.r + 1 74 | 75 | for i in range(self.r): 76 | state = self.encrypt_round_encode(state, i) 77 | return state 78 | 79 | def encrypt_encode_fast(self, state): 80 | assert len(state) == 16 81 | assert len(self.extendedKey) == self.r + 1 82 | 83 | state = self.encoding[0].decode(state) 84 | 85 | for i in range(self.r): 86 | state = self.encrypt_round(state, i) 87 | 88 | state = self.encoding[self.r].encode(state) 89 | return state 90 | 91 | def decrypt_round_encode(self, state, roundN): 92 | if roundN + 1 != self.r: 93 | state = self.applyReversePermutation(state, roundN) 94 | state = self.encoding[roundN + 1].decode(state) 95 | state = self.decrypt_round(state, roundN) 96 | state = self.encoding[roundN].encode(state) 97 | if roundN != 0: 98 | state = self.applyPermutation(state, roundN-1) 99 | return state 100 | 101 | def decrypt_encode(self, state): 102 | assert len(state) == 16 103 | assert len(self.extendedKey) == self.r + 1 104 | 105 | for i in reversed(list(range(self.r))): 106 | state = self.decrypt_round_encode(state, i) 107 | return state 108 | 109 | def decrypt_encode_fast(self, state): 110 | assert len(state) == 16 111 | assert len(self.extendedKey) == self.r + 1 112 | 113 | state = self.encoding[self.r].decode(state) 114 | 115 | for i in reversed(list(range(self.r))): 116 | state = self.decrypt_round(state, i) 117 | 118 | state = self.encoding[0].encode(state) 119 | return state 120 | 121 | 122 | def test(): 123 | # test case : https://github.com/ircmaxell/quality-checker/blob/master/tmp/gh_18/PHP-PasswordLib-master/test/Data/Vectors/aes-ecb.test-vectors # noqa:E501 124 | test_cases = [ 125 | ("2b7e151628aed2a6abf7158809cf4f3c", 10, 126 | "6bc1bee22e409f96e93d7e117393172a", 127 | "3ad77bb40d7a3660a89ecaf32466ef97"), 128 | ("2b7e151628aed2a6abf7158809cf4f3c", 10, 129 | "ae2d8a571e03ac9c9eb76fac45af8e51", 130 | "f5d3d58503b9699de785895a96fdbaaf"), 131 | ("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", 12, 132 | "6bc1bee22e409f96e93d7e117393172a", 133 | "bd334f1d6e45f25ff712a214571fa5cc"), 134 | ("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", 12, 135 | "ae2d8a571e03ac9c9eb76fac45af8e51", 136 | "974104846d0ad3ad7734ecb3ecee4eef"), 137 | ("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 14, 138 | "6bc1bee22e409f96e93d7e117393172a", 139 | "f3eed1bdb5d2a03c064b5a7e3db181f8"), 140 | ("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 14, 141 | "ae2d8a571e03ac9c9eb76fac45af8e51", 142 | "591ccb10d410ed26dc5ba74a31362870"), 143 | ] 144 | 145 | for key, r, plain, cipher in test_cases: 146 | print(key, r, plain, cipher) 147 | c = AESEncoded(bytes.fromhex(key)) 148 | c.encoding[0].setType(EncodeType.IDENTITY) 149 | c.encoding[-1].setType(EncodeType.IDENTITY) 150 | assert c.encrypt(bytes.fromhex(plain)) == bytes.fromhex(cipher) 151 | assert c.decrypt(bytes.fromhex(cipher)) == bytes.fromhex(plain) 152 | assert c.encrypt_encode(bytes.fromhex(plain)) == bytes.fromhex(cipher) 153 | assert c.decrypt_encode(bytes.fromhex(cipher)) == bytes.fromhex(plain) 154 | 155 | for _ in range(16): 156 | c = AESEncoded(bytes.fromhex(key), byteSwap=True) 157 | data = random.randbytes(16) 158 | assert c.encrypt_encode(c.decrypt_encode(data)) == data 159 | assert c.decrypt_encode(c.encrypt_encode(data)) == data 160 | 161 | assert c.encrypt_encode(data) == c.encrypt_encode_fast(data) 162 | assert c.decrypt_encode(data) == c.decrypt_encode_fast(data) 163 | 164 | for roundN in range(c.r): 165 | assert c.encrypt_round_encode(c.decrypt_round_encode(data, roundN), roundN) == data 166 | assert c.decrypt_round_encode(c.encrypt_round_encode(data, roundN), roundN) == data 167 | 168 | 169 | if __name__ == "__main__": 170 | test() 171 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #include "inputReader.hpp" 15 | #include "affineencoding.hpp" 16 | #include "baseencoding.hpp" 17 | #include "approximateencoding.hpp" 18 | #include "encodingkey.hpp" 19 | #include "computeQPtilde.hpp" 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #define STRINGIFY(x) #x 29 | #define MACRO_STRINGIFY(x) STRINGIFY(x) 30 | 31 | namespace py = pybind11; 32 | using namespace pybind11::literals; 33 | 34 | std::tuple>, 35 | std::map, 16>>, 36 | std::map, 4>, 4>>> 37 | recoverkey(const std::map < uint8_t, std::array < std::array < std::array, LEN_ARRAY + 2 >, 16 >> & data, 38 | bool isEncrypt) { 39 | InputReader inputReader {}; 40 | 41 | inputReader.readData(data); 42 | 43 | std::array isQtildeAvailable {}; 44 | std::array, 4>, AES_ROUND> Qtilde; 45 | 46 | // Compute the Qtilde if possible, that is Qtilde(x) = Q(A(x)), where A is an unknown affine encoding 47 | for (int round = 0; round < AES_ROUND; round++) { 48 | if (inputReader.isRoundAvailable(round)) { 49 | isQtildeAvailable[round] = true; 50 | for (size_t col = 0; col < 4; col++) { 51 | for (size_t row = 0; row < 4; row ++) { 52 | if (not Qtilde[round][col][row].build(inputReader.getx0_all(round, col, row))) { 53 | std::cerr << "Fail to build Qtilde for round " << round + 1 << " byte " << (col * 4 + row) << std::endl; 54 | isQtildeAvailable[round] = false; 55 | } 56 | } 57 | } 58 | } else { 59 | isQtildeAvailable[round] = false; 60 | } 61 | } 62 | std::array < std::array, AES_ROUND - 1 > QPtilde; 63 | 64 | for (int round = 0; round < AES_ROUND - 1; round++) { 65 | if (not(isQtildeAvailable[round] and isQtildeAvailable[round + 1])) { 66 | continue; 67 | } 68 | for (unsigned int col = 0; col < 4; col++) { 69 | std::array, 4> xy; 70 | for (unsigned int x = 0; x < 4; x++) { 71 | unsigned int byteOffset = (isEncrypt) ? (ShiftRow[x][col]) : (InvShiftRow[x][col]); 72 | for (unsigned int y = 0; y < 4; y++) { 73 | xy[x][y] = Qtilde[round + 1][col][y].inverse().compose( 74 | inputReader.getx(x, round + 1, col, y).compose( 75 | Qtilde[round][byteOffset][x])); 76 | } 77 | } 78 | if (not computeQPtilde(xy, QPtilde[round][col], isEncrypt)) { 79 | std::cerr << "Fail to compute Q and Ptilde for round=" << round << ", col=" << col << std::endl; 80 | break; 81 | } 82 | } 83 | } 84 | 85 | // in case of encryption, QPtilde is already determined. But in case of decrypt, 86 | // this isn't the case. We need to perform an additional collision between 87 | // consecutive QPtilde 88 | if (!isEncrypt) { 89 | for (int round = 0; round < AES_ROUND - 2; round++) { 90 | finalizeQPtildeDecrypt(QPtilde[round], QPtilde[round+1]); 91 | } 92 | } 93 | 94 | std::array isQPtildeAvailable {}; 95 | 96 | for (int round = 0; round < AES_ROUND - 1; round++) { 97 | isQPtildeAvailable[round] = 98 | QPtilde[round][0].isFinalize() && 99 | QPtilde[round][1].isFinalize() && 100 | QPtilde[round][2].isFinalize() && 101 | QPtilde[round][3].isFinalize(); 102 | if ((!isQPtildeAvailable[round]) && 103 | QPtilde[round][0].isValid() && 104 | QPtilde[round][1].isValid() && 105 | QPtilde[round][2].isValid() && 106 | QPtilde[round][3].isValid()) { 107 | 108 | std::cerr << "Fail to finalize computation Q and Ptilde for round=" << round << std::endl; 109 | } 110 | } 111 | 112 | 113 | std::array < std::array, 4>, AES_ROUND - 2 > rkey; 114 | std::array keyAvailable {}; 115 | 116 | for (int round = 0; round < AES_ROUND - 2; round++) { 117 | if (not(isQPtildeAvailable[round] and isQPtildeAvailable[round + 1])) { 118 | keyAvailable[round] = false; 119 | continue; 120 | } 121 | keyAvailable[round] = true; 122 | for (unsigned int col = 0; col < 4; col++) { 123 | for (unsigned int row = 0; row < 4; row++) { 124 | unsigned int byteOffset = (isEncrypt) ? (InvShiftRow[row][col]) : (ShiftRow[row][col]); 125 | if (!getAndVerifyKey(QPtilde[round][col].getQ(row), 126 | QPtilde[round + 1][byteOffset].getPtilde(row), 127 | rkey[round][col][row])) { 128 | std::cerr << "Fail to verify xor property for roundkey " 129 | << (round + 2) << " byte " 130 | << (col * 4 + row) << std::endl; 131 | keyAvailable[round] = false; 132 | break; 133 | } 134 | } 135 | } 136 | } 137 | 138 | // fixme return better struct 139 | 140 | std::map> keyMap; 141 | 142 | for (int round = 0; round < AES_ROUND - 2; round++) { 143 | if (not keyAvailable[round]) { 144 | continue; 145 | } 146 | std::array roundKey; 147 | for (unsigned int col = 0; col < 4; col++) { 148 | for (unsigned int row = 0; row < 4; row++) { 149 | roundKey[col * 4 + row] = static_cast(rkey[round][col][row]); 150 | } 151 | } 152 | keyMap[round + 2] = std::move(roundKey); 153 | } 154 | 155 | std::map, 16>> encodingMap; 156 | 157 | for (int round = 1; round < AES_ROUND - 2; round++) { 158 | if (not (isQtildeAvailable[round] and isQPtildeAvailable[round - 1])) { 159 | continue; 160 | } 161 | std::array, 16> roundEncoding; 162 | for (unsigned int col = 0; col < 4; col++) { 163 | for (unsigned int row = 0; row < 4; row++) { 164 | roundEncoding[col * 4 + row] = Qtilde[round][col][row].compose( 165 | QPtilde[round - 1][col].getQ(row)).getEncodingArray(); 166 | } 167 | } 168 | encodingMap[round + 1] = std::move(roundEncoding); 169 | } 170 | std::map , 4>, 4>> coeffMap; 171 | 172 | for (int round = 0; round < AES_ROUND - 1; round++) { 173 | if (isQPtildeAvailable[round]) { 174 | coeffMap[round + 1] = { 175 | QPtilde[round][0].getCoeff(), 176 | QPtilde[round][1].getCoeff(), 177 | QPtilde[round][2].getCoeff(), 178 | QPtilde[round][3].getCoeff() 179 | }; 180 | } 181 | } 182 | 183 | 184 | return std::make_tuple(std::move(keyMap), std::move(encodingMap), std::move(coeffMap)); 185 | } 186 | 187 | PYBIND11_MODULE(_core, m) { 188 | m.doc() = "Blue Galaxy Energy module"; 189 | m.def("recoverkey", &recoverkey, "Core of the BGE key recovery", "data"_a, "isEncrypt"_a); 190 | 191 | #ifdef VERSION_INFO 192 | m.attr("__version__") = MACRO_STRINGIFY(VERSION_INFO); 193 | #else 194 | m.attr("__version__") = "dev"; 195 | #endif 196 | } 197 | -------------------------------------------------------------------------------- /src/matrix.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef MATRIX_HPP 15 | #define MATRIX_HPP 16 | 17 | #include "utils.hpp" 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #define SIZE_MATRIX 8 28 | 29 | // Convert a vector of length 8 to a uint8_t 30 | // for example, with vec = [1 0 0 0 0 0 1 0], the uint8_t is 130 31 | static uint8_t vec_GF2_to_uint8(const NTL::vec_GF2 &vec) { 32 | // std::cout << vec << std::endl; 33 | assert(vec.length() == SIZE_MATRIX); 34 | uint8_t tmp = 0; 35 | for (unsigned int i = 0; i < SIZE_MATRIX; i++) { 36 | tmp = static_cast(tmp << 1); 37 | tmp = tmp ^ static_cast(NTL::rep(vec[i])); 38 | } 39 | 40 | // std::cout << static_cast(tmp) << std::endl; 41 | return tmp; 42 | } 43 | 44 | // Convert a uint8_t a to a polynomial on GF2 of at most degree 7 as 45 | // for example, with a = 0b10000010, the polynomial is equal to x^7 + x 46 | static NTL::GF2X uint8_to_GF2X(const uint8_t &a) { 47 | NTL::GF2X P; 48 | for (unsigned int i = 0; i < 8; i++) { 49 | NTL::SetCoeff(P, static_cast(i), static_cast(a >> i & 1)); 50 | } 51 | 52 | return P; 53 | } 54 | 55 | // Return the polynomial in the form 56 | // x^8 + x^4 + x^3 + x + 1 = 0b100011011 = 283 57 | static unsigned int GF2X_to_uint(const NTL::GF2X &p) { 58 | unsigned int b = 0; 59 | unsigned int d = static_cast(NTL::deg(p)); 60 | for (unsigned int i = 0; i <= d; i++) { 61 | b = b << 1; 62 | b = b ^ static_cast(NTL::conv(p[d - i])); 63 | } 64 | 65 | return b; 66 | } 67 | 68 | // Generate the matrix Lambda_alpha. 69 | // alpha being a polynomial and p0 another polynomial, 70 | // p0 * alpha = p1 mod P (P being the AES polynomial) 71 | // Representing P0 as a vector of the coefficient of P0, and p1 as P1, 72 | // P0 * Lambda_alpha = P1 73 | NTL::mat_GF2 gen_multiplication_matrix(uint8_t alpha) { 74 | // AES polynomial: P(x) = x^8 + x^4 + x^3 + x + 1 75 | NTL::GF2X P; 76 | NTL::SetCoeff(P, 0, 1); 77 | NTL::SetCoeff(P, 1, 1); 78 | NTL::SetCoeff(P, 2, 0); 79 | NTL::SetCoeff(P, 3, 1); 80 | NTL::SetCoeff(P, 4, 1); 81 | NTL::SetCoeff(P, 5, 0); 82 | NTL::SetCoeff(P, 6, 0); 83 | NTL::SetCoeff(P, 7, 0); 84 | NTL::SetCoeff(P, 8, 1); 85 | 86 | NTL::GF2E::init(P); 87 | 88 | NTL::GF2E palphae = NTL::conv(uint8_to_GF2X(alpha)); 89 | 90 | NTL::vec_vec_GF2 vec; 91 | vec.SetLength(SIZE_MATRIX); 92 | 93 | for (unsigned int i = 0; i < 8; i++) { 94 | vec[i].SetLength(SIZE_MATRIX); 95 | NTL::GF2X p; 96 | NTL::SetCoeff(p, 7 - i, 1); 97 | p = NTL::conv(palphae * NTL::conv(p)); 98 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) 99 | vec[i][c] = NTL::coeff(p, SIZE_MATRIX - 1 - c); 100 | } 101 | 102 | return NTL::to_mat_GF2(vec); 103 | } 104 | 105 | // Recursive way to compute the determinant of matrices in NTL::GF2X 106 | NTL::GF2X determinant(NTL::Mat M) { 107 | if (M.NumRows() == 2) 108 | return M[0][0] * M[1][1] + M[1][0] * M[0][1]; 109 | 110 | NTL::GF2X p; 111 | 112 | // The submatrix on which the recursive call will be done 113 | NTL::Mat m; 114 | m.SetDims(M.NumRows() - 1, M.NumCols() - 1); 115 | 116 | // Construct the submatrix m by first looking for a non-zero value then building the submatrix without the row and 117 | // column where this value stays 118 | for (unsigned int c = 0; c < M.NumCols(); c++) { 119 | if (M[0][c] == 0) 120 | continue; 121 | for (unsigned int r = 1; r < M.NumRows(); r++) { 122 | unsigned int C = 0; 123 | for (unsigned int col = 0; col < M.NumCols(); col++) { 124 | if (col != c) { 125 | m[r - 1][C] = M[r][col]; 126 | C = C + 1; 127 | } 128 | } 129 | } 130 | p = p + M[0][c] * determinant(m); 131 | } 132 | 133 | return p; 134 | } 135 | 136 | // TODO: we can remove the dependency to NTL::GF2X by using our own gf2x 137 | // implementation on uint16_t since we know that we will not exceed the degree 8 138 | // and do not need to have efficient multiplication, only shift are sufficient. 139 | // Return the polynomial in the form 140 | // x^8 + x^4 + x^3 + x + 1 = 0b100011011 = 283 141 | unsigned int characteristic_polynomial(NTL::mat_GF2 M) { 142 | NTL::Mat m; 143 | m.SetDims(M.NumRows(), M.NumCols()); 144 | NTL::GF2X p; 145 | NTL::SetCoeff(p, 1, 1); 146 | for (unsigned int r = 0; r < M.NumRows(); r++) 147 | m[r][r] = p; 148 | for (unsigned int r = 0; r < M.NumRows(); r++) 149 | for (unsigned int c = 0; c < M.NumCols(); c++) { 150 | m[r][c] = m[r][c] + M[r][c]; 151 | } 152 | 153 | p = determinant(m); 154 | 155 | return GF2X_to_uint(p); 156 | } 157 | 158 | /* 159 | * This class will be a bit unusual. Indeed, our matrix is of size 8*8 on GF(2). 160 | * The matrix uses a row-wise representation of the basis vectors, that is we 161 | * multiply by a vector on the left of the matrix. 162 | * 163 | * The unusual stuff here is that we will define the matrix row by row, but what 164 | * we need is in fact the columns of the matrix, not its rows. So, we will 165 | * construct the matrix thanks to its rows, but store the matrix by columns. 166 | * 167 | * Exemple (on a matrix 2 * 2). 168 | * M = matrix([a0, a1], [b0, b1]) -> M = [a0 a1] 169 | * [b0 b1] 170 | * 171 | * To store M, we will store [[a0, b0], [a1, b1]] 172 | * Now, if we want to do v * M, the output will be [v * M[0], v * M[1]] 173 | * 174 | * Since we work with 8-bit vectors, the vectors are represented on an 175 | * uint8_t. To perform the operation v * M[0], we need to do 176 | * __builtin_parity(v & M[0]) 177 | */ 178 | 179 | class Matrix { 180 | private: 181 | std::array col; 182 | 183 | public: 184 | Matrix() {}; 185 | 186 | Matrix(const std::array row) { build(row); }; 187 | 188 | Matrix(const NTL::mat_GF2 &m) { from_mat_GF2(m); }; 189 | 190 | Matrix(uint8_t alpha) { generate_multiplication_matrix(alpha); }; 191 | 192 | void build(const std::array row) { 193 | uint8_t tmp; 194 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) { 195 | uint8_t col_tmp = 0; 196 | for (unsigned int i = 0; i < SIZE_MATRIX; i++) { 197 | tmp = (row[i] >> (SIZE_MATRIX - 1 - c)) & 1; 198 | col_tmp = 199 | col_tmp ^ (static_cast(tmp << (SIZE_MATRIX - 1 - i))); 200 | } 201 | col[c] = col_tmp; 202 | } 203 | } 204 | 205 | uint8_t getCol(unsigned int i) const { return col[i]; } 206 | 207 | bool get(unsigned int r, unsigned int c) const { 208 | return static_cast((getCol(c) >> (SIZE_MATRIX - 1 - r)) & 1); 209 | } 210 | 211 | uint8_t multiply(uint8_t v) { 212 | uint8_t tmp; 213 | uint8_t result = 0; 214 | for (unsigned int i = 0; i < SIZE_MATRIX; i++) { 215 | tmp = static_cast( 216 | __builtin_parity(static_cast(v & col[i]))); 217 | result = result ^ (static_cast(tmp << (SIZE_MATRIX - 1 - i))); 218 | } 219 | 220 | return result; 221 | } 222 | 223 | // Brute-force way to determine if an GF(2) 8×8 matrix is full rank, i.e., 224 | // test all the possible vectors v in GF(2)^8: if the result of v * A was already computed by a different vector, A is not full rank 225 | bool is_full_rank() { 226 | std::set set; 227 | uint8_t res; 228 | 229 | for (unsigned int x = 0; x < LEN_ARRAY; x++) { 230 | res = multiply(static_cast(x)); 231 | if (!set.insert(res).second) 232 | return false; 233 | } 234 | 235 | return true; 236 | } 237 | 238 | // Transform a Matrix to a NTL::mat_GF2 239 | NTL::mat_GF2 to_mat_GF2() const { 240 | NTL::vec_vec_GF2 vec; 241 | vec.SetLength(SIZE_MATRIX); 242 | for (unsigned int r = 0; r < 8; r++) { 243 | vec[r].SetLength(SIZE_MATRIX); 244 | for (unsigned int c = 0; c < 8; c++) 245 | vec[r][c] = static_cast(get(r, c)); 246 | } 247 | 248 | return NTL::to_mat_GF2(vec); 249 | } 250 | 251 | // Transform a NTL::mat_GF2 to a Matrix 252 | void from_mat_GF2(const NTL::mat_GF2 &m) { 253 | std::array row; 254 | for (unsigned int i = 0; i < SIZE_MATRIX; i++) 255 | row[i] = vec_GF2_to_uint8(m[i]); 256 | 257 | build(row); 258 | } 259 | 260 | // See the comment of gen_multiplication_matrix 261 | void generate_multiplication_matrix(uint8_t alpha) { 262 | from_mat_GF2(gen_multiplication_matrix(alpha)); 263 | } 264 | 265 | // Evaluate i * Matrix + offset, where i and offset represent bit vectors 266 | std::array tabulate(uint8_t offset = 0) { 267 | std::array res; 268 | for (unsigned int i = 0; i < LEN_ARRAY; i++) 269 | res[i] = multiply(static_cast(i)) ^ offset; 270 | 271 | return res; 272 | } 273 | }; 274 | 275 | // Print a Matrix 276 | MAYBE_UNUSED static std::ostream &operator<<(std::ostream &os, 277 | const Matrix &M) { 278 | for (unsigned int r = 0; r < SIZE_MATRIX - 1; r++) { 279 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) 280 | os << static_cast(M.get(r, c)); 281 | os << std::endl; 282 | } 283 | 284 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) 285 | os << static_cast(M.get(SIZE_MATRIX - 1, c)); 286 | 287 | return os; 288 | } 289 | 290 | #endif /* MATRIX_HPP */ 291 | 292 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/BGE.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from .AES import revertKey, MC 17 | from .BGEGenInput import genBGEInput 18 | from .ByteOrder import isValidRound 19 | from .Encoding import Encoding8, Encoding, EncodingXor 20 | from .Exceptions import SaveLoadError 21 | from .FinalizeUnshuffled import FinalizeUnshuffled 22 | from .WhiteBoxedUnshuffledProxy import WhiteBoxedUnshuffledProxy 23 | from enum import Enum 24 | import json 25 | 26 | try: 27 | from ._core import recoverkey 28 | except ImportError: 29 | print("[!] fail to import native library to resolve equation") 30 | recoverkey = None 31 | 32 | 33 | class BGEState(Enum): 34 | NoInput = 1 35 | InputComputed = 2 36 | ResolveDone = 3 37 | 38 | 39 | class BGE: 40 | 41 | EXPORT_VERSION = 1 42 | EXPORT_TYPE = "BGEInputs" 43 | 44 | def __init__(self, wb=None, isEncrypt=None, numRound=None): 45 | self.wb = wb 46 | self.state = BGEState.NoInput 47 | if self.wb is not None: 48 | assert isEncrypt is None or isEncrypt == self.wb.isEncrypt() 49 | assert numRound is None or numRound == self.wb.getRoundNumber() 50 | self.isEncrypt = self.wb.isEncrypt() 51 | self.numRound = self.wb.getRoundNumber() 52 | else: 53 | self.isEncrypt = isEncrypt 54 | self.numRound = numRound 55 | assert type(self.isEncrypt) is bool 56 | assert type(self.numRound) is int 57 | assert self.numRound in [10, 12, 14] 58 | 59 | self.roundList = None 60 | self.paramShuffle = None 61 | 62 | @classmethod 63 | def loadFrom(cls, filename): 64 | 65 | # when parsing dict from json, try to restore the type of the key if the 66 | # key is an integer-like string 67 | def hook(d): 68 | return { 69 | int(k) if k.lstrip('-').isdigit() else k: v 70 | for k, v in d.items() 71 | } 72 | 73 | with open(filename, 'r') as f: 74 | data = json.loads(f.read(), object_hook=hook) 75 | 76 | if (type(data) is not dict or 77 | data.get("DataType", None) != cls.EXPORT_TYPE or 78 | data.get("Version", None) != cls.EXPORT_VERSION): 79 | 80 | raise SaveLoadError("Unexpected file structure") 81 | 82 | attack = cls(isEncrypt=data.get("isEncrypt", None), 83 | numRound=data.get("numRound", None)) 84 | 85 | attack.roundList = data["roundList"] 86 | attack.shuffle = data["shuffle"] 87 | attack.BGEInputs = data["BGEInputs"] 88 | attack.shuffle = data["shuffle"] 89 | attack.paramShuffle = data["paramShuffle"] 90 | attack.state = BGEState.InputComputed 91 | 92 | return attack 93 | 94 | def saveTo(self, filename, compress=True): 95 | if self.state not in [BGEState.InputComputed, BGEState.ResolveDone]: 96 | raise SaveLoadError("Cannot save without data") 97 | 98 | data = {} 99 | data["DataType"] = self.EXPORT_TYPE 100 | data["Version"] = self.EXPORT_VERSION 101 | data["isEncrypt"] = self.isEncrypt 102 | data["numRound"] = self.numRound 103 | data["roundList"] = self.roundList 104 | data["BGEInputs"] = self.BGEInputs 105 | data["shuffle"] = self.shuffle 106 | data["paramShuffle"] = self.paramShuffle 107 | 108 | with open(filename, 'w') as f: 109 | f.write(json.dumps(data)) 110 | 111 | def run(self, roundList=None, shuffle=False, nmultiProcess=None): 112 | self.generateInput(roundList=roundList, shuffle=shuffle, 113 | nmultiProcess=nmultiProcess) 114 | self.resolve() 115 | 116 | def generateInput(self, roundList=None, shuffle=False, nmultiProcess=None): 117 | if self.state != BGEState.NoInput: 118 | return 119 | 120 | assert self.wb is not None, "Cannot prepare input without the whitebox implementation" 121 | self.shuffle = shuffle 122 | 123 | if roundList is None: 124 | roundList = [] 125 | for roundN in list(range(self.numRound-1)): 126 | if isValidRound(self.wb, roundN): 127 | roundList.append(roundN) 128 | else: 129 | print(f"Disable round {roundN} : not a valid round for the attack") 130 | 131 | self.roundList = sorted(list(set(roundList))) 132 | 133 | # if roundList is given, the list must be consecutive rounds 134 | assert self.roundList == list(range(self.roundList[0], 135 | self.roundList[0] + 136 | len(self.roundList))), \ 137 | "RoundList is not made of consecutive rounds" 138 | assert all([x >= 0 for x in self.roundList]), "Invalid round number" 139 | assert all([x < self.numRound-1 for x in self.roundList]), "Invalid round number" 140 | 141 | if self.shuffle: 142 | proxyWb = WhiteBoxedUnshuffledProxy(self.wb, self.roundList) 143 | proxyWb.computeSimplePermutation() 144 | self.BGEInputs = genBGEInput(proxyWb, self.roundList, nmultiProcess) 145 | self.paramShuffle = proxyWb.exctractPermutation() 146 | else: 147 | self.BGEInputs = genBGEInput(self.wb, self.roundList, nmultiProcess) 148 | self.paramShuffle = [None, None, 149 | [list(range(16)) for _ in range(self.numRound)]] 150 | 151 | self.state = BGEState.InputComputed 152 | 153 | def resolve(self): 154 | if self.state == BGEState.ResolveDone: 155 | return 156 | self.shuffleProxy = FinalizeUnshuffled(self.isEncrypt, *self.paramShuffle) 157 | assert self.state != BGEState.NoInput, "Cannot resolve before generating inputs" 158 | assert recoverkey is not None, "Missing resolve library" 159 | 160 | self.roundkey = [None for _ in range(self.numRound+1)] 161 | self.encoding = [[None for _ in range(16)] for _ in range(self.numRound+1)] 162 | 163 | keyResult, encodingResult, columnCoeff = recoverkey(self.BGEInputs, self.isEncrypt) 164 | 165 | for roundN, key in keyResult.items(): 166 | self.roundkey[roundN] = bytes(key) 167 | for roundN, perms in encodingResult.items(): 168 | for byte, perm in enumerate(perms): 169 | self.encoding[roundN][byte] = Encoding8(perm) 170 | 171 | for roundN in range(len(self.encoding)): 172 | if all([x is not None for x in self.encoding[roundN]]): 173 | self.encoding[roundN] = Encoding(self.encoding[roundN]) 174 | else: 175 | self.encoding[roundN] = None 176 | 177 | if self.shuffle: 178 | self._reverseWbProxy() 179 | self.shuffleProxy.reorderWithCoeff(columnCoeff) 180 | self.shuffleProxy.setFirstByte(0) 181 | self._applyWbProxy() 182 | 183 | self.state = BGEState.ResolveDone 184 | 185 | def _reverseWbProxy(self): 186 | if not self.shuffle: 187 | return 188 | for roundN, key in enumerate(self.roundkey): 189 | if key is not None: 190 | self.roundkey[roundN] = self.shuffleProxy.applyReversePermutation(key, roundN-1) 191 | 192 | for roundN, perm in enumerate(self.encoding): 193 | if perm is not None: 194 | self.encoding[roundN] = Encoding(self.shuffleProxy.applyReversePermutation(perm.encodingList, roundN-1, False)) 195 | 196 | def _applyWbProxy(self): 197 | if not self.shuffle: 198 | return 199 | 200 | for roundN, key in enumerate(self.roundkey): 201 | if key is not None: 202 | self.roundkey[roundN] = self.shuffleProxy.applyPermutation(key, roundN-1) 203 | 204 | for roundN, perm in enumerate(self.encoding): 205 | if perm is not None: 206 | self.encoding[roundN] = Encoding(self.shuffleProxy.applyPermutation(perm.encodingList, roundN-1, False)) 207 | 208 | def _changeWbProxyFirstByte(self, n): 209 | if not self.shuffle: 210 | return 211 | 212 | self._reverseWbProxy() 213 | self.shuffleProxy.setFirstByte(n) 214 | self._applyWbProxy() 215 | 216 | def getEncoding(self): 217 | assert self.state == BGEState.ResolveDone 218 | if self.isEncrypt: 219 | return list(self.encoding) 220 | else: 221 | res = [] 222 | rkey = list(reversed(self.roundkey)) 223 | for ident, enc in enumerate(reversed(self.encoding)): 224 | if enc is not None and rkey[ident] is not None: 225 | res.append(enc.combine(EncodingXor(rkey[ident]))) 226 | else: 227 | res.append(None) 228 | return res 229 | 230 | def getShuffle(self): 231 | assert self.state == BGEState.ResolveDone 232 | res = {} 233 | for r in [self.roundList[0]] + self.roundList: 234 | if self.isEncrypt: 235 | res[r] = self.shuffleProxy.reverseRoundPerm[r] 236 | else: 237 | res[self.numRound-(2+r)] = self.shuffleProxy.reverseRoundPerm[r] 238 | return res 239 | 240 | def computeKey(self, keyLen=None, offset=0): 241 | assert self.state == BGEState.ResolveDone 242 | if not self.shuffle: 243 | return self._computeKey(keyLen=keyLen, offset=offset) 244 | else: 245 | possibleKey = {} 246 | for i in range(16): 247 | self._changeWbProxyFirstByte(i) 248 | key = self._computeKey(keyLen=keyLen, offset=offset) 249 | if key is not None: 250 | possibleKey[i] = key 251 | if len(possibleKey.keys()) == 0: 252 | return None 253 | targetByte = list(possibleKey.keys())[0] 254 | self._changeWbProxyFirstByte(targetByte) 255 | if len(possibleKey.keys()) == 1: 256 | return possibleKey[targetByte] 257 | return possibleKey 258 | 259 | def _computeKey(self, keyLen=None, offset=0): 260 | assert self.state == BGEState.ResolveDone 261 | assert self.numRound is not None 262 | if keyLen is None: 263 | keyLen = {10: 16, 12: 24, 14: 32}[self.numRound] 264 | 265 | if self.isEncrypt: 266 | localRoundKey = list(self.roundkey) 267 | else: 268 | localRoundKey = list(reversed(self.roundkey)) 269 | 270 | possibleKey = None 271 | for i in range(len(localRoundKey)): 272 | j = i 273 | k = b'' 274 | while len(k) < keyLen and j < len(localRoundKey) and localRoundKey[j] is not None: 275 | if self.isEncrypt: 276 | k += localRoundKey[j] 277 | else: 278 | k += MC(localRoundKey[j]) 279 | j += 1 280 | if len(k) < keyLen: 281 | continue 282 | tempk = revertKey(k, i + offset) 283 | if possibleKey is None: 284 | possibleKey = tempk 285 | # all roundkeys are not part of the same AES keyscheduling 286 | elif tempk != possibleKey: 287 | return None 288 | return possibleKey 289 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2020 - 2022 L. Grémy, N. Surbayrole 190 | Copyright 2020 - 2022 Quarkslab 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /src/computeQPtilde.hpp: -------------------------------------------------------------------------------- 1 | //----------------------------------------------------------------------------- 2 | // Copyright (C) Quarkslab. See README.md for details. 3 | // 4 | // This program is free software: you can redistribute it and/or modify 5 | // it under the terms of the Apache License as published by 6 | // the Apache Software Foundation, either version 2.0 of the License. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 11 | // See LICENSE.txt for the text of the Apache license. 12 | //----------------------------------------------------------------------------- 13 | 14 | #ifndef COMPUTEQPTILDE_HPP 15 | #define COMPUTEQPTILDE_HPP 16 | 17 | #include "affineencoding.hpp" 18 | #include "baseencoding.hpp" 19 | #include "encodingkey.hpp" 20 | #include "matrix.hpp" 21 | #include "precompute.hpp" 22 | #include "relationencoding.hpp" 23 | #include 24 | #include 25 | #include 26 | 27 | static unsigned int get_beta_req(const BaseEncoding &y0x0, const BaseEncoding &y1x0, 28 | const BaseEncoding &y0x1, const BaseEncoding &y1x1, 29 | NTL::mat_GF2& L) { 30 | 31 | // TODO: l0 and l1 can be precomputed or can be reused after, depending on the 32 | // flow of the algorithm 33 | RelationEncoding l0(y0x0, y1x0); 34 | RelationEncoding l1(y0x1, y1x1); 35 | 36 | NTL::mat_GF2 L0 = l0.getA().to_mat_GF2(); 37 | NTL::mat_GF2 L1 = l1.getA().to_mat_GF2(); 38 | L.SetDims(SIZE_MATRIX, SIZE_MATRIX); 39 | if (NTL::IsZero(NTL::determinant(L1))) return 0; 40 | NTL::inv(L1, L1); 41 | mul(L, L1, L0); 42 | 43 | return characteristic_polynomial(L); 44 | } 45 | 46 | static BaseEncoding compute_Atilde(const NTL::mat_GF2& L, uint8_t beta) { 47 | NTL::mat_GF2 Lbeta = gen_multiplication_matrix(beta); 48 | 49 | unsigned int length = SIZE_MATRIX * SIZE_MATRIX; 50 | NTL::mat_GF2 S; 51 | S.SetDims(length, length); 52 | for (unsigned int s = 0; s < SIZE_MATRIX; s++) 53 | for (unsigned int r = 0; r < SIZE_MATRIX; r++) 54 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) { 55 | S[r + s * SIZE_MATRIX][c + s * SIZE_MATRIX] = 56 | S[r + s * SIZE_MATRIX][c + s * SIZE_MATRIX] + L[r][c]; 57 | S[r + s * SIZE_MATRIX][r + c * SIZE_MATRIX] = 58 | S[r + s * SIZE_MATRIX][r + c * SIZE_MATRIX] + Lbeta[c][s]; 59 | } 60 | 61 | NTL::mat_GF2 ker; 62 | NTL::kernel(ker, S); 63 | 64 | NTL::mat_GF2 Atilde; 65 | Atilde.SetDims(SIZE_MATRIX, SIZE_MATRIX); 66 | for (unsigned int r = 0; r < SIZE_MATRIX; r++) 67 | for (unsigned int c = 0; c < SIZE_MATRIX; c++) 68 | Atilde[r][c] = ker[0][r * SIZE_MATRIX + c]; 69 | 70 | return Matrix(Atilde).tabulate(); 71 | } 72 | 73 | static bool get_betas(const std::array& req, 74 | std::array& res, 75 | std::array, 2>, 4>& coeff, 76 | bool& isUnsure) { 77 | std::array sortedReq = req; 78 | std::sort(sortedReq.begin(), sortedReq.end()); 79 | 80 | auto it = std::find_if(PrecomputedBeta.cbegin(), PrecomputedBeta.cend(), 81 | [&sortedReq](const precomputedBetaStruct& el) { 82 | return el.beta_req == sortedReq; 83 | }); 84 | 85 | if (it == PrecomputedBeta.cend()) { 86 | std::cerr << "get_betas error: " << req[0] 87 | << ", " << req[1] 88 | << ", " << req[2] 89 | << ", " << req[3] << std::endl; 90 | return false; 91 | } 92 | isUnsure = it->UnclearRequest; 93 | 94 | std::array used = {false, false, false, false}; 95 | for (int reqIndex = 0; reqIndex < 4; reqIndex++) { 96 | bool found = false; 97 | for (int precomputedIndex = 0; precomputedIndex < 4; precomputedIndex++) { 98 | if ((!used[precomputedIndex]) && it->beta_req[precomputedIndex] == req[reqIndex]) { 99 | res[reqIndex] = it->beta[precomputedIndex]; 100 | coeff[reqIndex] = it->coeff[precomputedIndex]; 101 | used[precomputedIndex] = true; 102 | found = true; 103 | break; 104 | } 105 | } 106 | if (!found) { 107 | std::cerr << "get_betas cannot get beta for index " << reqIndex 108 | << " in " << req[0] 109 | << ", " << req[1] 110 | << ", " << req[2] 111 | << ", " << req[3] << std::endl; 112 | return false; 113 | } 114 | } 115 | 116 | return true; 117 | } 118 | 119 | static bool checkPreCoeff(const std::array, 2>, 4>& Coeff) { 120 | if (Coeff[0][0] != Coeff[1][1] || 121 | Coeff[0][1] != Coeff[1][0] || 122 | Coeff[2][0] != Coeff[3][1] || 123 | Coeff[2][1] != Coeff[3][0]) { 124 | 125 | std::cerr << "coeff permutation error = [[[" 126 | << (unsigned)Coeff[0][0][0] << ", " << (unsigned)Coeff[0][0][1] << "], [" 127 | << (unsigned)Coeff[0][1][0] << ", " << (unsigned)Coeff[0][1][1] << "]], [[" 128 | << (unsigned)Coeff[1][0][0] << ", " << (unsigned)Coeff[1][0][1] << "], [" 129 | << (unsigned)Coeff[1][1][0] << ", " << (unsigned)Coeff[1][1][1] << "]], [[" 130 | << (unsigned)Coeff[2][0][0] << ", " << (unsigned)Coeff[2][0][1] << "], [" 131 | << (unsigned)Coeff[2][1][0] << ", " << (unsigned)Coeff[2][1][1] << "]], [[" 132 | << (unsigned)Coeff[3][0][0] << ", " << (unsigned)Coeff[3][0][1] << "], [" 133 | << (unsigned)Coeff[3][1][0] << ", " << (unsigned)Coeff[3][1][1] << "]]]" << std::endl; 134 | return false; 135 | } 136 | return true; 137 | } 138 | 139 | static bool checkCoeff(const std::array, 4>& Coeff, bool isEncrypt) { 140 | static const std::array targetEnc = {1, 1, 2, 3}; 141 | static const std::array targetDec = {9, 11, 13, 14}; 142 | const std::array& target = (isEncrypt)? targetEnc : targetDec; 143 | 144 | bool res = true; 145 | 146 | for (uint8_t col = 0; col < 4; col++) { 147 | std::array c = Coeff[col]; 148 | std::sort(c.begin(), c.end()); 149 | res &= (c == target); 150 | } 151 | for (uint8_t row = 0; row < 4; row++) { 152 | std::array c = {Coeff[0][row], Coeff[1][row], Coeff[2][row], Coeff[3][row]}; 153 | std::sort(c.begin(), c.end()); 154 | res &= (c == target); 155 | } 156 | 157 | if (!res) { 158 | std::cerr << "Coefficient doesn't match expected MixColumns value : [[" 159 | << (unsigned)Coeff[0][0] << ", " << (unsigned)Coeff[0][1] << ", " 160 | << (unsigned)Coeff[0][2] << ", " << (unsigned)Coeff[0][3] << "], [" 161 | << (unsigned)Coeff[1][0] << ", " << (unsigned)Coeff[1][1] << ", " 162 | << (unsigned)Coeff[1][2] << ", " << (unsigned)Coeff[1][3] << "], [" 163 | << (unsigned)Coeff[2][0] << ", " << (unsigned)Coeff[2][1] << ", " 164 | << (unsigned)Coeff[2][2] << ", " << (unsigned)Coeff[2][3] << "], [" 165 | << (unsigned)Coeff[3][0] << ", " << (unsigned)Coeff[3][1] << ", " 166 | << (unsigned)Coeff[3][2] << ", " << (unsigned)Coeff[3][3] << "]]" << std::endl; 167 | } 168 | 169 | return res; 170 | } 171 | 172 | static bool commonCoeff(const std::array& a, const std::array& b, uint8_t& res, bool tryRun=false) { 173 | for (unsigned idA = 0; idA < 2; idA++) { 174 | for (unsigned idB = 0; idB < 2; idB++) { 175 | // note : special case if a[idA ^ 1] == b[idB ^ 1] == 1 176 | // As the coefficient 1 is used twice in AES encrypt MixColumns, 177 | // we can assume that we want the other coefficient if we found two pairs 178 | if (a[idA] == b[idB] && (a[idA ^ 1] != b[idB ^ 1] || a[idA ^ 1] == 1)) { 179 | res = a[idA]; 180 | return true; 181 | } 182 | } 183 | } 184 | if (!tryRun) { 185 | std::cerr << "Fail commonCoeff with : [" 186 | << (unsigned)a[0] << ", " << (unsigned)a[1] << "], [" 187 | << (unsigned)b[0] << ", " << (unsigned)b[1] << "]" << std::endl; 188 | } 189 | return false; 190 | } 191 | 192 | static bool notCoeff(uint8_t a, const std::array& b, uint8_t& res) { 193 | if (a == b[0]) { 194 | res = b[1]; 195 | return true; 196 | } else if (a == b[1]) { 197 | res = b[0]; 198 | return true; 199 | } else { 200 | std::cerr << "fail notCoeff " 201 | << (unsigned)a << " in (" 202 | << (unsigned)b[0] << ", " 203 | << (unsigned)b[1] << ")" << std::endl; 204 | return false; 205 | } 206 | } 207 | 208 | static bool resolveCoeff( 209 | const std::array, 2>, 4>, 4>& preCoeff, 210 | std::array, 4>& coeff, 211 | bool isEncrypt, 212 | bool tryRun) { 213 | 214 | if (!checkPreCoeff(preCoeff[0])) return false; 215 | if (!checkPreCoeff(preCoeff[1])) return false; 216 | if (!checkPreCoeff(preCoeff[2])) return false; 217 | if (!checkPreCoeff(preCoeff[3])) return false; 218 | 219 | for (unsigned p0 = 0; p0 < 2; p0++) { 220 | for (unsigned p1 = 0; p1 < 4; p1+=2) { 221 | if (!commonCoeff(preCoeff[0][p1][p0], preCoeff[1][p1][p0], coeff[p0][p1], tryRun)) { 222 | return false; 223 | } 224 | if (!notCoeff(coeff[p0][p1], preCoeff[0][p1][p0], coeff[p0^1][p1^1])) { 225 | return false; 226 | } 227 | if (!commonCoeff(preCoeff[2][p1][p0], preCoeff[3][p1][p0], coeff[p0^2][p1], tryRun)) { 228 | return false; 229 | } 230 | if (!notCoeff(coeff[p0^2][p1], preCoeff[2][p1][p0], coeff[p0^3][p1^1])) { 231 | return false; 232 | } 233 | } 234 | } 235 | 236 | return checkCoeff(coeff, isEncrypt); 237 | } 238 | 239 | static void switchCoeff( 240 | std::array& isUnsure, 241 | const std::array, 4>& BetaReq, 242 | std::array, 4>& Beta, 243 | std::array, 2>, 4>, 4>& preCoeff, 244 | unsigned v) { 245 | 246 | for (unsigned reqNum = 0; reqNum < 4; reqNum++) { 247 | if (!isUnsure[reqNum]) { 248 | continue; 249 | } 250 | bool perform = v & 1; 251 | v = v >> 1; 252 | if (!perform) { 253 | continue; 254 | } 255 | 256 | auto it = std::find_if(BetaReq[reqNum].cbegin(), BetaReq[reqNum].cend(), 257 | [](unsigned x) { 258 | return x == 471; 259 | }); 260 | if (it == BetaReq[reqNum].cend()) { 261 | std::cerr << "switchCoeff fail to find first position of coeff 471" << std::endl; 262 | exit(-1); 263 | } 264 | unsigned pos1 = std::distance(BetaReq[reqNum].cbegin(), it); 265 | 266 | it = std::find_if(BetaReq[reqNum].cbegin() + pos1 + 1, BetaReq[reqNum].cend(), 267 | [](unsigned x) { 268 | return x == 471; 269 | }); 270 | if (it == BetaReq[reqNum].cend()) { 271 | std::cerr << "switchCoeff fail to find second position of coeff 471" << std::endl; 272 | exit(-1); 273 | } 274 | unsigned pos2 = std::distance(BetaReq[reqNum].cbegin(), it); 275 | 276 | Beta[reqNum][pos1] = std::exchange(Beta[reqNum][pos2], Beta[reqNum][pos1]); 277 | preCoeff[reqNum][pos1] = std::exchange(preCoeff[reqNum][pos2], preCoeff[reqNum][pos1]); 278 | } 279 | if (v != 0) { 280 | std::cerr << "switchCoeff internal error" << std::endl; 281 | exit(-1); 282 | } 283 | } 284 | 285 | static bool computeCoeffXYAtilde( 286 | const std::array, 4> &xy, 287 | std::array& Atilde, 288 | std::array, 4>& coeff, 289 | bool isEncrypt) { 290 | 291 | std::array, 4> BetaReq; 292 | std::array L; 293 | NTL::mat_GF2 L_dummy; 294 | for (unsigned int row = 0; row < 4; row+=2) { 295 | BetaReq[0][row + 0] = get_beta_req(xy[0][row], xy[0][row ^ 1], xy[1][row], xy[1][row ^ 1], L[row + 0]); 296 | BetaReq[0][row + 1] = get_beta_req(xy[0][row ^ 1], xy[0][row], xy[1][row ^ 1], xy[1][row], L[row + 1]); 297 | 298 | BetaReq[1][row + 0] = get_beta_req(xy[0][row], xy[0][row ^ 3], xy[1][row], xy[1][row ^ 3], L_dummy); 299 | BetaReq[1][row + 1] = get_beta_req(xy[0][row ^ 3], xy[0][row], xy[1][row ^ 3], xy[1][row], L_dummy); 300 | 301 | BetaReq[2][row + 0] = get_beta_req(xy[2][row], xy[2][row ^ 1], xy[3][row], xy[3][row ^ 1], L_dummy); 302 | BetaReq[2][row + 1] = get_beta_req(xy[2][row ^ 1], xy[2][row], xy[3][row ^ 1], xy[3][row], L_dummy); 303 | 304 | BetaReq[3][row + 0] = get_beta_req(xy[2][row], xy[2][row ^ 3], xy[3][row], xy[3][row ^ 3], L_dummy); 305 | BetaReq[3][row + 1] = get_beta_req(xy[2][row ^ 3], xy[2][row], xy[3][row ^ 3], xy[3][row], L_dummy); 306 | } 307 | 308 | std::array, 2>, 4>, 4> preCoeff; 309 | 310 | std::array isUnsure; 311 | std::array, 4> Beta; 312 | 313 | if (!get_betas(BetaReq[0], Beta[0], preCoeff[0], isUnsure[0])) return false; 314 | if (!get_betas(BetaReq[1], Beta[1], preCoeff[1], isUnsure[1])) return false; 315 | if (!get_betas(BetaReq[2], Beta[2], preCoeff[2], isUnsure[2])) return false; 316 | if (!get_betas(BetaReq[3], Beta[3], preCoeff[3], isUnsure[3])) return false; 317 | 318 | uint8_t numPossibility = 319 | ((isUnsure[0])? 2 :1) * 320 | ((isUnsure[1])? 2 :1) * 321 | ((isUnsure[2])? 2 :1) * 322 | ((isUnsure[3])? 2 :1); 323 | 324 | if (numPossibility != 1) { 325 | for (unsigned v = 0; v < numPossibility; v++) { 326 | switchCoeff(isUnsure, BetaReq, Beta, preCoeff, v); 327 | 328 | if (resolveCoeff(preCoeff, coeff, isEncrypt, true)) { 329 | break; 330 | } 331 | switchCoeff(isUnsure, BetaReq, Beta, preCoeff, v); 332 | } 333 | } 334 | 335 | if (!resolveCoeff(preCoeff, coeff, isEncrypt, false)) { 336 | return false; 337 | } 338 | 339 | for (unsigned int row = 0; row < 4; row++) { 340 | Atilde[row] = compute_Atilde(L[row], Beta[0][row]); 341 | } 342 | return true; 343 | } 344 | 345 | static bool compute_affine(const BaseEncoding &xy, 346 | const BaseEncoding &commonF, 347 | uint8_t k, 348 | EncodingKey &Ptilde, 349 | bool initPtilde) { 350 | 351 | std::array v = {}; 352 | for (unsigned int x = 0; x < LEN_ARRAY; x++) { 353 | v[x] = commonF[xy[x] ^ k]; 354 | 355 | if (!initPtilde && v[x] != Ptilde[x]) { 356 | return false; 357 | } 358 | 359 | // verify immediately if the new value verify the affine property 360 | // note: if y < x but x^y < x, we cannot check the equation now 361 | // but it will be checked later when X = x^y 362 | for (unsigned int y = 1; y < x; y++) { 363 | if ((x ^ y) < x and v[x ^ y] != (v[x] ^ v[y] ^ v[0])) { 364 | return false; 365 | } 366 | } 367 | // should never happen 368 | // discard constant 369 | if (x != 0 and v[0] == v[x]) { 370 | return false; 371 | } 372 | } 373 | if (initPtilde) { 374 | Ptilde = EncodingKey(v); 375 | } 376 | return true; 377 | } 378 | 379 | static bool computec_(const BaseEncoding &xy, 380 | const BaseEncoding Ã_inv, 381 | uint8_t &c, uint8_t delta, 382 | EncodingKey &Ptilde, 383 | bool isEncrypt, 384 | bool initPtilde) { 385 | 386 | // precompute SBOX_inv[deltaMul[Atilde_inv[x]]] 387 | const BaseEncoding commonF = ((isEncrypt) ? 388 | (SBOX_inv.compose(Mult_GF28_tab[delta]).compose(Atilde_inv)) : 389 | (SBOX.compose(Mult_GF28_tab[delta]).compose(Atilde_inv))); 390 | 391 | for (unsigned k = 0; k < LEN_ARRAY; k++) { 392 | if (compute_affine(xy, commonF, k, Ptilde, initPtilde)) { 393 | c = k; 394 | return true; 395 | } 396 | } 397 | return false; 398 | } 399 | 400 | static bool computecd(const BaseEncoding &xy, 401 | const BaseEncoding Ã_inv, 402 | uint8_t &c, uint8_t &d, 403 | EncodingKey &Ptilde, 404 | bool isEncrypt, 405 | bool initPtilde) { 406 | 407 | if (isEncrypt) { 408 | for (unsigned int delta = 1; delta < LEN_ARRAY; delta++) { 409 | if (computec_(xy, Atilde_inv, c, delta, Ptilde, isEncrypt, initPtilde)) { 410 | d = delta; 411 | return true; 412 | } 413 | } 414 | std::cerr << "computecd fail" << std::endl; 415 | return false; 416 | } else { 417 | EncodingKey Ptilde_tmp; 418 | if (!computec_(xy, Atilde_inv, c, 1, Ptilde_tmp, isEncrypt, true)) { 419 | std::cerr << "computecd determine c" << std::endl; 420 | return false; 421 | } 422 | if (initPtilde) { 423 | Ptilde = Ptilde_tmp; 424 | d = 1; 425 | return true; 426 | } 427 | if (Ptilde_tmp == Ptilde) { 428 | d = 1; 429 | return true; 430 | } 431 | for (unsigned delta = 2; delta < LEN_ARRAY; delta++) { 432 | const BaseEncoding commonF = SBOX.compose(Mult_GF28_tab[delta]).compose(Atilde_inv); 433 | 434 | if (compute_affine(xy, commonF, c, Ptilde, false)) { 435 | d = delta; 436 | return true; 437 | } 438 | } 439 | std::cerr << "computecd determine delta" << std::endl; 440 | return false; 441 | } 442 | } 443 | 444 | static bool computec(const BaseEncoding &xy, 445 | const BaseEncoding Ã_inv, 446 | uint8_t &c, uint8_t delta, 447 | EncodingKey &Ptilde, 448 | bool isEncrypt, 449 | bool initPtilde) { 450 | 451 | if (computec_(xy, Atilde_inv, c, delta, Ptilde, isEncrypt, initPtilde)) { 452 | return true; 453 | } 454 | std::cerr << "computec fail" << std::endl; 455 | return false; 456 | } 457 | 458 | class QPtildeLinear { 459 | private: 460 | std::array Atilde_; 461 | std::array, 4> Coeff_; 462 | std::array Q_; 463 | std::array Ptilde_; 464 | std::array q_; 465 | std::array d_; 466 | 467 | bool isFinalize_ = false; 468 | bool isValid_ = false; 469 | 470 | public: 471 | 472 | QPtildeLinear() : isValid_(false) {} 473 | 474 | QPtildeLinear( 475 | const std::array, 4>& xy, 476 | std::array&& Atilde, 477 | std::array, 4>&& Coeff, 478 | bool isEncrypt); 479 | 480 | const std::array, 4>& getCoeff() const { 481 | return Coeff_; 482 | } 483 | 484 | bool isValid() const { return isValid_; } 485 | bool isFinalize() const { return isFinalize_ && isValid_; } 486 | 487 | const std::array& getQ() const { 488 | return Q_; 489 | } 490 | 491 | const AffineEncoding& getQ(uint8_t row) const { 492 | return Q_[row]; 493 | } 494 | 495 | const std::array& getPtilde() const { 496 | return Ptilde_; 497 | } 498 | 499 | const EncodingKey& getPtilde(uint8_t row) const { 500 | return Ptilde_[row]; 501 | } 502 | 503 | void setNextDelta(); 504 | void setFinal() { isFinalize_ = true; } 505 | }; 506 | 507 | static bool computeQPtilde(const std::array, 4> &xy, 508 | QPtildeLinear &QPtilde, 509 | bool isEncrypt) { 510 | 511 | 512 | std::array, 4> Coeff; 513 | std::array Atilde; 514 | if (!computeCoeffXYAtilde(xy, Atilde, Coeff, isEncrypt)) return false; 515 | 516 | 517 | QPtilde = QPtildeLinear(xy, std::move(Atilde), std::move(Coeff), isEncrypt); 518 | if (QPtilde.isValid() && isEncrypt && !QPtilde.isFinalize()) { 519 | std::cerr << "computeQPtilde expect QPtilde to be finalized" << std::endl; 520 | return false; 521 | } 522 | return QPtilde.isValid(); 523 | } 524 | 525 | QPtildeLinear::QPtildeLinear( 526 | const std::array, 4>& xy, 527 | std::array&& Atilde, 528 | std::array, 4>&& Coeff, 529 | bool isEncrypt) 530 | : Atilde_(std::move(Atilde)), 531 | Coeff_(std::move(Coeff)) { 532 | 533 | isValid_ = false; 534 | for (unsigned int row = 0; row < 4; row++) { 535 | BaseEncoding Atilde_inv = Atilde_[row].inverse(); 536 | 537 | std::array c; 538 | uint8_t d_col0; 539 | 540 | if (!computecd(xy[0][row], Atilde_inv, c[0], d_col0, Ptilde_[0], isEncrypt, row==0)) return; 541 | 542 | d_[row] = Mult_GF28(d_col0, Coeff[0][row]); 543 | 544 | if (!computec(xy[1][row], Atilde_inv, c[1], Div_GF28(d_[row], Coeff[1][row]), Ptilde_[1], isEncrypt, row==0)) return; 545 | if (!computec(xy[2][row], Atilde_inv, c[2], Div_GF28(d_[row], Coeff[2][row]), Ptilde_[2], isEncrypt, row==0)) return; 546 | if (!computec(xy[3][row], Atilde_inv, c[3], Div_GF28(d_[row], Coeff[3][row]), Ptilde_[3], isEncrypt, row==0)) return; 547 | 548 | q_[row] = xy[0][row][0] ^ c[0] ^ c[1] ^ c[2] ^ c[3]; 549 | 550 | Q_[row] = AffineEncoding(Atilde_[row].compose(Mult_GF28_tab[Div_GF28(1, d_[row])]), q_[row]); 551 | } 552 | 553 | isValid_ = true; 554 | isFinalize_ = isEncrypt; 555 | } 556 | 557 | void QPtildeLinear::setNextDelta() { 558 | if (isFinalize()) { 559 | return; 560 | } 561 | 562 | uint8_t newd = d_[0]; 563 | if (d_[0] == 255) { 564 | newd = 1; 565 | } else { 566 | newd = d_[0] + 1; 567 | } 568 | 569 | uint8_t diffD = Div_GF28(newd, d_[0]); 570 | d_[0] = newd; 571 | d_[1] = Mult_GF28(d_[1], diffD); 572 | d_[2] = Mult_GF28(d_[2], diffD); 573 | d_[3] = Mult_GF28(d_[3], diffD); 574 | 575 | for (unsigned int row = 0; row < 4; row++) { 576 | Q_[row] = AffineEncoding(Atilde_[row].compose(Mult_GF28_tab[Div_GF28(1, d_[row])]), q_[row]); 577 | } 578 | 579 | for (unsigned int row = 0; row < 4; row++) { 580 | Ptilde_[row] = EncodingKey(SBOX.compose(Mult_GF28_tab[diffD]).compose(SBOX_inv).compose(Ptilde_[row])); 581 | } 582 | } 583 | 584 | static bool getAndVerifyKey(const AffineEncoding& Q, const EncodingKey& Ptilde, uint8_t& key) { 585 | 586 | key = Ptilde[Q[0]]; 587 | for (unsigned int x = 1; x < LEN_ARRAY; x++) { 588 | if (key != (Ptilde[Q[x]] ^ x)) { 589 | return false; 590 | } 591 | } 592 | return true; 593 | } 594 | 595 | static void finalizeQPtilde( 596 | QPtildeLinear &QPtilde0, 597 | QPtildeLinear &QPtilde1, uint8_t row) { 598 | 599 | if (QPtilde0.isFinalize() && QPtilde1.isFinalize()) { 600 | return; 601 | } 602 | 603 | for (unsigned delta0 = 0; delta0 < 255; delta0++) { 604 | for (unsigned delta1 = 0; delta1 < 255; delta1++) { 605 | uint8_t key_dummy; 606 | if (getAndVerifyKey(QPtilde0.getQ(row), QPtilde1.getPtilde(row), key_dummy)) { 607 | QPtilde0.setFinal(); 608 | QPtilde1.setFinal(); 609 | return; 610 | } 611 | 612 | if (QPtilde1.isFinalize() or delta1 == 254) { 613 | break; 614 | } else { 615 | QPtilde1.setNextDelta(); 616 | } 617 | } 618 | if (QPtilde0.isFinalize() or delta0 == 254) { 619 | break; 620 | } else { 621 | QPtilde0.setNextDelta(); 622 | } 623 | } 624 | } 625 | 626 | static void finalizeQPtildeDecrypt( 627 | std::array &QPtilde0, 628 | std::array &QPtilde1) { 629 | 630 | for (unsigned col = 0; col < 4; col++) { 631 | if (not (QPtilde0[col].isValid() and QPtilde1[col].isValid())) { 632 | return; 633 | } 634 | } 635 | 636 | for (unsigned row = 0; row < 4; row++) { 637 | finalizeQPtilde(QPtilde0[0], QPtilde1[ShiftRow[row][0]], row); 638 | } 639 | for (unsigned col = 1; col < 4; col++) { 640 | finalizeQPtilde(QPtilde0[col], QPtilde1[ShiftRow[0][col]], 0); 641 | } 642 | } 643 | 644 | #endif /* COMPUTEQPTILDE_HPP */ 645 | -------------------------------------------------------------------------------- /src/bluegalaxyenergy/AES.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # ----------------------------------------------------------------------------- 4 | # Copyright (C) Quarkslab. See README.md for details. 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the Apache License as published by 8 | # the Apache Software Foundation, either version 2.0 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | # See LICENSE.txt for the text of the Apache license. 14 | # ----------------------------------------------------------------------------- 15 | 16 | from enum import Enum, auto 17 | 18 | _AesInvSBox = ( 19 | 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 20 | 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 21 | 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 22 | 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 23 | 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 24 | 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 25 | 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 26 | 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 27 | 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 28 | 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 29 | 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 30 | 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 31 | 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 32 | 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 33 | 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 34 | 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D 35 | ) 36 | 37 | _AesSBox = ( 38 | 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 39 | 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 40 | 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 41 | 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 42 | 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 43 | 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 44 | 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 45 | 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 46 | 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 47 | 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 48 | 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 49 | 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 50 | 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 51 | 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 52 | 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 53 | 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 54 | ) 55 | 56 | _RCon = ( 57 | 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 58 | 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 59 | 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 60 | 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 61 | ) 62 | 63 | _AesMult = ( 64 | None, ( 65 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 66 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 67 | 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 68 | 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 69 | 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 70 | 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f, 71 | 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, 0x6f, 72 | 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f, 73 | 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 74 | 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 75 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, 76 | 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, 77 | 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, 78 | 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, 79 | 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, 80 | 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff 81 | ), ( 82 | 0x00, 0x02, 0x04, 0x06, 0x08, 0x0a, 0x0c, 0x0e, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1a, 0x1c, 0x1e, 83 | 0x20, 0x22, 0x24, 0x26, 0x28, 0x2a, 0x2c, 0x2e, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3a, 0x3c, 0x3e, 84 | 0x40, 0x42, 0x44, 0x46, 0x48, 0x4a, 0x4c, 0x4e, 0x50, 0x52, 0x54, 0x56, 0x58, 0x5a, 0x5c, 0x5e, 85 | 0x60, 0x62, 0x64, 0x66, 0x68, 0x6a, 0x6c, 0x6e, 0x70, 0x72, 0x74, 0x76, 0x78, 0x7a, 0x7c, 0x7e, 86 | 0x80, 0x82, 0x84, 0x86, 0x88, 0x8a, 0x8c, 0x8e, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9a, 0x9c, 0x9e, 87 | 0xa0, 0xa2, 0xa4, 0xa6, 0xa8, 0xaa, 0xac, 0xae, 0xb0, 0xb2, 0xb4, 0xb6, 0xb8, 0xba, 0xbc, 0xbe, 88 | 0xc0, 0xc2, 0xc4, 0xc6, 0xc8, 0xca, 0xcc, 0xce, 0xd0, 0xd2, 0xd4, 0xd6, 0xd8, 0xda, 0xdc, 0xde, 89 | 0xe0, 0xe2, 0xe4, 0xe6, 0xe8, 0xea, 0xec, 0xee, 0xf0, 0xf2, 0xf4, 0xf6, 0xf8, 0xfa, 0xfc, 0xfe, 90 | 0x1b, 0x19, 0x1f, 0x1d, 0x13, 0x11, 0x17, 0x15, 0x0b, 0x09, 0x0f, 0x0d, 0x03, 0x01, 0x07, 0x05, 91 | 0x3b, 0x39, 0x3f, 0x3d, 0x33, 0x31, 0x37, 0x35, 0x2b, 0x29, 0x2f, 0x2d, 0x23, 0x21, 0x27, 0x25, 92 | 0x5b, 0x59, 0x5f, 0x5d, 0x53, 0x51, 0x57, 0x55, 0x4b, 0x49, 0x4f, 0x4d, 0x43, 0x41, 0x47, 0x45, 93 | 0x7b, 0x79, 0x7f, 0x7d, 0x73, 0x71, 0x77, 0x75, 0x6b, 0x69, 0x6f, 0x6d, 0x63, 0x61, 0x67, 0x65, 94 | 0x9b, 0x99, 0x9f, 0x9d, 0x93, 0x91, 0x97, 0x95, 0x8b, 0x89, 0x8f, 0x8d, 0x83, 0x81, 0x87, 0x85, 95 | 0xbb, 0xb9, 0xbf, 0xbd, 0xb3, 0xb1, 0xb7, 0xb5, 0xab, 0xa9, 0xaf, 0xad, 0xa3, 0xa1, 0xa7, 0xa5, 96 | 0xdb, 0xd9, 0xdf, 0xdd, 0xd3, 0xd1, 0xd7, 0xd5, 0xcb, 0xc9, 0xcf, 0xcd, 0xc3, 0xc1, 0xc7, 0xc5, 97 | 0xfb, 0xf9, 0xff, 0xfd, 0xf3, 0xf1, 0xf7, 0xf5, 0xeb, 0xe9, 0xef, 0xed, 0xe3, 0xe1, 0xe7, 0xe5 98 | ), ( 99 | 0x00, 0x03, 0x06, 0x05, 0x0c, 0x0f, 0x0a, 0x09, 0x18, 0x1b, 0x1e, 0x1d, 0x14, 0x17, 0x12, 0x11, 100 | 0x30, 0x33, 0x36, 0x35, 0x3c, 0x3f, 0x3a, 0x39, 0x28, 0x2b, 0x2e, 0x2d, 0x24, 0x27, 0x22, 0x21, 101 | 0x60, 0x63, 0x66, 0x65, 0x6c, 0x6f, 0x6a, 0x69, 0x78, 0x7b, 0x7e, 0x7d, 0x74, 0x77, 0x72, 0x71, 102 | 0x50, 0x53, 0x56, 0x55, 0x5c, 0x5f, 0x5a, 0x59, 0x48, 0x4b, 0x4e, 0x4d, 0x44, 0x47, 0x42, 0x41, 103 | 0xc0, 0xc3, 0xc6, 0xc5, 0xcc, 0xcf, 0xca, 0xc9, 0xd8, 0xdb, 0xde, 0xdd, 0xd4, 0xd7, 0xd2, 0xd1, 104 | 0xf0, 0xf3, 0xf6, 0xf5, 0xfc, 0xff, 0xfa, 0xf9, 0xe8, 0xeb, 0xee, 0xed, 0xe4, 0xe7, 0xe2, 0xe1, 105 | 0xa0, 0xa3, 0xa6, 0xa5, 0xac, 0xaf, 0xaa, 0xa9, 0xb8, 0xbb, 0xbe, 0xbd, 0xb4, 0xb7, 0xb2, 0xb1, 106 | 0x90, 0x93, 0x96, 0x95, 0x9c, 0x9f, 0x9a, 0x99, 0x88, 0x8b, 0x8e, 0x8d, 0x84, 0x87, 0x82, 0x81, 107 | 0x9b, 0x98, 0x9d, 0x9e, 0x97, 0x94, 0x91, 0x92, 0x83, 0x80, 0x85, 0x86, 0x8f, 0x8c, 0x89, 0x8a, 108 | 0xab, 0xa8, 0xad, 0xae, 0xa7, 0xa4, 0xa1, 0xa2, 0xb3, 0xb0, 0xb5, 0xb6, 0xbf, 0xbc, 0xb9, 0xba, 109 | 0xfb, 0xf8, 0xfd, 0xfe, 0xf7, 0xf4, 0xf1, 0xf2, 0xe3, 0xe0, 0xe5, 0xe6, 0xef, 0xec, 0xe9, 0xea, 110 | 0xcb, 0xc8, 0xcd, 0xce, 0xc7, 0xc4, 0xc1, 0xc2, 0xd3, 0xd0, 0xd5, 0xd6, 0xdf, 0xdc, 0xd9, 0xda, 111 | 0x5b, 0x58, 0x5d, 0x5e, 0x57, 0x54, 0x51, 0x52, 0x43, 0x40, 0x45, 0x46, 0x4f, 0x4c, 0x49, 0x4a, 112 | 0x6b, 0x68, 0x6d, 0x6e, 0x67, 0x64, 0x61, 0x62, 0x73, 0x70, 0x75, 0x76, 0x7f, 0x7c, 0x79, 0x7a, 113 | 0x3b, 0x38, 0x3d, 0x3e, 0x37, 0x34, 0x31, 0x32, 0x23, 0x20, 0x25, 0x26, 0x2f, 0x2c, 0x29, 0x2a, 114 | 0x0b, 0x08, 0x0d, 0x0e, 0x07, 0x04, 0x01, 0x02, 0x13, 0x10, 0x15, 0x16, 0x1f, 0x1c, 0x19, 0x1a 115 | ), None, None, None, None, None, ( 116 | 0x00, 0x09, 0x12, 0x1b, 0x24, 0x2d, 0x36, 0x3f, 0x48, 0x41, 0x5a, 0x53, 0x6c, 0x65, 0x7e, 0x77, 117 | 0x90, 0x99, 0x82, 0x8b, 0xb4, 0xbd, 0xa6, 0xaf, 0xd8, 0xd1, 0xca, 0xc3, 0xfc, 0xf5, 0xee, 0xe7, 118 | 0x3b, 0x32, 0x29, 0x20, 0x1f, 0x16, 0x0d, 0x04, 0x73, 0x7a, 0x61, 0x68, 0x57, 0x5e, 0x45, 0x4c, 119 | 0xab, 0xa2, 0xb9, 0xb0, 0x8f, 0x86, 0x9d, 0x94, 0xe3, 0xea, 0xf1, 0xf8, 0xc7, 0xce, 0xd5, 0xdc, 120 | 0x76, 0x7f, 0x64, 0x6d, 0x52, 0x5b, 0x40, 0x49, 0x3e, 0x37, 0x2c, 0x25, 0x1a, 0x13, 0x08, 0x01, 121 | 0xe6, 0xef, 0xf4, 0xfd, 0xc2, 0xcb, 0xd0, 0xd9, 0xae, 0xa7, 0xbc, 0xb5, 0x8a, 0x83, 0x98, 0x91, 122 | 0x4d, 0x44, 0x5f, 0x56, 0x69, 0x60, 0x7b, 0x72, 0x05, 0x0c, 0x17, 0x1e, 0x21, 0x28, 0x33, 0x3a, 123 | 0xdd, 0xd4, 0xcf, 0xc6, 0xf9, 0xf0, 0xeb, 0xe2, 0x95, 0x9c, 0x87, 0x8e, 0xb1, 0xb8, 0xa3, 0xaa, 124 | 0xec, 0xe5, 0xfe, 0xf7, 0xc8, 0xc1, 0xda, 0xd3, 0xa4, 0xad, 0xb6, 0xbf, 0x80, 0x89, 0x92, 0x9b, 125 | 0x7c, 0x75, 0x6e, 0x67, 0x58, 0x51, 0x4a, 0x43, 0x34, 0x3d, 0x26, 0x2f, 0x10, 0x19, 0x02, 0x0b, 126 | 0xd7, 0xde, 0xc5, 0xcc, 0xf3, 0xfa, 0xe1, 0xe8, 0x9f, 0x96, 0x8d, 0x84, 0xbb, 0xb2, 0xa9, 0xa0, 127 | 0x47, 0x4e, 0x55, 0x5c, 0x63, 0x6a, 0x71, 0x78, 0x0f, 0x06, 0x1d, 0x14, 0x2b, 0x22, 0x39, 0x30, 128 | 0x9a, 0x93, 0x88, 0x81, 0xbe, 0xb7, 0xac, 0xa5, 0xd2, 0xdb, 0xc0, 0xc9, 0xf6, 0xff, 0xe4, 0xed, 129 | 0x0a, 0x03, 0x18, 0x11, 0x2e, 0x27, 0x3c, 0x35, 0x42, 0x4b, 0x50, 0x59, 0x66, 0x6f, 0x74, 0x7d, 130 | 0xa1, 0xa8, 0xb3, 0xba, 0x85, 0x8c, 0x97, 0x9e, 0xe9, 0xe0, 0xfb, 0xf2, 0xcd, 0xc4, 0xdf, 0xd6, 131 | 0x31, 0x38, 0x23, 0x2a, 0x15, 0x1c, 0x07, 0x0e, 0x79, 0x70, 0x6b, 0x62, 0x5d, 0x54, 0x4f, 0x46 132 | ), None, ( 133 | 0x00, 0x0b, 0x16, 0x1d, 0x2c, 0x27, 0x3a, 0x31, 0x58, 0x53, 0x4e, 0x45, 0x74, 0x7f, 0x62, 0x69, 134 | 0xb0, 0xbb, 0xa6, 0xad, 0x9c, 0x97, 0x8a, 0x81, 0xe8, 0xe3, 0xfe, 0xf5, 0xc4, 0xcf, 0xd2, 0xd9, 135 | 0x7b, 0x70, 0x6d, 0x66, 0x57, 0x5c, 0x41, 0x4a, 0x23, 0x28, 0x35, 0x3e, 0x0f, 0x04, 0x19, 0x12, 136 | 0xcb, 0xc0, 0xdd, 0xd6, 0xe7, 0xec, 0xf1, 0xfa, 0x93, 0x98, 0x85, 0x8e, 0xbf, 0xb4, 0xa9, 0xa2, 137 | 0xf6, 0xfd, 0xe0, 0xeb, 0xda, 0xd1, 0xcc, 0xc7, 0xae, 0xa5, 0xb8, 0xb3, 0x82, 0x89, 0x94, 0x9f, 138 | 0x46, 0x4d, 0x50, 0x5b, 0x6a, 0x61, 0x7c, 0x77, 0x1e, 0x15, 0x08, 0x03, 0x32, 0x39, 0x24, 0x2f, 139 | 0x8d, 0x86, 0x9b, 0x90, 0xa1, 0xaa, 0xb7, 0xbc, 0xd5, 0xde, 0xc3, 0xc8, 0xf9, 0xf2, 0xef, 0xe4, 140 | 0x3d, 0x36, 0x2b, 0x20, 0x11, 0x1a, 0x07, 0x0c, 0x65, 0x6e, 0x73, 0x78, 0x49, 0x42, 0x5f, 0x54, 141 | 0xf7, 0xfc, 0xe1, 0xea, 0xdb, 0xd0, 0xcd, 0xc6, 0xaf, 0xa4, 0xb9, 0xb2, 0x83, 0x88, 0x95, 0x9e, 142 | 0x47, 0x4c, 0x51, 0x5a, 0x6b, 0x60, 0x7d, 0x76, 0x1f, 0x14, 0x09, 0x02, 0x33, 0x38, 0x25, 0x2e, 143 | 0x8c, 0x87, 0x9a, 0x91, 0xa0, 0xab, 0xb6, 0xbd, 0xd4, 0xdf, 0xc2, 0xc9, 0xf8, 0xf3, 0xee, 0xe5, 144 | 0x3c, 0x37, 0x2a, 0x21, 0x10, 0x1b, 0x06, 0x0d, 0x64, 0x6f, 0x72, 0x79, 0x48, 0x43, 0x5e, 0x55, 145 | 0x01, 0x0a, 0x17, 0x1c, 0x2d, 0x26, 0x3b, 0x30, 0x59, 0x52, 0x4f, 0x44, 0x75, 0x7e, 0x63, 0x68, 146 | 0xb1, 0xba, 0xa7, 0xac, 0x9d, 0x96, 0x8b, 0x80, 0xe9, 0xe2, 0xff, 0xf4, 0xc5, 0xce, 0xd3, 0xd8, 147 | 0x7a, 0x71, 0x6c, 0x67, 0x56, 0x5d, 0x40, 0x4b, 0x22, 0x29, 0x34, 0x3f, 0x0e, 0x05, 0x18, 0x13, 148 | 0xca, 0xc1, 0xdc, 0xd7, 0xe6, 0xed, 0xf0, 0xfb, 0x92, 0x99, 0x84, 0x8f, 0xbe, 0xb5, 0xa8, 0xa3 149 | ), None, ( 150 | 0x00, 0x0d, 0x1a, 0x17, 0x34, 0x39, 0x2e, 0x23, 0x68, 0x65, 0x72, 0x7f, 0x5c, 0x51, 0x46, 0x4b, 151 | 0xd0, 0xdd, 0xca, 0xc7, 0xe4, 0xe9, 0xfe, 0xf3, 0xb8, 0xb5, 0xa2, 0xaf, 0x8c, 0x81, 0x96, 0x9b, 152 | 0xbb, 0xb6, 0xa1, 0xac, 0x8f, 0x82, 0x95, 0x98, 0xd3, 0xde, 0xc9, 0xc4, 0xe7, 0xea, 0xfd, 0xf0, 153 | 0x6b, 0x66, 0x71, 0x7c, 0x5f, 0x52, 0x45, 0x48, 0x03, 0x0e, 0x19, 0x14, 0x37, 0x3a, 0x2d, 0x20, 154 | 0x6d, 0x60, 0x77, 0x7a, 0x59, 0x54, 0x43, 0x4e, 0x05, 0x08, 0x1f, 0x12, 0x31, 0x3c, 0x2b, 0x26, 155 | 0xbd, 0xb0, 0xa7, 0xaa, 0x89, 0x84, 0x93, 0x9e, 0xd5, 0xd8, 0xcf, 0xc2, 0xe1, 0xec, 0xfb, 0xf6, 156 | 0xd6, 0xdb, 0xcc, 0xc1, 0xe2, 0xef, 0xf8, 0xf5, 0xbe, 0xb3, 0xa4, 0xa9, 0x8a, 0x87, 0x90, 0x9d, 157 | 0x06, 0x0b, 0x1c, 0x11, 0x32, 0x3f, 0x28, 0x25, 0x6e, 0x63, 0x74, 0x79, 0x5a, 0x57, 0x40, 0x4d, 158 | 0xda, 0xd7, 0xc0, 0xcd, 0xee, 0xe3, 0xf4, 0xf9, 0xb2, 0xbf, 0xa8, 0xa5, 0x86, 0x8b, 0x9c, 0x91, 159 | 0x0a, 0x07, 0x10, 0x1d, 0x3e, 0x33, 0x24, 0x29, 0x62, 0x6f, 0x78, 0x75, 0x56, 0x5b, 0x4c, 0x41, 160 | 0x61, 0x6c, 0x7b, 0x76, 0x55, 0x58, 0x4f, 0x42, 0x09, 0x04, 0x13, 0x1e, 0x3d, 0x30, 0x27, 0x2a, 161 | 0xb1, 0xbc, 0xab, 0xa6, 0x85, 0x88, 0x9f, 0x92, 0xd9, 0xd4, 0xc3, 0xce, 0xed, 0xe0, 0xf7, 0xfa, 162 | 0xb7, 0xba, 0xad, 0xa0, 0x83, 0x8e, 0x99, 0x94, 0xdf, 0xd2, 0xc5, 0xc8, 0xeb, 0xe6, 0xf1, 0xfc, 163 | 0x67, 0x6a, 0x7d, 0x70, 0x53, 0x5e, 0x49, 0x44, 0x0f, 0x02, 0x15, 0x18, 0x3b, 0x36, 0x21, 0x2c, 164 | 0x0c, 0x01, 0x16, 0x1b, 0x38, 0x35, 0x22, 0x2f, 0x64, 0x69, 0x7e, 0x73, 0x50, 0x5d, 0x4a, 0x47, 165 | 0xdc, 0xd1, 0xc6, 0xcb, 0xe8, 0xe5, 0xf2, 0xff, 0xb4, 0xb9, 0xae, 0xa3, 0x80, 0x8d, 0x9a, 0x97 166 | ), ( 167 | 0x00, 0x0e, 0x1c, 0x12, 0x38, 0x36, 0x24, 0x2a, 0x70, 0x7e, 0x6c, 0x62, 0x48, 0x46, 0x54, 0x5a, 168 | 0xe0, 0xee, 0xfc, 0xf2, 0xd8, 0xd6, 0xc4, 0xca, 0x90, 0x9e, 0x8c, 0x82, 0xa8, 0xa6, 0xb4, 0xba, 169 | 0xdb, 0xd5, 0xc7, 0xc9, 0xe3, 0xed, 0xff, 0xf1, 0xab, 0xa5, 0xb7, 0xb9, 0x93, 0x9d, 0x8f, 0x81, 170 | 0x3b, 0x35, 0x27, 0x29, 0x03, 0x0d, 0x1f, 0x11, 0x4b, 0x45, 0x57, 0x59, 0x73, 0x7d, 0x6f, 0x61, 171 | 0xad, 0xa3, 0xb1, 0xbf, 0x95, 0x9b, 0x89, 0x87, 0xdd, 0xd3, 0xc1, 0xcf, 0xe5, 0xeb, 0xf9, 0xf7, 172 | 0x4d, 0x43, 0x51, 0x5f, 0x75, 0x7b, 0x69, 0x67, 0x3d, 0x33, 0x21, 0x2f, 0x05, 0x0b, 0x19, 0x17, 173 | 0x76, 0x78, 0x6a, 0x64, 0x4e, 0x40, 0x52, 0x5c, 0x06, 0x08, 0x1a, 0x14, 0x3e, 0x30, 0x22, 0x2c, 174 | 0x96, 0x98, 0x8a, 0x84, 0xae, 0xa0, 0xb2, 0xbc, 0xe6, 0xe8, 0xfa, 0xf4, 0xde, 0xd0, 0xc2, 0xcc, 175 | 0x41, 0x4f, 0x5d, 0x53, 0x79, 0x77, 0x65, 0x6b, 0x31, 0x3f, 0x2d, 0x23, 0x09, 0x07, 0x15, 0x1b, 176 | 0xa1, 0xaf, 0xbd, 0xb3, 0x99, 0x97, 0x85, 0x8b, 0xd1, 0xdf, 0xcd, 0xc3, 0xe9, 0xe7, 0xf5, 0xfb, 177 | 0x9a, 0x94, 0x86, 0x88, 0xa2, 0xac, 0xbe, 0xb0, 0xea, 0xe4, 0xf6, 0xf8, 0xd2, 0xdc, 0xce, 0xc0, 178 | 0x7a, 0x74, 0x66, 0x68, 0x42, 0x4c, 0x5e, 0x50, 0x0a, 0x04, 0x16, 0x18, 0x32, 0x3c, 0x2e, 0x20, 179 | 0xec, 0xe2, 0xf0, 0xfe, 0xd4, 0xda, 0xc8, 0xc6, 0x9c, 0x92, 0x80, 0x8e, 0xa4, 0xaa, 0xb8, 0xb6, 180 | 0x0c, 0x02, 0x10, 0x1e, 0x34, 0x3a, 0x28, 0x26, 0x7c, 0x72, 0x60, 0x6e, 0x44, 0x4a, 0x58, 0x56, 181 | 0x37, 0x39, 0x2b, 0x25, 0x0f, 0x01, 0x13, 0x1d, 0x47, 0x49, 0x5b, 0x55, 0x7f, 0x71, 0x63, 0x6d, 182 | 0xd7, 0xd9, 0xcb, 0xc5, 0xef, 0xe1, 0xf3, 0xfd, 0xa7, 0xa9, 0xbb, 0xb5, 0x9f, 0x91, 0x83, 0x8d 183 | ), None 184 | ) 185 | 186 | _AesShiftRow = (0, 5, 10, 15, 4, 9, 14, 3, 8, 13, 2, 7, 12, 1, 6, 11) 187 | _AesInvShiftRow = (0, 13, 10, 7, 4, 1, 14, 11, 8, 5, 2, 15, 12, 9, 6, 3) 188 | _MC = ((2, 3, 1, 1), (1, 2, 3, 1), (1, 1, 2, 3), (3, 1, 1, 2)) 189 | _invMC = ((14, 11, 13, 9), (9, 14, 11, 13), (13, 9, 14, 11), (11, 13, 9, 14)) 190 | 191 | 192 | def MC(state): 193 | """ 194 | Applies AES MixColumns on AES state 195 | :param state: AES state, as 16-byte list 196 | :returns: new AES state 197 | """ 198 | state2 = [0] * 16 199 | for i in range(4): 200 | for j in range(4): 201 | state2[(4*i)+j] = _AesMult[_MC[j][0]][state[(4*i)+0]] ^ \ 202 | _AesMult[_MC[j][1]][state[(4*i)+1]] ^ \ 203 | _AesMult[_MC[j][2]][state[(4*i)+2]] ^ \ 204 | _AesMult[_MC[j][3]][state[(4*i)+3]] 205 | return bytes(state2) 206 | 207 | 208 | def InvMC(state): 209 | """ 210 | Applies AES invMixColumns on AES state 211 | :param state: AES state, as 16-byte list 212 | :returns: new AES state 213 | """ 214 | state2 = [0] * 16 215 | for i in range(4): 216 | for j in range(4): 217 | state2[(4*i)+j] = _AesMult[_invMC[j][0]][state[(4*i)+0]] ^ \ 218 | _AesMult[_invMC[j][1]][state[(4*i)+1]] ^ \ 219 | _AesMult[_invMC[j][2]][state[(4*i)+2]] ^ \ 220 | _AesMult[_invMC[j][3]][state[(4*i)+3]] 221 | return bytes(state2) 222 | 223 | 224 | def InvSBox(state): 225 | """ 226 | Applies AES invSBox on AES state 227 | :param state: AES state, as 16-byte list 228 | :returns: new AES state 229 | """ 230 | return bytes([_AesInvSBox[x] for x in state]) 231 | 232 | 233 | def SBox(state): 234 | """ 235 | Applies AES SBox on AES state 236 | :param state: AES state, as 16-byte list 237 | :returns: new AES state 238 | """ 239 | return bytes([_AesSBox[x] for x in state]) 240 | 241 | 242 | def InvShiftRow(state): 243 | """ 244 | Applies AES invShiftRow on AES state 245 | :param state: AES state, as 16-byte list 246 | :returns: new AES state 247 | """ 248 | return bytes([state[0], state[13], state[10], state[7], 249 | state[4], state[1], state[14], state[11], 250 | state[8], state[5], state[2], state[15], 251 | state[12], state[9], state[6], state[3]]) 252 | 253 | 254 | def ShiftRow(state): 255 | """ 256 | Applies AES ShiftRow on AES state 257 | :param state: AES state, as 16-byte list 258 | :returns: new AES state 259 | """ 260 | return bytes([state[0], state[5], state[10], state[15], 261 | state[4], state[9], state[14], state[3], 262 | state[8], state[13], state[2], state[7], 263 | state[12], state[1], state[6], state[11]]) 264 | 265 | 266 | def xor(s, k): 267 | return bytes([x ^ y for x, y in zip(s, k)]) 268 | 269 | 270 | def extendKey(key, r): 271 | """ 272 | Key Schedule of AES 273 | :param key: AES key, as 16/24/32 bytes 274 | :param r: The number of rounds for AES (need r+1 roundKey) 275 | :returns: An array of roundKey 276 | """ 277 | keySize = len(key) // 4 278 | extKey = key 279 | for i in range(keySize, (r + 1) * 4): 280 | t = extKey[(i-1)*4:i*4] 281 | t2 = extKey[(i-keySize)*4:(i+1-keySize)*4] 282 | if i % keySize == 0: 283 | t = SBox(t[1:] + t[:1]) 284 | t = xor(t, bytes([_RCon[i // keySize], 0, 0, 0])) 285 | elif keySize == 8 and (i % 4) == 0: 286 | t = SBox(t) 287 | extKey += xor(t, t2) 288 | 289 | return [extKey[16*i:16*(i+1)] for i in range(len(extKey) // 16)] 290 | 291 | 292 | def revertKey(rKey, roundN): 293 | """ 294 | Revert AES Key from AES round Key 295 | :param key: AES round key, as 16 (for AES-128) /24 (for AES-192) / 32 (for AES-256) bytes 296 | :param roundN: The round associated with this key. for AES>128, round associated with the first round key. 297 | :returns: The Associated AES Key 298 | """ 299 | keySize = len(rKey) // 4 300 | extKey = rKey 301 | for i in range((roundN * 4) + keySize - 1, keySize - 1, -1): 302 | t = extKey[(keySize-2)*4:(keySize-1)*4] 303 | t2 = extKey[(keySize-1)*4:keySize*4] 304 | if i % keySize == 0: 305 | t = SBox(t[1:] + t[:1]) 306 | t = xor(t, bytes([_RCon[i // keySize], 0, 0, 0])) 307 | elif keySize == 8 and (i % 4) == 0: 308 | t = SBox(t) 309 | extKey = xor(t, t2) + extKey[:(keySize-1)*4] 310 | return extKey[:keySize*4] 311 | 312 | 313 | class RoundType(Enum): 314 | RoundEncType0 = auto() 315 | RoundEncType1 = auto() 316 | RoundEncType2 = auto() 317 | RoundDecType0 = auto() 318 | 319 | 320 | class AES: 321 | 322 | def __init__(self, key, r=None, addExtraMC=False, roundType=RoundType.RoundEncType0): 323 | 324 | assert len(key) in [16, 24, 32] 325 | if r is None: 326 | r = {16: 10, 24: 12, 32: 14}[len(key)] 327 | 328 | self.r = r 329 | self.addExtraMC = addExtraMC 330 | self.setKey(key) 331 | self.roundType = roundType 332 | 333 | def setKey(self, key): 334 | self.key = key 335 | self.extendedKey = extendKey(key, self.r) 336 | 337 | def encrypt_round(self, state, roundN): 338 | assert 0 <= roundN and roundN < self.r 339 | 340 | if self.roundType == RoundType.RoundEncType0: 341 | if roundN == self.r - 1: 342 | state = xor(state, self.extendedKey[roundN]) 343 | state = SBox(state) 344 | state = ShiftRow(state) 345 | if self.addExtraMC: 346 | state = MC(state) 347 | state = xor(state, self.extendedKey[self.r]) 348 | else: 349 | state = xor(state, self.extendedKey[roundN]) 350 | state = SBox(state) 351 | state = ShiftRow(state) 352 | state = MC(state) 353 | elif self.roundType == RoundType.RoundEncType1: 354 | if roundN == self.r - 1: 355 | state = SBox(state) 356 | state = ShiftRow(state) 357 | if self.addExtraMC: 358 | state = MC(state) 359 | state = xor(state, self.extendedKey[self.r]) 360 | else: 361 | if roundN == 0: 362 | state = xor(state, self.extendedKey[0]) 363 | state = SBox(state) 364 | state = ShiftRow(state) 365 | state = MC(state) 366 | state = xor(state, self.extendedKey[roundN+1]) 367 | elif self.roundType == RoundType.RoundEncType2: 368 | if roundN == self.r - 1: 369 | state = ShiftRow(state) 370 | if self.addExtraMC: 371 | state = MC(state) 372 | state = xor(state, self.extendedKey[self.r]) 373 | else: 374 | if roundN == 0: 375 | state = xor(state, self.extendedKey[0]) 376 | state = SBox(state) 377 | state = ShiftRow(state) 378 | state = MC(state) 379 | state = xor(state, self.extendedKey[roundN+1]) 380 | state = SBox(state) 381 | elif self.roundType == RoundType.RoundDecType0: 382 | if roundN == self.r - 1: 383 | state = MC(state) 384 | state = xor(state, self.extendedKey[roundN]) 385 | state = SBox(state) 386 | state = ShiftRow(state) 387 | if self.addExtraMC: 388 | state = MC(state) 389 | state = xor(state, self.extendedKey[self.r]) 390 | else: 391 | if roundN != 0: 392 | state = MC(state) 393 | state = xor(state, self.extendedKey[roundN]) 394 | state = SBox(state) 395 | state = ShiftRow(state) 396 | return state 397 | 398 | def encrypt(self, state): 399 | assert len(state) == 16 400 | assert len(self.extendedKey) == self.r + 1 401 | 402 | for i in range(self.r): 403 | state = self.encrypt_round(state, i) 404 | return state 405 | 406 | def decrypt_round(self, state, roundN): 407 | assert 0 <= roundN and roundN < self.r 408 | 409 | if self.roundType == RoundType.RoundEncType0: 410 | if roundN == self.r - 1: 411 | state = xor(state, self.extendedKey[self.r]) 412 | if self.addExtraMC: 413 | state = InvMC(state) 414 | state = InvShiftRow(state) 415 | state = InvSBox(state) 416 | state = xor(state, self.extendedKey[roundN]) 417 | else: 418 | state = InvMC(state) 419 | state = InvShiftRow(state) 420 | state = InvSBox(state) 421 | state = xor(state, self.extendedKey[roundN]) 422 | elif self.roundType == RoundType.RoundEncType1: 423 | if roundN == self.r - 1: 424 | state = xor(state, self.extendedKey[self.r]) 425 | if self.addExtraMC: 426 | state = InvMC(state) 427 | state = InvShiftRow(state) 428 | state = InvSBox(state) 429 | else: 430 | state = xor(state, self.extendedKey[roundN+1]) 431 | state = InvMC(state) 432 | state = InvShiftRow(state) 433 | state = InvSBox(state) 434 | if roundN == 0: 435 | state = xor(state, self.extendedKey[0]) 436 | elif self.roundType == RoundType.RoundEncType2: 437 | if roundN == self.r - 1: 438 | state = xor(state, self.extendedKey[self.r]) 439 | if self.addExtraMC: 440 | state = InvMC(state) 441 | state = InvShiftRow(state) 442 | else: 443 | state = InvSBox(state) 444 | state = xor(state, self.extendedKey[roundN+1]) 445 | state = InvMC(state) 446 | state = InvShiftRow(state) 447 | if roundN == 0: 448 | state = InvSBox(state) 449 | state = xor(state, self.extendedKey[0]) 450 | elif self.roundType == RoundType.RoundDecType0: 451 | if roundN == self.r - 1: 452 | state = xor(state, self.extendedKey[self.r]) 453 | if self.addExtraMC: 454 | state = InvMC(state) 455 | state = InvShiftRow(state) 456 | state = InvSBox(state) 457 | state = xor(state, self.extendedKey[roundN]) 458 | state = InvMC(state) 459 | else: 460 | state = InvShiftRow(state) 461 | state = InvSBox(state) 462 | state = xor(state, self.extendedKey[roundN]) 463 | if roundN != 0: 464 | state = InvMC(state) 465 | else: 466 | assert False 467 | return state 468 | 469 | def decrypt(self, state): 470 | assert len(state) == 16 471 | assert len(self.extendedKey) == self.r + 1 472 | 473 | for i in reversed(list(range(self.r))): 474 | state = self.decrypt_round(state, i) 475 | return state 476 | 477 | 478 | def test(): 479 | # test case : https://github.com/ircmaxell/quality-checker/blob/master/tmp/gh_18/PHP-PasswordLib-master/test/Data/Vectors/aes-ecb.test-vectors # noqa:E501 480 | test_cases = [ 481 | ("2b7e151628aed2a6abf7158809cf4f3c", 10, 482 | "6bc1bee22e409f96e93d7e117393172a", 483 | "3ad77bb40d7a3660a89ecaf32466ef97"), 484 | ("2b7e151628aed2a6abf7158809cf4f3c", 10, 485 | "ae2d8a571e03ac9c9eb76fac45af8e51", 486 | "f5d3d58503b9699de785895a96fdbaaf"), 487 | ("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", 12, 488 | "6bc1bee22e409f96e93d7e117393172a", 489 | "bd334f1d6e45f25ff712a214571fa5cc"), 490 | ("8e73b0f7da0e6452c810f32b809079e562f8ead2522c6b7b", 12, 491 | "ae2d8a571e03ac9c9eb76fac45af8e51", 492 | "974104846d0ad3ad7734ecb3ecee4eef"), 493 | ("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 14, 494 | "6bc1bee22e409f96e93d7e117393172a", 495 | "f3eed1bdb5d2a03c064b5a7e3db181f8"), 496 | ("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4", 14, 497 | "ae2d8a571e03ac9c9eb76fac45af8e51", 498 | "591ccb10d410ed26dc5ba74a31362870"), 499 | ] 500 | 501 | for roundType in RoundType: 502 | for key, r, plain, cipher in test_cases: 503 | c = AES(bytes.fromhex(key), r, roundType=roundType) 504 | assert c.encrypt(bytes.fromhex(plain)) == bytes.fromhex(cipher) 505 | assert c.decrypt(bytes.fromhex(cipher)) == bytes.fromhex(plain) 506 | 507 | for i in range(r): 508 | assert c.decrypt_round(c.encrypt_round(bytes.fromhex(plain), i), i) == bytes.fromhex(plain) 509 | 510 | for keyLength in [16, 24, 32]: 511 | for _ in range(16): 512 | key = random.randbytes(keyLength) 513 | keySched = extendKey(key, 10) 514 | rKey = (keySched[9] + keySched[10])[:keyLength] 515 | assert revertKey(rKey, 9) == key 516 | 517 | 518 | if __name__ == "__main__": 519 | import random 520 | test() 521 | -------------------------------------------------------------------------------- /implementation_details.md: -------------------------------------------------------------------------------- 1 | # BGE implementation 2 | 3 | This file intends to explain some of the choices that diverge from the 4 | original [[BGE](#BGE)] paper and explain some of our design choices. 5 | 6 | We try to do our best to write the note in a unified way, by trying to 7 | keep the notation of [[BGE](#BGE)]. The basis vectors of the matrix are 8 | row-wise oriented, so multiplying a matrix `A` by a vector `v` will be denoted 9 | `v * A`. 10 | 11 | ## Setup 12 | 13 | Suppose that we have access to a white-box, where each round `r` and 14 | each column of a state `c` can be viewed as 15 | 16 | ```txt 17 | --------------------------------------------- 18 | 8 bits x[r, 3, c] | P[r, 3, c] | T[r, 3, c] | | Q[r, 3, c] | y[r, 3, c] 8 bits 19 | |-------------------------- -------------| 20 | 8 bits x[r, 2, c] | P[r, 2, c] | T[r, 2, c] | | Q[r, 2, c] | y[r, 2, c] 8 bits 21 | |-------------------------- MC -------------| 22 | 8 bits x[r, 1, c] | P[r, 1, c] | T[r, 1, c] | | Q[r, 1, c] | y[r, 1, c] 8 bits 23 | |-------------------------- -------------| 24 | 8 bits x[r, 0, c] | P[r, 0, c] | T[r, 0, c] | | Q[r, 0, c] | y[r, 0, c] 8 bits 25 | --------------------------------------------- 26 | ``` 27 | 28 | where `T[round, i, c](v)` is `SubBytes(v) + RoundKey[idx, c]` 29 | 30 | where `P^(-1)[r + 1, i, c] = Q[r, i, c]`. Since we will work always on 31 | the same round, we will remove the index of the round in the notation if 32 | it is not necessary. We also work on the same column `c`, so we will 33 | also remove this component. 34 | 35 | ## Recovering non-linear parts, Section 3.1 36 | 37 | **Note: in this section, `(S, o)` denotes the set `S` with the operation 38 | `o`, which is the composition of functions of `S`.** 39 | 40 | Referring to the diagram above, the output `y[0]` is a function of 41 | `(x[0], x[1], x[2], x[3])`. Let `c1, c2` and `c3` be three constants, 42 | the function `y[0]` can be written as `y[0](x[0], c1, c2, c3) = Q[0](a * 43 | T[0](P[0](x[0]) + β[c1, c2, c3]))`. Let `c1'` be another constant, we 44 | can write `y[0](x[0], c1', c2, c3)^(-1) = P[0]^(-1)(T[0]^(-1)(a^(-1) * 45 | (Q[0]^(-1)(x) + β[c1', c2, c3])))`. We can therefore write that 46 | `y[0](y[0]^(-1)(x[0], c1', c2, c3), c1, c2, c3) = 47 | Q[0](Q[0]^(-1)[0](x[0]) + β)`, where `β = β[c1, c2, c3] + β[c1', c2, 48 | c3]`. 49 | 50 | Let `S` be the set of functions of the form `Q(Q^(-1)(x) + β)`. This set 51 | contains 256 functions, from `GF(2^8)` to `GF(2^8)`. The goals of the 52 | first step of the [[BGE](#BGE)] attack is to recover `Q` up to an affine 53 | mapping `A`, that is finding `Qtilde(x) = Q(A(x))`. In order to recover 54 | `Qtilde`, the first step is to recover a group isomorphism `ψ` from `(S, 55 | o)` to `(GF(2)^8, +)`. By following [[BGE](#BGE)], we recover `Qtilde` 56 | by applying on each function `f` of `S` the equality: `f(0) = Qtilde(ψ(f)) 57 | `. 58 | 59 | ### Recover `ψ` 60 | 61 | The function `ψ` maps `(S, o)` to `(GF(2)^8, +)`. Let `φ` be the map 62 | from `(S, o)` to `(GF(2)^8, +)` where the binary representation of `β` 63 | is viewed as a vector of `GF(2)^8` in the canonical basis of the vector 64 | space `GF(2)^8`. However, since `β`is unknown, we may not find `φ`, but 65 | another maps `ψ` that maps functions of `S` into vector of `GF(2)^8` in 66 | an arbitrary basis. A change from a basis to another is a linear map, 67 | hence the fact that `Qtilde` is equal to `Q` up to a linear map. 68 | 69 | An algorithm to find `ψ` is the following: 70 | 71 | 0. Let `i` be equal to `1`. 72 | 1. Pick a random function `f` from `S` and remove `f` from `S`. 73 | 2. If `(f, ·)` is not in the list `R`, include in `R` the tuple `(f, 74 | ei)`, where `ei` is the `i`-th basis vector (i.e. `ei = (0, 0, …,0, 75 | 1, 0, 0, …, 0)` where the `1` is at the `i`-th place). Set `ψ(f) = 76 | e`. For all the tuples `(g, η)` in `R`, set `ψ(f(g)) = ψ(f) + ψ(g) = 77 | e + η`. 78 | 3. If `S` is not empty, increment `i` and go to step `1`. 79 | 80 | **Note**: this presentation is a bit different from the one of 81 | [[BGE](#BGE)], since the constructed basis is built by repeatedly 82 | multiplying by `02` the element `ei` initialized to `01`, which gives 83 | the sequence `(2, 4, 8, 16, 32, 64, 128, 27)`. 84 | 85 | #### Classical way 86 | 87 | **Note**: you can find the initial description in [[BGE](#BGE)]. What we 88 | present here mimic the algorithm by incorporating our modification with 89 | respect to the basis vector. 90 | 91 | Let us describe first how we think that object must be managed and 92 | created. 93 | 94 | ##### Objects 95 | 96 | Let `f` be a function of `S`, described by its 256 values in `GF(2^8)` 97 | (f.val). A unique identifier of a function `f` is its evaluation in `0` 98 | (f.id = f.val[0]) for example, as in[[Tol](#Tol)]. 99 | 100 | The set `S` can be considered as a stack where the functions are stored 101 | (only the pop() function will be used). Since at a point of the 102 | algorithm, we need to refer to the functions stored in `S`, we consider 103 | `S` as a hash map which maps function identifier to the function `f` in 104 | the chosen basis and `s` as a list of the 256 values in [0, 256[, not 105 | necessarily sorted. 106 | 107 | The set `R` can be considered as a hash map that maps the function 108 | identifier to an 8-bit integer that represents `β` in a basis of 109 | `GF(2)^8`. 110 | 111 | Finally, the function `ψ`, which maps `(S, o)` to `(GF(2)^8, +)`, can be 112 | represented by a hash map that maps function identifier to decomposition 113 | of `β` in the chosen basis. 114 | 115 | Note that, since the hash function of the hash map is the function that 116 | maps a function `f` of `S` to its function identifier, hash map are 117 | simple array. We denote by `R.nb` the number of elements inserted into 118 | `R`, by `R.contains(id)` the request to know if something was defined at 119 | the index `id` of `R`, by `R.keys()` the list of all the keys (i.e. 120 | indexes) defined in `R` (remember that the keys is the function 121 | identifier) and by `R.find(id)` the functions that returns the element 122 | at the index `id`. 123 | 124 | ##### Algorithm 125 | 126 | We now try to write the algorithm described in [[BGE](#BGE)] in 127 | pseudo-code, with our modification. 128 | 129 | ```txt 130 | R[0] <- 0 131 | ψ[0] <- 0 132 | e <- 1 133 | i = 0 134 | while R.nb < 2^8: 135 | f <- S.find(s[i]) 136 | i <- i + 1 137 | if not R.contains(f.id): 138 | ψ[f.id] <- e 139 | for g in R.keys(): 140 | fogid <- f.val[g.id] // remember that g.id = g.val[0], then f.val[g.id] = f.val[g.val[0]] 141 | vec <- e + R[g.id] // + equal xor in this context 142 | R[fogid] <- vec 143 | ψ[fogid] <- vec 144 | e <- e << 1 145 | ``` 146 | 147 | As we can see, `R` and `ψ` play the same role when it is necessary to 148 | store the information about the value of a function in a basis. In order 149 | to remove `R`, we need to store the information about which function is 150 | in `R` or not and the number of elements in `R`. We replace `R` by 151 | respectively an array `inR` which stores at index `i` if the function of 152 | identifier `i` would be in `R` or not, and by an integer `nbR` the 153 | number of elements in `R`. The array `s` is also not necessary, we can 154 | take the function in the order of how they are stored in `S` (in order 155 | to mimic [[Tol](#Tol)], we store at index `i` of `S` the function of 156 | identifier `i`). To cover all the elements of `S`, it suffices to loop on 157 | all the possible integers in [0, 2^8[, then `nbR` is not useful. Still 158 | to mimic [[Tol](#Tol)], we change a bit the way to construct `e`. 159 | 160 | We then have 161 | 162 | ```txt 163 | ψ[0] <- 0 164 | inR[0] <- true 165 | 166 | for i in [1, 2^8[: 167 | ψ[i] <- 0 168 | inR[i] <- false 169 | 170 | k <- 0 171 | for i in [0, 2^8[: 172 | if not inR[i]: 173 | k <- k + 1 174 | ψ[i] <- 1 << (k - 1) 175 | inR[i] <- true 176 | for j in [0, 2^8[: 177 | if inR[j]: 178 | fogid <- S[i].val[j] // remember that g.id = g.val[0], then f.val[g.id] = f.val[g.val[0]] 179 | ψ[fogid] <- ψ[i] + ψ[j] // + equal xor in this context 180 | inR[fogid] <- true 181 | ``` 182 | 183 | Since we know that we will run through all the function, we can change 184 | the "`for i in [0, 2^8[`" into a while loop on `k` and with a few minor 185 | changes, we get the [[Tol](#Tol)] algorithm. 186 | 187 | #### Algorithm in [[Tol](#Tol)] 188 | 189 | We just rewrite the algorithm 190 | 191 | ``` 192 | ψ[0] <- 0 193 | inR[0] <- true 194 | 195 | for i in [1, 2^8[: 196 | ψ[i] <- 0 197 | inR[i] <- false 198 | 199 | k <- 0 // in the article, k = j 200 | while k != 8: 201 | i <- 1 202 | while inR[i]: 203 | i <- i + 1 204 | 205 | k <- k + 1 206 | ψ[i] = 1 << (k - 1) 207 | inR[i] <- true 208 | for j in [1, 2^8[: 209 | if inR[j]: 210 | fogid <- S[i].val[j] // in the article, fogid = m, but we keep the previous notation 211 | ψ[fogid] <- ψ[i] + ψ[j] 212 | inR[fogid] <- true 213 | ``` 214 | 215 | Note that the index `i` in the while loop seems to be initialized only 216 | one time to `1` outside of the loop. This gives 217 | 218 | ``` 219 | ψ[0] <- 0 220 | inR[0] <- true 221 | 222 | for i in [1, 2^8[: 223 | ψ[i] <- 0 224 | inR[i] <- false 225 | 226 | k <- 0 // in the article, k = j 227 | i <- 1 228 | while k != 8: 229 | while inR[i]: 230 | i <- i + 1 231 | 232 | k <- k + 1 233 | ψ[i] = 1 << (k - 1) 234 | inR[i] <- true 235 | for j in [1, 2^8[: 236 | if inR[j]: 237 | fogid <- S[i].val[j] // in the article, fogid = m, but we keep the previous notation 238 | ψ[fogid] <- ψ[i] + ψ[j] 239 | inR[fogid] <- true 240 | ``` 241 | 242 | This is what is implemented in 243 | [`approximateencoding.hpp`](src/approximateencoding.hpp), computing the 244 | inverse of `ψ` at the same time. 245 | 246 | ## Relation between affine parasites, Section 3.2 247 | 248 | We choose to not use the linear dependencies that may be found between the 249 | affine encodings and directly use the result of Section 3.3 on each independent 250 | encodings. We, however, need to implement part of Section 3.2 for a result in 251 | Section 3.3. 252 | 253 | #### Find `L[i,j,x]` and `c[i,j,x]` 254 | 255 | Remember that `y[i)` and `y[j]` are 8-bit length. Then, the matrix 256 | `M(L[i,j,0])` which represents `L[i,j,0]` has 64 entries in `GF(2)` and 257 | `c[i,j,x]` 8 entries. We know `y[i]` and `y[j]` by their values. 258 | 259 | Implementing an algorithm to solve a tiny linear system on GF(2) is not 260 | the only way to get `M(L[i,j,0])` and `c[i,j,0]`. This requires an 261 | inversion of an 8×8 matrix over GF(2) (which needs around `O(n^3)` with 262 | classical algorithm) constructed by requiring at least 8 times `y[j]` to 263 | get an invertible matrix (see 264 | [wiki](https://en.wikipedia.org/wiki/General_linear_group#Over_finite_fields) 265 | to estimate probability), and a bit more to get the non-linear part. We 266 | propose an algorithm that works in almost `O(2^n)`, but when `n=8`, we 267 | are just below the crossing point. 268 | 269 | We will use the inverse of the function in order to recover the unknown 270 | parts. Let us begin with `c[i,j,0]`: we have `y[i](y[j]^(-1)(0), 0, 0, 271 | 0) = c[i,j,0]`. Let us consider in the following that the functions `yi` 272 | and `yj` output a bit-vector. Then, we can write `y[i](x[0], 0, 0, 0) = 273 | y[j](x[0], 0, 0, 0) * M(L[i,j,0]) + c[i,j,0]`. Like the computation of 274 | `c[i,j,0]`, we can choose specific values of `y[j](x[0], 0, 0, 0)`. 275 | Choosing values such that `y[j](x, 0, 0, 0) = 2^k`, where `k` is a power 276 | of 2 less than `2^n`, allows to find the row coefficients of a line. For 277 | example, to find the last line of `M(L[i,j,0])`, it suffices to find `x` 278 | such that `y[j](x, 0, 0, 0) = 0b00…01`, that is `x = y[j]^(-1)(1)`. The 279 | previous line is found by setting `x = y[j]^(-1)(2)`. We then recover 280 | `M(L[i,j,0])` and `c[i,j,0]` only with evaluation of functions, instead 281 | of solving linear system. This is what is implemented in 282 | [`relationencoding.hpp`](src/relationencoding.hpp). 283 | 284 | ## Recovering the affine parasites, Section 3.3 285 | 286 | ### About `Q[0]`'s linear part 287 | 288 | #### Computation of the set B 289 | 290 | We adapt a bit the list B of the possible β, as computed in the `find_B()` 291 | function in the following [Sage](https://www.sagemath.org/) file. Indeed, the 292 | `α[i,j]` come from the `MixColumn` coefficients, `β` takes its values in `B 293 | = {'7b', 'a4', '8d', '8c', '46', 'f5', '06', '02', 'f6', '8f', 'f7', '03'}`. In 294 | the article, `B` contains more values since the authors consider the set of 295 | `(α[i,x] * α[j,y]) / (α[i,y] * α[j,x])`, where `x ≠ y` and `(i, j, x, y)` in 296 | `[0, 4)^4`. In the set we describe, we only consider `(α[i,x] * α[j,y]) / 297 | (α[i,y] * α[j,x])`, where `x ≠ y` and `(i, j, x, y)` in `[0, 4)^2 × 298 | [0, 2)^2`. Note that in this set, there is no square, on the contrary to the set 299 | described in the original article. 300 | 301 | ```py 302 | Q. = GF(2)[] 303 | P. = GF(2^8, name='x', modulus=y^8+y^4+y^3+y+1) 304 | assert((x^6+x^4+x^2+x+1) * (x^7+x+1) == x^7+x^6+1) 305 | 306 | # p is a polynomial of P 307 | def poly_to_bin(p): 308 | L = p.polynomial().coefficients(sparse=False) 309 | L.reverse() 310 | L = [0 for i in range(8 - len(L))] + L 311 | return "".join([str(i) for i in L]) 312 | 313 | # p is a polynomial of P 314 | def poly_to_hex(p): 315 | L = hex(Integer(poly_to_bin(p), 2)).split('x')[1] 316 | return "".join(["0" for i in range(2- len(L))]) + L 317 | 318 | # s is a string with just the binary representation 319 | def bin_to_poly(s): 320 | k = 7 321 | p = P(0) 322 | for i in s: 323 | p = p + GF(2)(i) * x^k 324 | k = k - 1 325 | return p 326 | 327 | # s is a string with just the hexadecimal representation 328 | def hex_to_poly(s): 329 | L = bin(Integer(s, 16)).split("b")[1] 330 | L = "".join(["0" for i in range(8 - len(L))]) + L 331 | return bin_to_poly(L) 332 | 333 | P2 = [P(0)] + [P(1)] 334 | P4 = [P(0)] + [hex_to_poly("bc")^i for i in range(1, 4)] 335 | P16 = [P(0)] + [hex_to_poly("0d")^i for i in range(1, 16)] 336 | 337 | # Assert that all the elements of B are not in a subfield of P 338 | def assert_not_in_any_subfield(B): 339 | for b in B: 340 | assert((b not in [poly_to_hex(k) for k in P2]) and (b not in [poly_to_hex(k) for k in P4]) and (b not in [poly_to_hex(k) for k in P16])) 341 | 342 | # B as it is written in the article (do not respect the FIPS notation) 343 | B_original = ['02', 'd8', '03', '6f', '04', 'bc', '06', 'b7', '05', '25', '4a', 'f8', '7f', 'c8', '64', '5f'] 344 | B = [] 345 | for i in B_original: 346 | if i[0] == '0': 347 | B.append(i) 348 | else: 349 | B.append(i[1] + i[0]) 350 | 351 | assert_not_in_any_subfield(B) 352 | 353 | # Recompute B by our own way 354 | def find_B(): 355 | B = [] 356 | L = [[('02', '03'), ('01', '02'), ('01', '01'), ('03', '01')], # should be sufficient to compute L on 3.3 357 | # [('02', '01'), ('01', '03'), ('01', '02'), ('03', '01')], 358 | # [('02', '01'), ('01', '01'), ('01', '03'), ('03', '02')], 359 | # [('03', '01'), ('02', '03'), ('01', '02'), ('01', '01')], 360 | # [('03', '01'), ('02', '01'), ('01', '03'), ('01', '02')], 361 | # [('01', '01'), ('03', '01'), ('02', '03'), ('01', '02')] 362 | ] 363 | 364 | for l in L: 365 | for (a00, a01) in l: 366 | for (a10, a11) in l: 367 | if (a00 == a10 and a01 == a11): 368 | continue 369 | B.append(poly_to_hex(hex_to_poly(a00) * hex_to_poly(a11) / (hex_to_poly(a01) * hex_to_poly(a10)))) 370 | return list(set(B)) 371 | 372 | 373 | B_sufficient = find_B() 374 | for b in B_sufficient: 375 | assert(b in B) 376 | ``` 377 | 378 | From the first phase, we know that we can assume that all encodings, the `Q[i]` 379 | and the `P[i]` are affine, especially, we will denote `Q[i](x) = A[i](x) + q[i]`. From 380 | the second phase, we have the ability to get `M(Lijx)` and `cijx`. Let consider 381 | that we want all the linear relation involving `i = 0`, we are especially able 382 | to compute `M(L[0,j,x])`. The goal here is to compute `Atilde[0](x) = A[0](Λ[𝛾](x))`. 383 | 384 | Let now consider `L[i,j,0,1] = L[i,j,0](L[i,j,1]^(-1)(x))`, then `M(L[i,j,0,1]) 385 | = M(A[0])^(-1) * M(Λ[β]) * M(A[0])`. Since `M(L[i,j,0,1])` and `M(Λ[β])` share 386 | the same characteristic polynomial: a way to identify `β` is to compute the 387 | characteristic polynomial of `M(L[i,j,0,1])`. 388 | 389 | ```py 390 | # h is a string with just the hexadecimal representation 391 | def get_matrice_multiplication(h): 392 | M = [] 393 | ph = hex_to_poly(h) 394 | 395 | s = "10000000" 396 | for i in range(0, 8): 397 | p = bin_to_poly(s) 398 | r = p * ph 399 | M.append([GF(2)(k) for k in poly_to_bin(r)]) 400 | s = "0" + s[0:7] 401 | return matrix(M) 402 | 403 | for b in find_B(): 404 | "".join([str(i) for i in get_matrice_multiplication(b).characteristic_polynomial().coefficients(sparse=False)[::-1]]) 405 | ``` 406 | 407 | Given `M(L[i,j,0,1])`, we look for `Atilde[0](x) = A[0](Λ[𝛾](x))`, where 408 | `𝛾 ≠ 0`. A way to find this function is to look for 409 | `L[i,j,x,y](Atilde[0](x)) = Atilde[0](Λ[β](x))`. Since we now the 410 | matrices of `L[i,j,x,y]`, and then `β` and therefore `Λ[β]`, we need to 411 | solve `M(Atilde[0]) * M(L[i,j,x,y]) = M(Λ[β]) * M(Atilde[0])`. To solve that, a way is to develop each product keeping 412 | the elements of `M(Atilde[0])` unknown, giving a system of 64 equations. 413 | On an example of 3×3 matrices instead of 8×8, it gives 414 | 415 | ```txt 416 | (s[0,0], s[0,1], s[0,2], s[1,0], s[1,1], s[1,2], s[2,0], s[2,1], s[2,2]) * matrix( 417 | [[L[0,0] + Λ[0,0], L[0,1] , L[0,2] , Λ[1,0] , 0 , 0 , Λ[2,0] , 0 , 0], 418 | [L[1,0] , L[1,1] + Λ[0,0], L[1,2] , 0 , Λ[1,0] , 0 , 0 , Λ[2,0] , 0], 419 | [L[2,0] , L[2,1] , L[2,2] + Λ[0,0], 0 , 0 , Λ[1,0] , 0 , 0 , Λ[2,0]], 420 | [Λ[0,1] , 0 , 0 , L[0,0] + Λ[1,1], L[0,1] , L[0,2] , Λ[2,1] , 0 , 0], 421 | [0 , Λ[0,1] , 0 , L[1,0] , L[1,1] + Λ[1,1], L[1,2] , 0 , Λ[2,1] , 0], 422 | [0 , 0 , Λ[0,1] , L[2,0] , L[2,1] , L[2,2] + Λ[1,1], 0 , 0 , Λ[2,1]], 423 | [Λ[0,2] , 0 , 0 , Λ[1,2] , 0 , 0 , L[0,0] + Λ[2,2], L[0,1] , L[0,2]], 424 | [0 , Λ[0,2] , 0 , 0 , Λ[1,2] , 0 , L[1,0] , L[1,1] + Λ[2,2], L[1,2]], 425 | [0 , 0 , Λ[0,2] , 0 , 0 , Λ[1,2] , L[2,0] , L[2,1] , L[2,2] + Λ[2,2]]]) 426 | = (s[0,0], s[0,1], s[0,2], s[1,0], s[1,1], s[1,2], s[2,0], s[2,1], s[2,2]) * S = 0. 427 | ``` 428 | 429 | We then look for the kernel of `S` and recover the matrix `Atilde[0] = 430 | matrix([[s[0,0], s[0,1], s[0,2]], [s[1,0], s[1,1], s[1,2]], [s[2,0], s[2,1], 431 | s[2,2]]])`. 432 | 433 | ## Support of Shuffled States and of Decryption 434 | 435 | The [first version](https://github.com/SideChannelMarvels/BlueGalaxyEnergy/releases/tag/v1.0.1) 436 | of BlueGalaxyEnergy tools implements BGE for encryption whiteboxes with unshuffled 437 | internal states. For the next version, we improved the tools by handling 438 | shuffled states and decryption whiteboxes, with minimal changes to the 439 | previous implementation. 440 | 441 | ### Shuffled States 442 | 443 | In order to support shuffled states, we needed to add three features : 444 | 445 | - Before the inputs generation, finding a permutation that imitates the 446 | byte propagation of an AES round. 447 | - During recovery of affine parasites (Section 3.3), manipulating the 448 | coefficient of the MixColumn. We need to find the impact of each input byte of a 449 | column on each output byte. We also need to support `β` for every characteristic 450 | polynomial of this phase, as we skip the support of some `β` that will never 451 | appear in an unshuffled state. 452 | - After the key bytes recovery, using the impact coefficient found 453 | during the affine parasites recovery to finish unshuffling the 454 | state. Even with these coefficients, we still have 16 possibilities: we 455 | use the key schedule to select the correct key. 456 | 457 | The two last features followed the Phase 4 of [[DMRP](#DMRP)] with local 458 | improvement. 459 | 460 | #### First permutation 461 | 462 | When generating the inputs for the BGE attack, we use optimized inputs (like 463 | `(x,0,0,0,x,0,0,0,x,0,0,0,x,0,0,0)`) to lower the number of inputs needed. 464 | These optimized inputs allow us to reduce by 4 the number of calls to the whitebox. 465 | Indeed, for a given round, changing one input byte will only impact the 4 466 | output bytes of the same column. By using inputs that target the four columns at 467 | once, we reduce the load by 4. However, these optimized inputs can only be used 468 | if each input byte has the expected impact on the output byte. Otherwise, an 469 | optimized input may fault the same columns twice, while another column will be 470 | unchanged. 471 | 472 | We need to find a permutation that emulates the impact of each input byte 473 | on the expected output bytes. 474 | 475 | | Input bytes (encrypt) | Input bytes (decrypt) | Impacted Output Bytes | 476 | |-----------------------|------------------------|-----------------------| 477 | | 0, 5, 10, 15 | 0, 7, 10, 13 | 0, 1, 2, 3 | 478 | | 3, 4, 9, 14 | 1, 4, 11, 14 | 4, 5, 6, 7 | 479 | | 2, 7, 8, 13 | 2, 5, 8, 15 | 8, 9, 10, 11 | 480 | | 1, 6, 11, 12 | 3, 6, 9, 12 | 12, 13, 14, 15 | 481 | 482 | For each round, we need a permutation on both the inputs and the outputs. 483 | However, for consecutive rounds, the permutation between them must be the 484 | same (both as the output of the previous round and as the input of the 485 | next round). Once we found a permutation for each state, BGE input generation 486 | is handled on a modified whitebox that applies the permutation in each round. 487 | 488 | The permutations chosen at this step are kept and will be updated when the 489 | MixColumn coefficients are known for each round. 490 | 491 | #### Support of all β and MixColumn coefficient extraction during affine parasites recovery 492 | 493 | Although columns remain mixed after the previous step, 494 | implementing all possible `β` values associated with their 495 | corresponding characteristic polynomials of `L` is necessary. 496 | However, during this listing process, we discovered that 497 | two distinct `β` values share same characteristic polynomial. 498 | 499 | However, most collisions can be avoided by grouping multiple characteristic 500 | polynomials with shared characteristics. Consider two input 501 | bytes `(i, j)` in `[0, 4)^2` with `i ≠ j` and four output bytes 502 | `(u, v, x, y)` in `[0, 4)^4` that are all distinct. 503 | With this setup, we can compute the affine parasite relationships for 504 | four different `β` values: 505 | 506 | - `(α[i,u] * α[j,v]) / (α[i,v] * α[j,u])`, 507 | - `(α[i,v] * α[j,u]) / (α[i,u] * α[j,v])`, 508 | - `(α[i,x] * α[j,y]) / (α[i,y] * α[j,x])`, 509 | - `(α[i,y] * α[j,x]) / (α[i,x] * α[j,y])`. 510 | 511 | Grouping `β` in sets of four resolved most collisions on characteristic polynomials. 512 | Only one collision remained with polynomial 471, encountered 513 | in shuffled decryption whiteboxes (where `β` can be 54 or 102). Here, 514 | we iterate on the MixColumn coefficient recovery algorithm (explained later), 515 | which succeeds only with correct coefficients. 516 | 517 | In addition to recovering the correct `β` for each characteristic polynomial of `L`, 518 | we also need to recover every `α[i,x]` for every `(i, x)` in `[0, 4)^2`. 519 | We known that `α[i,x]` takes the value of the MixColumns and, for encryption: 520 | 521 | - `{α[i,0], α[i,1], α[i,2], α[i,3]} == {1, 1, 2, 3}` 522 | - `{α[0,x], α[1,x], α[2,x], α[3,x]} == {1, 1, 2, 3}` 523 | 524 | (for decryption, use the decryption MixColumns coefficients, which are `{9, 11, 13, 14}`). 525 | When we recover a `β` value, we can limit the possible coefficients associated with each 526 | `α[i,x]`. If we find the value 527 | `β0 = (α0 * α1) / (α2 * α3) = (α[i,x] * α[j,y]) / (α[i,y] * α[j,x])`, then we know that 528 | `α[i,x]` is either `α0` or `α1`. to determine the correct value, we resolve 529 | the same system a second time with the following relationship of affine parasites: 530 | 531 | - `(α[i,u] * α[j,y]) / (α[i,y] * α[j,u])`, 532 | - `(α[i,y] * α[j,u]) / (α[i,u] * α[j,y])`, 533 | - `(α[i,x] * α[j,v]) / (α[i,v] * α[j,x])`, 534 | - `(α[i,v] * α[j,x]) / (α[i,x] * α[j,v])`. 535 | 536 | With this system, we can find 537 | `β1 = (α0 * α4) / (α5 * α3) = (α[i,x] * α[j,v]) / (α[i,v] * α[j,x])`. 538 | This allows us to select `α[i,x]` that exists in both 539 | the set `{α0, α1}` and the set `{α0, α4}`. However, the 540 | coefficients of the encryption MixColumn have twice the coefficient 1. 541 | This means it's possible to have `α1` and `a4` both equal to 1, 542 | while the expected `α[i,x] == α0` has 543 | another value. As this is the only corner case, if the intersection of 544 | `{α0, α1}` and `{α0, α4}` returns two values, and one of them is 1, we select 545 | the other value. 546 | 547 | By solving this equation, we can retrieve all the MixColumn 548 | coefficients for the input bytes `(i, j)`. We then repeat the same algorithm 549 | to find the coefficients associated with the two other input bytes. 550 | 551 | The script [precomputeBetaTable.sage](src/precomputeBetaTable.sage) precomputes 552 | all possible characteristic polynomials, along with their associated `β` 553 | values and coefficients. 554 | 555 | #### Final unshuffling of the internal state 556 | 557 | For each round and its columns, the previous steps allowed us to retrieve the MixColumn coefficients. 558 | We also know which bytes interact with each column. 559 | By associating these coefficients with the permutation chosen in the first step, 560 | we can determine the MixColumn coefficient for each input byte impacting each output byte. 561 | 562 | Out of the 576 possible permutations (`4! * 4!`) for a round's column, 563 | only 4 will have coefficients positioned correctly to resemble a MixColumn. 564 | For each round's column, if we pick a random byte and place it first, only one permutation remains. 565 | 566 | After eliminating all impossible permutations for each round's column, we still 567 | have 6144 possible permutations for a given round (`4^4 * 4!`). This is because 568 | we still don't know the relative order of bytes within each column, and the 569 | relative order of columns within the round. 570 | 571 | At this step, [[DMRP](#DMRP)] proposes iterating through the 6144 possibilities. 572 | However, we can eliminate more incompatible permutations by using the next round, reducing the number 573 | of possibilities to 16 without yet using the keyschedule. 574 | 575 | To achieve this, we choose one input byte from round N and place it first. 576 | Its column is considered the first of the inputs, and the byte itself is the first within the column. 577 | This constraint fixes four bytes in the round's outputs. 578 | 579 | With these four output bytes fixed, we move to round N+1. 580 | Due to the ShiftRow operation, each fixed byte will occupy a different column in round N+1, 581 | further fixing the order of both columns and bytes within each column. 582 | This completely defines the permutation between N and N+1, 583 | as well as the output permutation for N+1. We can repeat this process for subsequent rounds. 584 | 585 | For the input permutation of round N, we can perform the reverse process with all output bytes now fixed. 586 | By the end of this algorithm, only 16 permutations remain, each with a distinct byte at the first position of the first round input. 587 | 588 | To find the correct permutation, we iterate through each one, extract the associated roundkeys, 589 | and verify if they correspond to the actual AES key. For AES-128 and AES-256, 590 | this increases the minimum required number of rounds by 1. 591 | 592 | ### Support of decryption 593 | 594 | To support decryption whiteboxes, we need to rearrange the AES steps. 595 | 596 | During encrypt, the round operations order is as follows: 597 | 598 | - Xor the round key 599 | - Perform the SBox 600 | - Perform the ShiftRow 601 | - Perform the MixColumn 602 | 603 | The last round differs with: 604 | 605 | - Xor the round key 606 | - Perform the SBox 607 | - Perform the ShiftRow 608 | - Xor the last round key 609 | 610 | To achieve a similar structure for decryption, we consider the following AES order. 611 | 612 | First round: 613 | 614 | - Xor the last round key 615 | - Perform the InvShiftRow 616 | - Perform the InvSBox 617 | - Xor the round key 618 | - Perform the InvMixColumn 619 | 620 | Intermediary rounds: 621 | 622 | - Perform the InvShiftRow 623 | - Perform the InvSBox 624 | - Xor the round key 625 | - Perform the InvMixColumn 626 | 627 | Last round: 628 | 629 | - Perform the InvShiftRow 630 | - Perform the InvSBox 631 | - Xor the round key 632 | 633 | Due to internal properties of AES steps, we can rearrange the rounds as : 634 | 635 | First round: 636 | - Xor the last round key 637 | - Perform the InvSBox 638 | - Perform the InvShiftRow 639 | - Perform the InvMixColumn 640 | 641 | Intermediary rounds: 642 | 643 | - Xor the InvMixColumn of the previous round key 644 | - Perform the InvSBox 645 | - Perform the InvShiftRow 646 | - Perform the InvMixColumn 647 | 648 | Last round: 649 | 650 | - Xor the InvMixColumn of the previous round key 651 | - Perform the InvSBox 652 | - Perform the InvShiftRow 653 | - Xor the round key 654 | 655 | With this final representation, we get a similar structure for encryption and 656 | decryption, with each operation replaced by its inverse. The main difference lies 657 | in the requirement to perform an InvMixColumn on most round keys during decryption. 658 | Consequently, we need to perform a MixColumn on all keys recovered through the BGE 659 | attack before feeding them into the key schedule to find the decryption key. 660 | 661 | While most steps of the BGE attack can be readily adapted for decryption, we 662 | discovered that Proposition 3 of [[BGE](#BGE)] no longer holds true when the 663 | SBox is replaced with it inverse. 664 | 665 | Proposition 3 states that: 666 | 667 | ```txt 668 | There exist unique pairs (δi, ci) i=0,...,3 of elements in GF(2^8), δi being non-zero, such that 669 | 670 | Ptilde0 : x -> (InvSbox ◦ Λδ0 ◦ Atilde0^{-1} ) ( y0(x, ‘00’, ‘00’, ‘00’) ⊕ c0) 671 | Ptilde1 : x -> (InvSbox ◦ Λδ1 ◦ Atilde0^{-1} ) ( y0(‘00’, x, ‘00’, ‘00’) ⊕ c1) 672 | Ptilde2 : x -> (InvSbox ◦ Λδ2 ◦ Atilde0^{-1} ) ( y0(‘00’, ‘00’, x, ‘00’) ⊕ c2) 673 | Ptilde3 : x -> (InvSbox ◦ Λδ3 ◦ Atilde0^{-1} ) ( y0(‘00’, ‘00’, ‘00’, x) ⊕ c3) 674 | 675 | are affine mappings. Any pair (δi, ci) can be computed with time complexity 2^{24}. 676 | Moreover, those mappings are exactly Ptildei = Pi(x) ⊕ ki. 677 | ``` 678 | 679 | Replacing InvSbox with SBox during the decryption whitebox still yields unique 680 | `ci i=0,...,3` values that create affine mappings for each Ptildei. 681 | However, with these `ci` values, any non-zero `δi` value also produces an affine mapping. 682 | 683 | We hypothesize that this discrepancy arises from the SBox's internal structure. 684 | Notably, the SBox can be represented as a composition of two functions: 685 | an affine mapping (`aff_1f_63`) 686 | and the multiplicative inverse operation within the finite field GF(2^8) (MultInv). 687 | Symbolically, this is expressed as `SBox = aff_1f_63 ◦ MultInv` and 688 | its inverse as `InvSbox = MultInv ◦ aff_1f_63^{-1}`. 689 | 690 | Proposition 3 is valid for encryption whiteboxes because the affine mapping `aff_1f_63` 691 | is positioned between `MultInv` and `Λδi`. However, in the decryption setting, 692 | `aff_1f_63` no longer occupies the same relative position. 693 | 694 | When the correct `ci` values are used, all operations between the `SBox` 695 | and its inverse within the `y0` function become multiplications within GF(2^8). 696 | Since multiplication in this field is commutative, the two `MultInv` operations 697 | (one inside `y0` and the other within `Sbox`) effectively cancel each other out. 698 | 699 | Consequently, the entire composition lacks any multiplicative inverse operations, 700 | rendering all resulting compositions affine mappings. 701 | Unfortunately, this absence of MultInv eliminates the capability to uniquely identify the correct `δi` values. 702 | 703 | While we cannot determine the exact `δi` values due to the limitations mentioned earlier, 704 | we still have 255 possible candidates. 705 | 706 | The MixColumn coefficients provide us with the following relationship for a given output byte `x`: 707 | `δ0 * α[0,x] = δ1 * α[1,x] = δ2 * α[2,x] = δ3 * α[3,x]` 708 | Furthermore, fixing `δ0` to an arbitrary value for a specific output byte `x` 709 | uniquely defines the corresponding `Ptildei`, which are also shared with the other output bytes in the same column. 710 | Therefore, for a given column, we only have 255 possibilities, 711 | which can be enumerated by iterating through all possible values of `δ0` for the first output byte. 712 | 713 | To identify the correct `δ0` value, we leverage the key extraction equation. 714 | In encryption whiteboxes, the key can be extracted by composing a `Ptilde` mapping 715 | from round N+1 with the corresponding `Q` mapping from round N. 716 | This composition should result in an affine function with a linear coefficient of 1. 717 | 718 | The composition depends on both the `δ0` value of the `Ptilde` mapping for a specific column in round N+1 719 | and the `δ0` value of the `Q` mapping for a column in round N. 720 | Since we lack the exact values, we need to analyze all possible combinations. 721 | With 255 possibilities for each `δ0`, this amounts to `255 * 255 ≈ 2^{16}` possible combinations. 722 | 723 | Through this analysis, we identified only one pair of `δ0` values that yields 724 | an affine mapping with a linear coefficient of 1. 725 | This unique pair represents the correct `δ0` values for both the `Ptilde` and `Q` mappings. 726 | 727 | The brute-force approach requiring `2^{16}` iterations only needs to be performed once for a given decryption whitebox. 728 | Once completed, we know the `δ0` values for a specific column in round N and a column in round N+1. 729 | Due to the ShiftRow operation, each `Q` mapping from round N's column will be paired with a `Ptilde` from a different column in round N+1. 730 | 731 | Therefore, we only need to repeat the 255-iteration brute force for each remaining pair of columns between rounds N and N+1. 732 | Notably, by iteratively processing subsequent rounds (N+2, N+3, ...) in order, we'll always have a fixed `δ0` value for the `Q` mapping. 733 | 734 | Based on this observation, we note that the overall complexity of the BGE attack on decryption whiteboxes is lower than that of encryption whiteboxes. 735 | 736 | ## Bibliography 737 | 738 | [BGE] Olivier Billet, Henri Gilbert and Charaf 739 | Ech-Chatbi, Cryptanalysis of a White Box AES Implementation, SAC 2004. 740 | [pdf](https://link.springer.com/content/pdf/10.1007%2F978-3-540-30564-4_16.pdf) 741 | 742 | [LRDMRP] Tancrède Lepoint, Matthieu Rivain, Yoni De Mulder, 743 | Peter Roelse and Bart Preneel, Two Attacks on a White-Box AES 744 | Implementation, SAC 2013. [pdf](https://link.springer.com/content/pdf/10.1007%2F978-3-662-43414-7_14.pdf) 745 | 746 | [Tol] Ludo Tolhuizen, Improved cryptanalysis of an AES 747 | implementation. 33rd WIC Symposium on Information Theory in the Benelux, 748 | 2012. [pdf](https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.913.5807&rep=rep1&type=pdf) 749 | 750 | [DMRP] Yoni De Mulder, Peter Roelse and Bart Preneel, 751 | Revisiting the BGE Attack on a White-Box AES Implementation, 752 | Cryptology ePrint Archive, 2013. 753 | [pdf](http://eprint.iacr.org/2013/450.pdf) 754 | --------------------------------------------------------------------------------