-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathattackbranch.py
More file actions
executable file
·357 lines (326 loc) · 18 KB
/
attackbranch.py
File metadata and controls
executable file
·357 lines (326 loc) · 18 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
#!/usr/env/bin python3
#-*- coding: UTF-8 -*-
"""
Copyright (C) 2024 Hosein Hadipour
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
In case you use this tool please include the above copyright
information (name, contact, license)
"""
import logging
from pathlib import Path
from random import randint
logging.basicConfig(filename="minizinc-python.log", level=logging.DEBUG)
import time
import minizinc
import datetime
from argparse import ArgumentParser, RawTextHelpFormatter
import subprocess
from draw import DrawDL
# Check if gurobi_cl command is recognized
try:
subprocess.run(['gurobi_cl', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
gurobi_available = True
except FileNotFoundError:
gurobi_available = False
if gurobi_available:
print("Gurobi is available")
from diff import Diff
class DiffLin:
DL_counter = 0
def __init__(self, param) -> None:
DiffLin.DL_counter += 1
self.id = DiffLin.DL_counter
self.name = "DiffLin" + str(self.id)
self.type = "DiffLin"
self.RU = param["RU"]
self.RM = param["RM"]
self.RL = param["RL"]
self.RMU = param["RMU"]
self.RML = param["RML"]
self.WU = param["WU"]
self.WM = param["WM"]
self.WL = param["WL"]
self.offset = param["offset"]
self.branchtype = param["branchtype"]
self.RD = self.RU + self.RM + self.RL
self.cp_solver_name = param["solver"]
self.compute_differential_effect_using_gurobi = param["differential_effect"]
self.cp_solver = minizinc.Solver.lookup(self.cp_solver_name)
self.time_limit = param["timelimit"]
self.num_of_threads = param["np"]
self.mzn_file_name = None
self.output_file_name = param["output"]
self.mzn_file_name = "attackbranch.mzn"
#############################################################################################################################################
#############################################################################################################################################
# ____ _ __ ____ _ _ _ _ _
# / ___| ___ __ _ _ __ ___ | |__ / _| ___ _ __ __ _ | _ \ (_) ___ | |_ (_) _ __ __ _ _ _ (_) ___ | |__ ___ _ __
# \___ \ / _ \ / _` || '__|/ __|| '_ \ | |_ / _ \ | '__| / _` | | | | || |/ __|| __|| || '_ \ / _` || | | || |/ __|| '_ \ / _ \| '__|
# ___) || __/| (_| || | | (__ | | | | | _|| (_) || | | (_| | | |_| || |\__ \| |_ | || | | || (_| || |_| || |\__ \| | | || __/| |
# |____/ \___| \__,_||_| \___||_| |_| |_| \___/ |_| \__,_| |____/ |_||___/ \__||_||_| |_| \__, | \__,_||_||___/|_| |_| \___||_|
# |___/
# Search for a distinguisher using MiniZinc
def search(self):
"""
Search for a distinguisher
"""
if self.time_limit != -1:
time_limit = datetime.timedelta(seconds=self.time_limit)
else:
time_limit = None
start_time = time.time()
#############################################################################################################################################
print(f"Searching a distinguisher for {self.RD} rounds of Orthros ...")
self.cp_model = minizinc.Model()
self.cp_model.add_file(self.mzn_file_name)
self.cp_inst = minizinc.Instance(solver=self.cp_solver, model=self.cp_model)
self.cp_inst["RU"] = self.RU
self.cp_inst["RM"] = self.RM
self.cp_inst["RL"] = self.RL
self.cp_inst["RMU"] = self.RMU
self.cp_inst["RML"] = self.RML
self.cp_inst["WU"] = self.WU
self.cp_inst["WM"] = self.WM
self.cp_inst["WL"] = self.WL
self.cp_inst["offset"] = self.offset
self.cp_inst["branchtype"] = self.branchtype
self.result = self.cp_inst.solve(timeout=time_limit,
processes=self.num_of_threads,
verbose=False,
# debug_output=Path("./debug_output.txt",intermediate_solutions=True),
random_seed=randint(0, 100))
# optimisation_level=2)
#############################################################################################################################################
elapsed_time = time.time() - start_time
self.attack_summary = "Time used to find a distinguisher: {:0.02f} seconds".format(elapsed_time) + "\n"
self.attack_summary += f"Solver status: {self.result.status}" + "\n"
if minizinc.Status.has_solution(self.result.status) or self.result.status == minizinc.Status.ERROR:
attack_summary, self.upper_trail, self.lower_trail = self.parse_solution()
self.attack_summary += attack_summary
if self.compute_differential_effect_using_gurobi:
diff_effect = self.compute_differential_effect()
self.attack_summary += "Differential effect over EU: 2^({:.2f})".format(diff_effect) + "\n"
print(self.attack_summary)
draw = DrawDL(self, output_file_name=self.output_file_name)
draw.generate_distinguisher_shape()
print("-Log2(P) ~= \t{:02d}".format(self.result["PU"]))
print("-Log2(r) ~= \t{:02d}".format(self.result["CM"]))
print("-Log2(Q^2) ~= \t{:02d}".format(self.result["CL"]))
if self.compute_differential_effect_using_gurobi:
print("-Log2(DiffEffect) ~= \t{:.2f}".format(-1*diff_effect))
elif self.result.status == minizinc.Status.UNSATISFIABLE:
print("Model is unsatisfiable")
elif self.result.status == minizinc.Status.UNKNOWN:
print("Unknown error!")
else:
print("Solving process was interrupted")
def compute_differential_effect(self):
"""
Compute the differential effect over EU
"""
# Load default values
params = {"nrounds" : self.RU,
"branchtype" : self.branchtype,
"offset" : self.offset,
"mode" : 2,
"startweight" : 0,
"endweight" : 128,
"timelimit" : 3600,
"numberoftrails" : 2,
"fixedVariables" : {}}
input_diff = hex(int("".join(list(map(str, self.result["xu"][0]))), 2))[2:].zfill(32)
params["fixedVariables"][f"x_0"] = input_diff
outputdiff = hex(int("".join(list(map(str, self.result["xu"][self.RU]))), 2))[2:].zfill(32)
params["fixedVariables"][f"x_{self.RU}"] = outputdiff
UDiff = Diff(params)
UDiff.make_model()
output = UDiff.solve()
return output
#############################################################################################################################################
#############################################################################################################################################
# ____ _ _ ____ _ _ _
# | _ \ __ _ _ __ ___ ___ | |_ | |__ ___ / ___| ___ | | _ _ | |_ (_) ___ _ __
# | |_) |/ _` || '__|/ __| / _ \ | __|| '_ \ / _ \ \___ \ / _ \ | || | | || __|| | / _ \ | '_ \
# | __/| (_| || | \__ \| __/ | |_ | | | || __/ ___) || (_) || || |_| || |_ | || (_) || | | |
# |_| \__,_||_| |___/ \___| \__||_| |_| \___| |____/ \___/ |_| \__,_| \__||_| \___/ |_| |_|
# Parse the solution and print the distinguisher's specifications
def parse_solution(self):
"""
Parse the solution and print the distinguisher's specifications
"""
upper_trail = {"x": [0 for _ in range(self.RU + self.RM + 1)],
"y": [0 for _ in range(self.RU + self.RM)],
"z": [0 for _ in range(self.RU + self.RM)]}
for r in range(self.RU):
upper_trail["x"][r] = self.result["xu"][r]
upper_trail["y"][r] = self.result["yu"][r]
upper_trail["z"][r] = self.result["zu"][r]
for r in range(self.RU, self.RU + self.RM + 1):
upper_trail["x"][r] = self.result["xmu"][r - self.RU]
if r < self.RU + self.RM:
upper_trail["y"][r] = self.result["ymu"][r - self.RU]
upper_trail["z"][r] = self.result["zmu"][r - self.RU]
lower_trail = {"x": [0 for _ in range(self.RM + self.RL + 1)],
"y": [0 for _ in range(self.RM + self.RL)],
"z": [0 for _ in range(self.RM + self.RL)]}
for r in range(self.RM + 1):
lower_trail["x"][r] = self.result["xml"][r]
if r < self.RM:
lower_trail["y"][r] = self.result["yml"][r]
lower_trail["z"][r] = self.result["zml"][r]
for r in range(self.RM, self.RM + self.RL + 1):
lower_trail["x"][r] = self.result["xl"][r - self.RM]
if r < self.RM + self.RL:
lower_trail["y"][r] = self.result["yl"][r - self.RM]
lower_trail["z"][r] = self.result["zl"][r - self.RM]
input_diff = ""
input_diff += f"char inputdiff[] = \"" + hex(int("".join(list(map(str, self.result["xu"][0]))), 2))[2:].zfill(32) + "\";\n"
input_diff_middle = ""
temp = self.result["xmu"][0]
temp = [0 if x == -1 else x for x in temp]
input_diff_middle += f"char inputdiff[] = \"" + hex(int("".join(list(map(str, temp))), 2))[2:].zfill(32) + "\";\n"
output_mask_middle = ""
temp = self.result["xml"][self.RM]
temp = [0 if x == -1 else x for x in temp]
output_mask_middle += f"char outputmask[] = \"" + hex(int("".join(list(map(str, temp))), 2))[2:].zfill(32) + "\";\n"
output_mask = ""
output_mask += f"char outputmask[] = \"" + hex(int("".join(list(map(str, self.result["xl"][self.RL]))), 2))[2:].zfill(32) + "\";\n"
attack_summary = f"Attack summary:\n"
attack_summary += f"Setting: RU: {self.RU}, RM: {self.RM}, RL: {self.RL}, RMU: {self.RMU}, RML: {self.RML}, WU: {self.WU}, WM: {self.WM}, WL: {self.WL}, offset: {self.offset}, branchtype: {self.branchtype}\n"
attack_summary += "#"*50 + "\n"
attack_summary += f"input diff.: \n{input_diff}"
attack_summary += "#"*50 + "\n"
attack_summary += f"input diff. middle: \n{input_diff_middle}"
attack_summary += "#"*50 + "\n"
attack_summary += f"output mask. middle: \n{output_mask_middle}"
attack_summary += "#"*50 + "\n"
attack_summary += f"output mask: \n{output_mask}"
attack_summary += "#"*50 + "\n"
attack_summary += f"PU: {self.result['PU']}\n"
attack_summary += f"CM: {self.result['CM']}\n"
attack_summary += f"Q^2: {self.result['CL']}\n"
attack_summary += f"Number of effective S-boxes in the middle: {self.result['NASM']}\n"
attack_summary += f"Number of effective bit-positions in the middle: {self.result['CM']}\n"
attack_summary += "#"*50 + "\n"
# print the upper trail
attack_summary += "Upper trail:\n"
for r in range(self.RU + self.RM + 1):
attack_summary += f"Round {r}:\n"
attack_summary += "x{:02d} = ".format(r) + "".join(list(map(str, upper_trail["x"][r]))).replace("-1", "*") + "\n"
if r < self.RU + self.RM:
attack_summary += "y{:02d} = ".format(r) + "".join(list(map(str, upper_trail["y"][r]))).replace("-1", "*") + "\n"
attack_summary += "z{:02d} = ".format(r) + "".join(list(map(str, upper_trail["z"][r]))).replace("-1", "*") + "\n"
attack_summary += "#"*50 + "\n\n"
# print the lower trail
attack_summary += "Lower trail:\n"
for r in range(self.RM + self.RL + 1):
attack_summary += "Round {:02d}:\n".format(r)
attack_summary += "x{:02d} = ".format(r) + "".join(list(map(str, lower_trail["x"][r]))).replace("-1", "*") + "\n"
if r < self.RM + self.RL:
attack_summary += "y{:02d} = ".format(r) + "".join(list(map(str, lower_trail["y"][r]))).replace("-1", "*") + "\n"
attack_summary += "z{:02d} = ".format(r) + "".join(list(map(str, lower_trail["z"][r]))).replace("-1", "*") + "\n"
attack_summary += "#"*50 + "\n\n"
return attack_summary, upper_trail, lower_trail
#############################################################################################################################################
#############################################################################################################################################
#############################################################################################################################################
# _ _ ___ _ __
# | | | | ___ ___ _ __ |_ _| _ __ | |_ ___ _ __ / _| __ _ ___ ___
# | | | |/ __| / _ \| '__| | | | '_ \ | __|/ _ \| '__|| |_ / _` | / __|/ _ \
# | |_| |\__ \| __/| | | | | | | || |_| __/| | | _|| (_| || (__| __/
# \___/ |___/ \___||_| |___||_| |_| \__|\___||_| |_| \__,_| \___|\___|
def loadparameters(args):
'''
Extract parameters from the argument list and input file
'''
# Load default values
params = {"RU": 0,
"RM": 4,
"RL": 0,
"RMU": 0,
"RML": 0,
"WU": 1,
"WM": 1,
"WL": 1,
"offset": 4,
"branchtype": 1,
"np" : 8,
"tl" : -1,
"solver" : "ortools",
"output" : "output.tex",
"differential_effect" : 0}
# Override parameters if they are set on command line
if args.RU is not None:
params["RU"] = args.RU
if args.RM is not None:
params["RM"] = args.RM
if args.RL is not None:
params["RL"] = args.RL
if args.RMU is not None:
params["RMU"] = args.RMU
if args.RML is not None:
params["RML"] = args.RML
if args.WU is not None:
params["WU"] = args.WU
if args.WM is not None:
params["WM"] = args.WM
if args.WL is not None:
params["WL"] = args.WL
if args.offset is not None:
params["offset"] = args.offset
if args.branchtype is not None:
params["branchtype"] = args.branchtype
if args.np is not None:
params["np"] = args.np
if args.timelimit is not None:
params["timelimit"] = args.timelimit
if args.solver is not None:
params["solver"] = args.solver
if args.output is not None:
params["output"] = args.output
if args.differential_effect is not None:
params["differential_effect"] = args.differential_effect
return params
def main():
'''
Parse the arguments and start the request functionality with the provided
parameters.
'''
parser = ArgumentParser(description="This tool finds a nearly optimum boomerang"
"distinguisher for SKINNY family of block ciphers.",
formatter_class=RawTextHelpFormatter)
parser.add_argument("-RU", type=int, default=1, help="Number of rounds for EU")
parser.add_argument("-RM", type=int, default=6, help="Number of rounds in the middle")
parser.add_argument("-RL", type=int, default=0, help="Number of rounds for EL")
parser.add_argument("-offset", type=int, default=4, help="Offset for the starting round of the distinguisher")
parser.add_argument("-branchtype", type=int, default=0, help="Branch type of Orthros (0: Branch1, 1: Branch2)", choices=[0, 1])
parser.add_argument("-RMU", type=int, default=0, help="Number of rounds passed probabilistically at the beginning of EM")
parser.add_argument("-RML", type=int, default=0, help="Number of rounds passed probabilistically at the end of EM")
parser.add_argument("-WU", type=int, default=1, help="Scale of the probability transition over EU")
parser.add_argument("-WM", type=int, default=1, help="Scale of the correlation of DL distinguishers over EM ")
parser.add_argument("-WL", type=int, default=1, help="Scale of the squared correaltion of linear approximation over EL")
parser.add_argument("-de", "--differential_effect", type=int, default=0, help="Compute differential effect over EU using Gurobi", choices=[0, 1])
parser.add_argument("-np", type=int, default=8, help="Number of parallel threads")
parser.add_argument("-tl", "--timelimit", type=int, default=36000, help="Time limit in seconds")
# Fetch available solvers from MiniZinc
available_solvers = [solver_name for solver_name in minizinc.default_driver.available_solvers().keys()]
parser.add_argument("-sl", "--solver", default="cp-sat", type=str,
choices=available_solvers,
help="Choose a CP solver")
parser.add_argument("-o", "--output", default="output.tex", type=str, help="Output file name")
# Parse command line arguments and construct parameter list
args = parser.parse_args()
params = loadparameters(args)
dld = DiffLin(params)
dld.search()
if __name__ == "__main__":
main()