Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download
323 views
1
#! /usr/bin/pythonw
2
# -*- coding: utf-8 -*-
3
# Algorithm Name: Keccak
4
# Authors: Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche
5
# Implementation by Renaud Bauvin, STMicroelectronics
6
#
7
# This code, originally by Renaud Bauvin, is hereby put in the public domain.
8
# It is given as is, without any guarantee.
9
#
10
# For more information, feedback or questions, please refer to our website:
11
# http://keccak.noekeon.org/
12
13
import math
14
15
class KeccakError(Exception):
16
"""Class of error used in the Keccak implementation
17
18
Use: raise KeccakError.KeccakError("Text to be displayed")"""
19
20
def __init__(self, value):
21
self.value = value
22
def __str__(self):
23
return repr(self.value)
24
25
26
class Keccak:
27
"""
28
Class implementing the Keccak sponge function
29
"""
30
def __init__(self, b=1600):
31
"""Constructor:
32
33
b: parameter b, must be 25, 50, 100, 200, 400, 800 or 1600 (default value)"""
34
self.setB(b)
35
36
def setB(self,b):
37
"""Set the value of the parameter b (and thus w,l and nr)
38
39
b: parameter b, must be choosen among [25, 50, 100, 200, 400, 800, 1600]
40
"""
41
42
if b not in [25, 50, 100, 200, 400, 800, 1600]:
43
raise KeccakError.KeccakError('b value not supported - use 25, 50, 100, 200, 400, 800 or 1600')
44
45
# Update all the parameters based on the used value of b
46
self.b=b
47
self.w=b//25
48
self.l=int(math.log(self.w,2))
49
self.nr=12+2*self.l
50
51
# Constants
52
53
## Round constants
54
RC=[0x0000000000000001,
55
0x0000000000008082,
56
0x800000000000808A,
57
0x8000000080008000,
58
0x000000000000808B,
59
0x0000000080000001,
60
0x8000000080008081,
61
0x8000000000008009,
62
0x000000000000008A,
63
0x0000000000000088,
64
0x0000000080008009,
65
0x000000008000000A,
66
0x000000008000808B,
67
0x800000000000008B,
68
0x8000000000008089,
69
0x8000000000008003,
70
0x8000000000008002,
71
0x8000000000000080,
72
0x000000000000800A,
73
0x800000008000000A,
74
0x8000000080008081,
75
0x8000000000008080,
76
0x0000000080000001,
77
0x8000000080008008]
78
79
## Rotation offsets
80
r=[[0, 36, 3, 41, 18] ,
81
[1, 44, 10, 45, 2] ,
82
[62, 6, 43, 15, 61] ,
83
[28, 55, 25, 21, 56] ,
84
[27, 20, 39, 8, 14] ]
85
86
## Generic utility functions
87
88
def rot(self,x,n):
89
"""Bitwise rotation (to the left) of n bits considering the \
90
string of bits is w bits long"""
91
92
n = n%self.w
93
return ((x>>(self.w-n))+(x<<n))%(1<<self.w)
94
95
def enc(self,x,n):
96
"""Encode the integer x in n bits (n must be a multiple of 8)"""
97
98
if x>=2**n:
99
raise KeccakError.KeccakError('x is too big to be coded in n bits')
100
if n%8!=0:
101
raise KeccakError.KeccakError('n must be a multiple of 8')
102
return ("%%0%dX" % (2*n//8)) % (x)
103
104
def fromHexStringToLane(self, string):
105
"""Convert a string of bytes written in hexadecimal to a lane value"""
106
107
#Check that the string has an even number of characters i.e. whole number of bytes
108
if len(string)%2!=0:
109
raise KeccakError.KeccakError("The provided string does not end with a full byte")
110
111
#Perform the modification
112
temp=''
113
nrBytes=len(string)//2
114
for i in range(nrBytes):
115
offset=(nrBytes-i-1)*2
116
temp+=string[offset:offset+2]
117
return int(temp, 16)
118
119
def fromLaneToHexString(self, lane):
120
"""Convert a lane value to a string of bytes written in hexadecimal"""
121
122
laneHexBE = (("%%0%dX" % (self.w//4)) % lane)
123
#Perform the modification
124
temp=''
125
nrBytes=len(laneHexBE)//2
126
for i in range(nrBytes):
127
offset=(nrBytes-i-1)*2
128
temp+=laneHexBE[offset:offset+2]
129
return temp.upper()
130
131
def printState(self, state, info):
132
"""Print on screen the state of the sponge function preceded by \
133
string info
134
135
state: state of the sponge function
136
info: a string of characters used as identifier"""
137
138
print("Current value of state: %s" % (info))
139
for y in range(5):
140
line=[]
141
for x in range(5):
142
line.append(hex(state[x][y]))
143
print('\t%s' % line)
144
145
### Conversion functions String <-> Table (and vice-versa)
146
147
def convertStrToTable(self,string):
148
"""Convert a string of bytes to its 5×5 matrix representation
149
150
string: string of bytes of hex-coded bytes (e.g. '9A2C...')"""
151
152
#Check that input paramaters
153
if self.w%8!= 0:
154
raise KeccakError("w is not a multiple of 8")
155
if len(string)!=2*(self.b)//8:
156
raise KeccakError.KeccakError("string can't be divided in 25 blocks of w bits\
157
i.e. string must have exactly b bits")
158
159
#Convert
160
output=[[0,0,0,0,0],
161
[0,0,0,0,0],
162
[0,0,0,0,0],
163
[0,0,0,0,0],
164
[0,0,0,0,0]]
165
for x in range(5):
166
for y in range(5):
167
offset=2*((5*y+x)*self.w)//8
168
output[x][y]=self.fromHexStringToLane(string[offset:offset+(2*self.w//8)])
169
return output
170
171
def convertTableToStr(self,table):
172
"""Convert a 5×5 matrix representation to its string representation"""
173
174
#Check input format
175
if self.w%8!= 0:
176
raise KeccakError.KeccakError("w is not a multiple of 8")
177
if (len(table)!=5) or (False in [len(row)==5 for row in table]):
178
raise KeccakError.KeccakError("table must be 5×5")
179
180
#Convert
181
output=['']*25
182
for x in range(5):
183
for y in range(5):
184
output[5*y+x]=self.fromLaneToHexString(table[x][y])
185
output =''.join(output).upper()
186
return output
187
188
### Padding function
189
190
def pad(self,M, n):
191
"""Pad M with reverse-padding to reach a length multiple of n
192
193
M: message pair (length in bits, string of hex characters ('9AFC...')
194
n: length in bits (must be a multiple of 8)
195
Example: pad([60, 'BA594E0FB9EBBD30'],8) returns 'BA594E0FB9EBBD13'
196
"""
197
198
[my_string_length, my_string]=M
199
200
# Check the parameter n
201
if n%8!=0:
202
raise KeccakError.KeccakError("n must be a multiple of 8")
203
204
# Check the length of the provided string
205
if len(my_string)%2!=0:
206
#Pad with one '0' to reach correct length (don't know test
207
#vectors coding)
208
my_string=my_string+'0'
209
if my_string_length>(len(my_string)//2*8):
210
raise KeccakError.KeccakError("the string is too short to contain the number of bits announced")
211
212
#Add the bit allowing reversible padding
213
nr_bytes_filled=my_string_length//8
214
if nr_bytes_filled==len(my_string)//2:
215
#bits fill the whole package: add a byte '01'
216
my_string=my_string+"01"
217
else:
218
#there is no addition of a byte... modify the last one
219
nbr_bits_filled=my_string_length%8
220
221
#Add the leading bit
222
my_byte=int(my_string[nr_bytes_filled*2:nr_bytes_filled*2+2],16)
223
my_byte=(my_byte>>(8-nbr_bits_filled))
224
my_byte=my_byte+2**(nbr_bits_filled)
225
my_byte="%02X" % my_byte
226
my_string=my_string[0:nr_bytes_filled*2]+my_byte
227
228
#Complete my_string to reach a multiple of n bytes
229
while((8*len(my_string)//2)%n!=0):
230
my_string=my_string+'00'
231
return my_string
232
233
def Round(self,A,RCfixed):
234
"""Perform one round of computation as defined in the Keccak-f permutation
235
236
A: current state (5×5 matrix)
237
RCfixed: value of round constant to use (integer)
238
"""
239
240
#Initialisation of temporary variables
241
B=[[0,0,0,0,0],
242
[0,0,0,0,0],
243
[0,0,0,0,0],
244
[0,0,0,0,0],
245
[0,0,0,0,0]]
246
C= [0,0,0,0,0]
247
D= [0,0,0,0,0]
248
249
#Theta step
250
for x in range(5):
251
C[x] = A[x][0]^A[x][1]^A[x][2]^A[x][3]^A[x][4]
252
253
for x in range(5):
254
D[x] = C[(x-1)%5]^self.rot(C[(x+1)%5],1)
255
256
for x in range(5):
257
for y in range(5):
258
A[x][y] = A[x][y]^D[x]
259
260
#Rho and Pi steps
261
for x in range(5):
262
for y in range(5):
263
B[y][(2*x+3*y)%5] = self.rot(A[x][y], self.r[x][y])
264
265
#Chi step
266
for x in range(5):
267
for y in range(5):
268
A[x][y] = B[x][y]^((~B[(x+1)%5][y]) & B[(x+2)%5][y])
269
270
#Iota step
271
A[0][0] = A[0][0]^RCfixed
272
273
return A
274
275
def KeccakF(self,A, verbose=False):
276
"""Perform Keccak-f function on the state A
277
278
A: 5×5 matrix containing the state
279
verbose: a boolean flag activating the printing of intermediate computations
280
"""
281
282
if verbose:
283
self.printState(A,"Before first round")
284
285
for i in range(self.nr):
286
#NB: result is truncated to lane size
287
A = self.Round(A,self.RC[i]%(1<<self.w))
288
289
if verbose:
290
self.printState(A,"Satus end of round #%d/%d" % (i+1,self.nr))
291
292
return A
293
294
def Keccak(self,M,r=1024,c=576,d=0,n=1024,verbose=False):
295
"""Compute the Keccak[r,c,d] sponge function on message M
296
297
M: message pair (length in bits, string of hex characters ('9AFC...')
298
r: bitrate in bits (defautl: 1024)
299
c: capacity in bits (default: 576)
300
d: diversifier in bits (default: 0 bits)
301
n: length of output in bits (default: 1024),
302
verbose: print the details of computations(default:False)
303
"""
304
305
#Check the inputs
306
if (r<0) or (r%8!=0):
307
raise KeccakError.KeccakError('r must be a multiple of 8')
308
if (n%8!=0):
309
raise KeccakError.KeccakError('outputLength must be a multiple of 8')
310
if (d<0) or (d>255):
311
raise KeccakError.KeccakError('d must be in the range [0, 255]')
312
self.setB(r+c)
313
314
if verbose:
315
print("Create a Keccak function with (r=%d, c=%d, d=%d (i.e. w=%d))" % (r,c,d,(r+c)//25))
316
317
#Compute lane length (in bits)
318
w=(r+c)//25
319
320
# Initialisation of state
321
S=[[0,0,0,0,0],
322
[0,0,0,0,0],
323
[0,0,0,0,0],
324
[0,0,0,0,0],
325
[0,0,0,0,0]]
326
327
#Padding of messages
328
P=self.pad(M,8) + self.enc(d,8) + self.enc(r//8,8)
329
P=self.pad([8*len(P)//2,P],r)
330
331
if verbose:
332
print("String ready to be absorbed: %s (will be completed by %d x '00')" % (P, c//8))
333
334
#Absorbing phase
335
for i in range((len(P)*8//2)//r):
336
Pi=self.convertStrToTable(P[i*(2*r//8):(i+1)*(2*r//8)]+'00'*(c//8))
337
338
for y in range(5):
339
for x in range(5):
340
S[x][y] = S[x][y]^Pi[x][y]
341
S = self.KeccakF(S, verbose)
342
343
if verbose:
344
print("Value after absorption : %s" % (self.convertTableToStr(S)))
345
346
#Squeezing phase
347
Z = ''
348
outputLength = n
349
while outputLength>0:
350
string=self.convertTableToStr(S)
351
Z = Z + string[:r*2//8]
352
outputLength -= r
353
if outputLength>0:
354
S = self.KeccakF(S, verbose)
355
356
# NB: done by block of length r, could have to be cut if outputLength
357
# is not a multiple of r
358
359
if verbose:
360
print("Value after squeezing : %s" % (self.convertTableToStr(S)))
361
362
return Z[:2*n//8]
363
364