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