Skip to content

Commit e516dd3

Browse files
committed
Initial upload.
1 parent f7e25c2 commit e516dd3

File tree

2 files changed

+255
-1
lines changed

2 files changed

+255
-1
lines changed

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
1-
# DecodingAttack
1+
Demonstration of the decoding attack on SIMON-32. See P. Zajac: Upper bounds on the complexity of algebraic cryptanalysis of ciphers with a low multiplicative complexity.
2+
3+
Use (at your risk) from SAGE command line interface:
4+
5+
load('simon.sage')
6+
decoding(y1,y2,maxtries)

simon.sage

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
#####################################################################
2+
#
3+
# Demonstration of the decoding attack on SIMON-32,
4+
# see
5+
# P. Zajac: Upper bounds on the complexity of algebraic
6+
# cryptanalysis of ciphers with a low multiplicative complexity
7+
#
8+
# (C) 2016, Pavol Zajac
9+
#
10+
# Can be used for research and education,
11+
# with attribution and citation of the article if applicable.
12+
#
13+
#####################################################################
14+
15+
## Simon left-hand side matrix
16+
def getSimonMatrix4r():
17+
M=matrix(GF(2),32,3*32)
18+
19+
#F1 part
20+
for j in range(16):
21+
#in1:
22+
M[(j-8)%16,3*j+0] = 1 #S8 k0
23+
#in2:
24+
M[(j-1)%16,3*j+1] = 1 #S1 k0
25+
#out
26+
M[(j-2)%16,3*j+2] = 1 #S2 k0
27+
M[(j+3)%16,3*j+2] = 1 #S-3 k0
28+
M[(j+4)%16,3*j+2] = 1 #S-4 k0
29+
M[16+(j+6)%16,3*j+2] = 1 #S-6 k1
30+
M[16+(j+8)%16,3*j+2] = 1 #S-8 k1
31+
32+
#F2 part
33+
for j in range(16):
34+
#in1:
35+
M[(j-5)%16,3*16+3*j+0] = 1 #S8 S-3 k0
36+
M[(j-4)%16,3*16+3*j+0] = 1 #S8 S-4 k0
37+
M[16+(j-2)%16,3*16+3*j+0] = 1 #S8 S-6 k1
38+
M[16+(j+0)%16,3*16+3*j+0] = 1 #S8 S-8 k1
39+
#in2:
40+
M[(j+2)%16,3*16+3*j+1] = 1 #S1 S-3 k0
41+
M[(j+3)%16,3*16+3*j+1] = 1 #S1 S-4 k0
42+
M[16+(j+5)%16,3*16+3*j+1] = 1 #S1 S-6 k1
43+
M[16+(j+7)%16,3*16+3*j+1] = 1 #S1 S-8 k1
44+
45+
#out
46+
M[(j+1)%16,3*16+3*j+2] = 1 #S-1 k0
47+
M[(j+2)%16,3*16+3*j+2] = 1 #S-2 k0
48+
M[16+(j+3)%16,3*16+3*j+2] = 1 #S-3 k1
49+
M[16+(j+6)%16,3*16+3*j+2] = 1 #S-6 k1
50+
51+
return M
52+
##########################################
53+
54+
## Simon right-hand side sets
55+
def getSimonConstant(j):
56+
if j < 16: #for first part
57+
#if (j == 1) or (j == 13) or (j == 14):
58+
#0 is index in y1, y2 = 0
59+
if j in [1, 13, 14, 0]:
60+
return [0,0,0]
61+
else:
62+
return [0,0,1]
63+
else:
64+
j -= 16
65+
c1 = 0 if (j-8)%16 in [1,13,14,0] else 1
66+
c2 = 0 if (j-1)%16 in [1,13,14,0] else 1
67+
c3 = 1 if j in [0,1,3,15,2] else 0
68+
return [c1,c2,c3]
69+
70+
#S is a set of rhs, c is a hex constant
71+
def addBitConstant(S, c):
72+
S2 = copy(S)
73+
for i in range(S2.nrows()):
74+
for j in range(S2.ncols()):
75+
S2[i,j] = S2[i,j]+c[j]
76+
return S2
77+
78+
def rot(x, i=1):
79+
return ((x << i) & 0xffff ) | (x >> (16 - i))
80+
81+
def bit(x, i):
82+
return ((x >> i) & 1 )
83+
84+
def get_y_const(y1, y2):
85+
#print y1, y2
86+
87+
y1_F_y2 = (y1.__xor__(rot(y2,8)&rot(y2,1))).__xor__(rot(y2,2))
88+
consts = []
89+
#F1 - constant y1+F(y2) is added to result of AND
90+
for i in range(16):
91+
consts += [ [0,0,bit(y1_F_y2,i)] ]
92+
#F2 - y1+F(y2) is added to input 1 rotated by 8
93+
# - y1+F(y2) is added to input 2 rotated by 1
94+
# - y1+F(y2) is added to output rotated by 2
95+
# - y2 is added to output
96+
in1 = rot(y1_F_y2,8)
97+
in2 = rot(y1_F_y2,1)
98+
out = y2.__xor__(rot(y1_F_y2,2))
99+
for i in range(16):
100+
consts += [ [bit(in1,i),bit(in2,i),bit(out,i)] ]
101+
return consts
102+
103+
def getSimonConstants4r(y1, y2):
104+
SxAND=matrix(GF(2),[[0,0,0],[0,1,0],[1,0,0],[1,1,1]])
105+
without_y = [addBitConstant(SxAND,getSimonConstant(i)) for i in range(0,32)]
106+
y_const = get_y_const(y1, y2)
107+
with_y = [addBitConstant(without_y[i],y_const[i]) for i in range(0,32)]
108+
return block_diagonal_matrix(with_y,subdivide=False)
109+
##########################################
110+
111+
## MRHS transformation to decoding
112+
113+
#M - LHS matrix
114+
#S - RHS (block) matrix
115+
#n - variables, m - blocks, k - block size
116+
def getMatrixU( M, S, n, m, k ):
117+
#get systematic matrix + PCM
118+
C=LinearCode(M)
119+
G=C.generator_matrix_systematic()
120+
H=C.parity_check_matrix()
121+
122+
#compute V
123+
R = S*H.transpose()
124+
125+
#construct T to transform V to U
126+
T0 = matrix(GF(2), k-1, k)
127+
for i in range(k-1):
128+
T0[i,0] = 1
129+
T0[i,i+1] = 1
130+
131+
T1 = matrix(GF(2), 1, k)
132+
T1[0,0] = 1
133+
134+
# m blocks T0
135+
T2 = block_diagonal_matrix([T0 for i in range(0,m)],subdivide=False)
136+
137+
# m blocks T1
138+
T3 = block_matrix(1,m,[T1 for i in range(0,m)],subdivide=False)
139+
140+
T = block_matrix(2,1,[T3,T2])
141+
142+
#final parity check matrix, first row is syndrome
143+
Q=(T*R)
144+
145+
return Q
146+
##########################################
147+
148+
## Final check of solution
149+
def extract_solution(sols, up, S):
150+
#sols := [ sol ]
151+
# sol := (ix1, ix2, count, syndrome)
152+
#up := [rev]
153+
#rev := (block, solnum, row)
154+
posx = [0] * 32
155+
s = up[3*32][2]
156+
for sol in sols:
157+
if (sol[0] != '.'):
158+
posx[ up[sol[0]][0] ] = up[sol[0]][1]
159+
s += up[sol[0]][2]
160+
if (sol[1] != '.'):
161+
posx[ up[sol[1]][0] ] = up[sol[1]][1]
162+
s += up[sol[1]][2]
163+
for ix, val in enumerate(sol[3]):
164+
if val == 1:
165+
posx[ up[ix][0] ] = up[ix][1]
166+
s += up[ix][2]
167+
168+
print sol
169+
print s
170+
print posx
171+
rhs = []
172+
for i in range(32):
173+
rhs += list(S[4*i+posx[i]][3*i:3*i+3])
174+
print rhs
175+
##########################################
176+
177+
178+
## Decoding algorithm
179+
def hw(vec):
180+
return sum(1 for x in vec if x==1)
181+
182+
def final_check_conflicts(lst):
183+
syndrome = lst[3]
184+
pos1 = lst[0]
185+
pos2 = lst[1]
186+
187+
#syndrome cannot have 1 in positions x[0] and x[1]
188+
if pos1 != '.' and (syndrome[pos1%32] == 1 or syndrome[pos1%32+32] == 1): return False
189+
if pos2 != '.' and (syndrome[pos2%32] == 1 or syndrome[pos2%32+32] == 1): return False
190+
return True
191+
192+
#adapted version of Lee-Brickel decoding
193+
def decoding(y1 = 0, y2 = 0, max_try = 10000):
194+
M=getSimonMatrix4r()
195+
S=getSimonConstants4r(y1,y2)
196+
U=getMatrixU(M, S, 32, 32, 4)
197+
198+
u=U.rows()[0]
199+
200+
count = 0
201+
while count < max_try:
202+
203+
count += 1
204+
205+
#apply random permutation to blocks, and separate columns
206+
P=Permutations(32).random_element()
207+
208+
up = [None] * (3*32+1)
209+
for i in range(32):
210+
R = Permutations(3).random_element()
211+
for j in range(3):
212+
up[32*j + i] = (P[i]-1, R[j], U.rows()[1+(P[i]-1)*3+R[j]-1])
213+
up[3*32] = ('.', '.', u)
214+
215+
#create echelon form, last block has each vector from a different block
216+
UP = matrix(GF(2),map(lambda x: x[2], up)).transpose().echelon_form().transpose()
217+
if UP[0:64,0:64] != identity_matrix(64):
218+
#print count
219+
continue
220+
221+
#create a list of potential solutions including at most 2 vectors from the last block
222+
l1 = [[i,UP.rows()[i]] for i in range(64,96)]
223+
uhat = UP.rows()[96]
224+
225+
lst = [('.','.',0,uhat)]+[(x[0],'.',1,uhat+x[1]) for x in l1]+[(x[0],y[0],2,uhat+x[1]+y[1]) for x in l1 for y in l1 if y[0] > x[0]]
226+
227+
#check special position 64
228+
special = filter(lambda x: x[3][64] == 0, lst)
229+
230+
#filter by weight,
231+
weight_ok = filter(lambda x: hw(x[3])+x[2] <= 32, special)
232+
# then by conflicts within the first two blocks,
233+
no_conflicts12 = filter(lambda x: sum(1 if x[3][i]==1 and x[3][32+i]==1 else 0 for i in range(32)) < 1, weight_ok)
234+
# and finally conflicts with the chosen vectors from the last block
235+
no_conflicts = filter(final_check_conflicts, no_conflicts12)
236+
237+
if len(no_conflicts) > 0:
238+
#print UP.str()
239+
#print no_conflicts
240+
extract_solution(no_conflicts, up, S)
241+
242+
return count
243+
244+
return count
245+
##########################################
246+
247+
#time results = [decoding(1,1) for i in range(100)]
248+
#import random;
249+
#time results = [decoding(Integer(random.randint(0,0x10000)),Integer(random.randint(0,0x10000)),1500) for i in range(100)]

0 commit comments

Comments
 (0)