1 | # |
---|
2 | # test.py : Functions used for testing the modules |
---|
3 | # |
---|
4 | # Part of the Python Cryptography Toolkit |
---|
5 | # |
---|
6 | # Distribute and use freely; there are no restrictions on further |
---|
7 | # dissemination and usage except those imposed by the laws of your |
---|
8 | # country of residence. This software is provided "as is" without |
---|
9 | # warranty of fitness for use or suitability for any purpose, express |
---|
10 | # or implied. Use at your own risk or not at all. |
---|
11 | # |
---|
12 | |
---|
13 | __revision__ = "$Id: test.py,v 1.16 2004/08/13 22:24:18 akuchling Exp $" |
---|
14 | |
---|
15 | import binascii |
---|
16 | import string |
---|
17 | import testdata |
---|
18 | |
---|
19 | from Crypto.Cipher import * |
---|
20 | |
---|
21 | def die(string): |
---|
22 | import sys |
---|
23 | print '***ERROR: ', string |
---|
24 | # sys.exit(0) # Will default to continuing onward... |
---|
25 | |
---|
26 | def print_timing (size, delta, verbose): |
---|
27 | if verbose: |
---|
28 | if delta == 0: |
---|
29 | print 'Unable to measure time -- elapsed time too small' |
---|
30 | else: |
---|
31 | print '%.2f K/sec' % (size/delta) |
---|
32 | |
---|
33 | def exerciseBlockCipher(cipher, verbose): |
---|
34 | import string, time |
---|
35 | try: |
---|
36 | ciph = eval(cipher) |
---|
37 | except NameError: |
---|
38 | print cipher, 'module not available' |
---|
39 | return None |
---|
40 | print cipher+ ':' |
---|
41 | str='1' # Build 128K of test data |
---|
42 | for i in xrange(0, 17): |
---|
43 | str=str+str |
---|
44 | if ciph.key_size==0: ciph.key_size=16 |
---|
45 | password = 'password12345678Extra text for password'[0:ciph.key_size] |
---|
46 | IV = 'Test IV Test IV Test IV Test'[0:ciph.block_size] |
---|
47 | |
---|
48 | if verbose: print ' ECB mode:', |
---|
49 | obj=ciph.new(password, ciph.MODE_ECB) |
---|
50 | if obj.block_size != ciph.block_size: |
---|
51 | die("Module and cipher object block_size don't match") |
---|
52 | |
---|
53 | text='1234567812345678'[0:ciph.block_size] |
---|
54 | c=obj.encrypt(text) |
---|
55 | if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
56 | text='KuchlingKuchling'[0:ciph.block_size] |
---|
57 | c=obj.encrypt(text) |
---|
58 | if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
59 | text='NotTodayNotEver!'[0:ciph.block_size] |
---|
60 | c=obj.encrypt(text) |
---|
61 | if (obj.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
62 | |
---|
63 | start=time.time() |
---|
64 | s=obj.encrypt(str) |
---|
65 | s2=obj.decrypt(s) |
---|
66 | end=time.time() |
---|
67 | if (str!=s2): |
---|
68 | die('Error in resulting plaintext from ECB mode') |
---|
69 | print_timing(256, end-start, verbose) |
---|
70 | del obj |
---|
71 | |
---|
72 | if verbose: print ' CFB mode:', |
---|
73 | obj1=ciph.new(password, ciph.MODE_CFB, IV) |
---|
74 | obj2=ciph.new(password, ciph.MODE_CFB, IV) |
---|
75 | start=time.time() |
---|
76 | ciphertext=obj1.encrypt(str[0:65536]) |
---|
77 | plaintext=obj2.decrypt(ciphertext) |
---|
78 | end=time.time() |
---|
79 | if (plaintext!=str[0:65536]): |
---|
80 | die('Error in resulting plaintext from CFB mode') |
---|
81 | print_timing(64, end-start, verbose) |
---|
82 | del obj1, obj2 |
---|
83 | |
---|
84 | if verbose: print ' CBC mode:', |
---|
85 | obj1=ciph.new(password, ciph.MODE_CBC, IV) |
---|
86 | obj2=ciph.new(password, ciph.MODE_CBC, IV) |
---|
87 | start=time.time() |
---|
88 | ciphertext=obj1.encrypt(str) |
---|
89 | plaintext=obj2.decrypt(ciphertext) |
---|
90 | end=time.time() |
---|
91 | if (plaintext!=str): |
---|
92 | die('Error in resulting plaintext from CBC mode') |
---|
93 | print_timing(256, end-start, verbose) |
---|
94 | del obj1, obj2 |
---|
95 | |
---|
96 | if verbose: print ' PGP mode:', |
---|
97 | obj1=ciph.new(password, ciph.MODE_PGP, IV) |
---|
98 | obj2=ciph.new(password, ciph.MODE_PGP, IV) |
---|
99 | start=time.time() |
---|
100 | ciphertext=obj1.encrypt(str) |
---|
101 | plaintext=obj2.decrypt(ciphertext) |
---|
102 | end=time.time() |
---|
103 | if (plaintext!=str): |
---|
104 | die('Error in resulting plaintext from PGP mode') |
---|
105 | print_timing(256, end-start, verbose) |
---|
106 | del obj1, obj2 |
---|
107 | |
---|
108 | if verbose: print ' OFB mode:', |
---|
109 | obj1=ciph.new(password, ciph.MODE_OFB, IV) |
---|
110 | obj2=ciph.new(password, ciph.MODE_OFB, IV) |
---|
111 | start=time.time() |
---|
112 | ciphertext=obj1.encrypt(str) |
---|
113 | plaintext=obj2.decrypt(ciphertext) |
---|
114 | end=time.time() |
---|
115 | if (plaintext!=str): |
---|
116 | die('Error in resulting plaintext from OFB mode') |
---|
117 | print_timing(256, end-start, verbose) |
---|
118 | del obj1, obj2 |
---|
119 | |
---|
120 | def counter(length=ciph.block_size): |
---|
121 | return length * 'a' |
---|
122 | |
---|
123 | if verbose: print ' CTR mode:', |
---|
124 | obj1=ciph.new(password, ciph.MODE_CTR, counter=counter) |
---|
125 | obj2=ciph.new(password, ciph.MODE_CTR, counter=counter) |
---|
126 | start=time.time() |
---|
127 | ciphertext=obj1.encrypt(str) |
---|
128 | plaintext=obj2.decrypt(ciphertext) |
---|
129 | end=time.time() |
---|
130 | if (plaintext!=str): |
---|
131 | die('Error in resulting plaintext from CTR mode') |
---|
132 | print_timing(256, end-start, verbose) |
---|
133 | del obj1, obj2 |
---|
134 | |
---|
135 | # Test the IV handling |
---|
136 | if verbose: print ' Testing IV handling' |
---|
137 | obj1=ciph.new(password, ciph.MODE_CBC, IV) |
---|
138 | plaintext='Test'*(ciph.block_size/4)*3 |
---|
139 | ciphertext1=obj1.encrypt(plaintext) |
---|
140 | obj1.IV=IV |
---|
141 | ciphertext2=obj1.encrypt(plaintext) |
---|
142 | if ciphertext1!=ciphertext2: |
---|
143 | die('Error in setting IV') |
---|
144 | |
---|
145 | # Test keyword arguments |
---|
146 | obj1=ciph.new(key=password) |
---|
147 | obj1=ciph.new(password, mode=ciph.MODE_CBC) |
---|
148 | obj1=ciph.new(mode=ciph.MODE_CBC, key=password) |
---|
149 | obj1=ciph.new(IV=IV, mode=ciph.MODE_CBC, key=password) |
---|
150 | |
---|
151 | return ciph |
---|
152 | |
---|
153 | def exerciseStreamCipher(cipher, verbose): |
---|
154 | import string, time |
---|
155 | try: |
---|
156 | ciph = eval(cipher) |
---|
157 | except (NameError): |
---|
158 | print cipher, 'module not available' |
---|
159 | return None |
---|
160 | print cipher + ':', |
---|
161 | str='1' # Build 128K of test data |
---|
162 | for i in xrange(0, 17): |
---|
163 | str=str+str |
---|
164 | key_size = ciph.key_size or 16 |
---|
165 | password = 'password12345678Extra text for password'[0:key_size] |
---|
166 | |
---|
167 | obj1=ciph.new(password) |
---|
168 | obj2=ciph.new(password) |
---|
169 | if obj1.block_size != ciph.block_size: |
---|
170 | die("Module and cipher object block_size don't match") |
---|
171 | if obj1.key_size != ciph.key_size: |
---|
172 | die("Module and cipher object key_size don't match") |
---|
173 | |
---|
174 | text='1234567812345678Python' |
---|
175 | c=obj1.encrypt(text) |
---|
176 | if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
177 | text='B1FF I2 A R3A11Y |<00L D00D!!!!!' |
---|
178 | c=obj1.encrypt(text) |
---|
179 | if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
180 | text='SpamSpamSpamSpamSpamSpamSpamSpamSpam' |
---|
181 | c=obj1.encrypt(text) |
---|
182 | if (obj2.decrypt(c)!=text): die('Error encrypting "'+text+'"') |
---|
183 | |
---|
184 | start=time.time() |
---|
185 | s=obj1.encrypt(str) |
---|
186 | str=obj2.decrypt(s) |
---|
187 | end=time.time() |
---|
188 | print_timing(256, end-start, verbose) |
---|
189 | del obj1, obj2 |
---|
190 | |
---|
191 | return ciph |
---|
192 | |
---|
193 | def TestStreamModules(args=['arc4', 'XOR'], verbose=1): |
---|
194 | import sys, string |
---|
195 | args=map(string.lower, args) |
---|
196 | |
---|
197 | if 'arc4' in args: |
---|
198 | # Test ARC4 stream cipher |
---|
199 | arc4=exerciseStreamCipher('ARC4', verbose) |
---|
200 | if (arc4!=None): |
---|
201 | for entry in testdata.arc4: |
---|
202 | key,plain,cipher=entry |
---|
203 | key=binascii.a2b_hex(key) |
---|
204 | plain=binascii.a2b_hex(plain) |
---|
205 | cipher=binascii.a2b_hex(cipher) |
---|
206 | obj=arc4.new(key) |
---|
207 | ciphertext=obj.encrypt(plain) |
---|
208 | if (ciphertext!=cipher): |
---|
209 | die('ARC4 failed on entry '+`entry`) |
---|
210 | |
---|
211 | if 'xor' in args: |
---|
212 | # Test XOR stream cipher |
---|
213 | XOR=exerciseStreamCipher('XOR', verbose) |
---|
214 | if (XOR!=None): |
---|
215 | for entry in testdata.xor: |
---|
216 | key,plain,cipher=entry |
---|
217 | key=binascii.a2b_hex(key) |
---|
218 | plain=binascii.a2b_hex(plain) |
---|
219 | cipher=binascii.a2b_hex(cipher) |
---|
220 | obj=XOR.new(key) |
---|
221 | ciphertext=obj.encrypt(plain) |
---|
222 | if (ciphertext!=cipher): |
---|
223 | die('XOR failed on entry '+`entry`) |
---|
224 | |
---|
225 | |
---|
226 | def TestBlockModules(args=['aes', 'arc2', 'des', 'blowfish', 'cast', 'des3', |
---|
227 | 'idea', 'rc5'], |
---|
228 | verbose=1): |
---|
229 | import string |
---|
230 | args=map(string.lower, args) |
---|
231 | if 'aes' in args: |
---|
232 | ciph=exerciseBlockCipher('AES', verbose) # AES |
---|
233 | if (ciph!=None): |
---|
234 | if verbose: print ' Verifying against test suite...' |
---|
235 | for entry in testdata.aes: |
---|
236 | key,plain,cipher=entry |
---|
237 | key=binascii.a2b_hex(key) |
---|
238 | plain=binascii.a2b_hex(plain) |
---|
239 | cipher=binascii.a2b_hex(cipher) |
---|
240 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
241 | ciphertext=obj.encrypt(plain) |
---|
242 | if (ciphertext!=cipher): |
---|
243 | die('AES failed on entry '+`entry`) |
---|
244 | for i in ciphertext: |
---|
245 | if verbose: print hex(ord(i)), |
---|
246 | if verbose: print |
---|
247 | |
---|
248 | for entry in testdata.aes_modes: |
---|
249 | mode, key, plain, cipher, kw = entry |
---|
250 | key=binascii.a2b_hex(key) |
---|
251 | plain=binascii.a2b_hex(plain) |
---|
252 | cipher=binascii.a2b_hex(cipher) |
---|
253 | obj=ciph.new(key, mode, **kw) |
---|
254 | obj2=ciph.new(key, mode, **kw) |
---|
255 | ciphertext=obj.encrypt(plain) |
---|
256 | if (ciphertext!=cipher): |
---|
257 | die('AES encrypt failed on entry '+`entry`) |
---|
258 | for i in ciphertext: |
---|
259 | if verbose: print hex(ord(i)), |
---|
260 | if verbose: print |
---|
261 | |
---|
262 | plain2=obj2.decrypt(ciphertext) |
---|
263 | if plain2!=plain: |
---|
264 | die('AES decrypt failed on entry '+`entry`) |
---|
265 | for i in plain2: |
---|
266 | if verbose: print hex(ord(i)), |
---|
267 | if verbose: print |
---|
268 | |
---|
269 | |
---|
270 | if 'arc2' in args: |
---|
271 | ciph=exerciseBlockCipher('ARC2', verbose) # Alleged RC2 |
---|
272 | if (ciph!=None): |
---|
273 | if verbose: print ' Verifying against test suite...' |
---|
274 | for entry in testdata.arc2: |
---|
275 | key,plain,cipher=entry |
---|
276 | key=binascii.a2b_hex(key) |
---|
277 | plain=binascii.a2b_hex(plain) |
---|
278 | cipher=binascii.a2b_hex(cipher) |
---|
279 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
280 | ciphertext=obj.encrypt(plain) |
---|
281 | if (ciphertext!=cipher): |
---|
282 | die('ARC2 failed on entry '+`entry`) |
---|
283 | for i in ciphertext: |
---|
284 | if verbose: print hex(ord(i)), |
---|
285 | print |
---|
286 | |
---|
287 | if 'blowfish' in args: |
---|
288 | ciph=exerciseBlockCipher('Blowfish',verbose)# Bruce Schneier's Blowfish cipher |
---|
289 | if (ciph!=None): |
---|
290 | if verbose: print ' Verifying against test suite...' |
---|
291 | for entry in testdata.blowfish: |
---|
292 | key,plain,cipher=entry |
---|
293 | key=binascii.a2b_hex(key) |
---|
294 | plain=binascii.a2b_hex(plain) |
---|
295 | cipher=binascii.a2b_hex(cipher) |
---|
296 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
297 | ciphertext=obj.encrypt(plain) |
---|
298 | if (ciphertext!=cipher): |
---|
299 | die('Blowfish failed on entry '+`entry`) |
---|
300 | for i in ciphertext: |
---|
301 | if verbose: print hex(ord(i)), |
---|
302 | if verbose: print |
---|
303 | |
---|
304 | if 'cast' in args: |
---|
305 | ciph=exerciseBlockCipher('CAST', verbose) # CAST-128 |
---|
306 | if (ciph!=None): |
---|
307 | if verbose: print ' Verifying against test suite...' |
---|
308 | for entry in testdata.cast: |
---|
309 | key,plain,cipher=entry |
---|
310 | key=binascii.a2b_hex(key) |
---|
311 | plain=binascii.a2b_hex(plain) |
---|
312 | cipher=binascii.a2b_hex(cipher) |
---|
313 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
314 | ciphertext=obj.encrypt(plain) |
---|
315 | if (ciphertext!=cipher): |
---|
316 | die('CAST failed on entry '+`entry`) |
---|
317 | for i in ciphertext: |
---|
318 | if verbose: print hex(ord(i)), |
---|
319 | if verbose: print |
---|
320 | |
---|
321 | if 0: |
---|
322 | # The full-maintenance test; it requires 4 million encryptions, |
---|
323 | # and correspondingly is quite time-consuming. I've disabled |
---|
324 | # it; it's faster to compile block/cast.c with -DTEST and run |
---|
325 | # the resulting program. |
---|
326 | a = b = '\x01\x23\x45\x67\x12\x34\x56\x78\x23\x45\x67\x89\x34\x56\x78\x9A' |
---|
327 | |
---|
328 | for i in range(0, 1000000): |
---|
329 | obj = cast.new(b, cast.MODE_ECB) |
---|
330 | a = obj.encrypt(a[:8]) + obj.encrypt(a[-8:]) |
---|
331 | obj = cast.new(a, cast.MODE_ECB) |
---|
332 | b = obj.encrypt(b[:8]) + obj.encrypt(b[-8:]) |
---|
333 | |
---|
334 | if a!="\xEE\xA9\xD0\xA2\x49\xFD\x3B\xA6\xB3\x43\x6F\xB8\x9D\x6D\xCA\x92": |
---|
335 | if verbose: print 'CAST test failed: value of "a" doesn\'t match' |
---|
336 | if b!="\xB2\xC9\x5E\xB0\x0C\x31\xAD\x71\x80\xAC\x05\xB8\xE8\x3D\x69\x6E": |
---|
337 | if verbose: print 'CAST test failed: value of "b" doesn\'t match' |
---|
338 | |
---|
339 | if 'des' in args: |
---|
340 | # Test/benchmark DES block cipher |
---|
341 | des=exerciseBlockCipher('DES', verbose) |
---|
342 | if (des!=None): |
---|
343 | # Various tests taken from the DES library packaged with Kerberos V4 |
---|
344 | obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_ECB) |
---|
345 | s=obj.encrypt('Now is t') |
---|
346 | if (s!=binascii.a2b_hex('3fa40e8a984d4815')): |
---|
347 | die('DES fails test 1') |
---|
348 | obj=des.new(binascii.a2b_hex('08192a3b4c5d6e7f'), des.MODE_ECB) |
---|
349 | s=obj.encrypt('\000\000\000\000\000\000\000\000') |
---|
350 | if (s!=binascii.a2b_hex('25ddac3e96176467')): |
---|
351 | die('DES fails test 2') |
---|
352 | obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_CBC, |
---|
353 | binascii.a2b_hex('1234567890abcdef')) |
---|
354 | s=obj.encrypt("Now is the time for all ") |
---|
355 | if (s!=binascii.a2b_hex('e5c7cdde872bf27c43e934008c389c0f683788499a7c05f6')): |
---|
356 | die('DES fails test 3') |
---|
357 | obj=des.new(binascii.a2b_hex('0123456789abcdef'), des.MODE_CBC, |
---|
358 | binascii.a2b_hex('fedcba9876543210')) |
---|
359 | s=obj.encrypt("7654321 Now is the time for \000\000\000\000") |
---|
360 | if (s!=binascii.a2b_hex("ccd173ffab2039f4acd8aefddfd8a1eb468e91157888ba681d269397f7fe62b4")): |
---|
361 | die('DES fails test 4') |
---|
362 | del obj,s |
---|
363 | |
---|
364 | # R. Rivest's test: see http://theory.lcs.mit.edu/~rivest/destest.txt |
---|
365 | x=binascii.a2b_hex('9474B8E8C73BCA7D') |
---|
366 | for i in range(0, 16): |
---|
367 | obj=des.new(x, des.MODE_ECB) |
---|
368 | if (i & 1): x=obj.decrypt(x) |
---|
369 | else: x=obj.encrypt(x) |
---|
370 | if x!=binascii.a2b_hex('1B1A2DDB4C642438'): |
---|
371 | die("DES fails Rivest's test") |
---|
372 | |
---|
373 | if verbose: print ' Verifying against test suite...' |
---|
374 | for entry in testdata.des: |
---|
375 | key,plain,cipher=entry |
---|
376 | key=binascii.a2b_hex(key) |
---|
377 | plain=binascii.a2b_hex(plain) |
---|
378 | cipher=binascii.a2b_hex(cipher) |
---|
379 | obj=des.new(key, des.MODE_ECB) |
---|
380 | ciphertext=obj.encrypt(plain) |
---|
381 | if (ciphertext!=cipher): |
---|
382 | die('DES failed on entry '+`entry`) |
---|
383 | for entry in testdata.des_cbc: |
---|
384 | key, iv, plain, cipher=entry |
---|
385 | key, iv, cipher=binascii.a2b_hex(key),binascii.a2b_hex(iv),binascii.a2b_hex(cipher) |
---|
386 | obj1=des.new(key, des.MODE_CBC, iv) |
---|
387 | obj2=des.new(key, des.MODE_CBC, iv) |
---|
388 | ciphertext=obj1.encrypt(plain) |
---|
389 | if (ciphertext!=cipher): |
---|
390 | die('DES CBC mode failed on entry '+`entry`) |
---|
391 | |
---|
392 | if 'des3' in args: |
---|
393 | ciph=exerciseBlockCipher('DES3', verbose) # Triple DES |
---|
394 | if (ciph!=None): |
---|
395 | if verbose: print ' Verifying against test suite...' |
---|
396 | for entry in testdata.des3: |
---|
397 | key,plain,cipher=entry |
---|
398 | key=binascii.a2b_hex(key) |
---|
399 | plain=binascii.a2b_hex(plain) |
---|
400 | cipher=binascii.a2b_hex(cipher) |
---|
401 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
402 | ciphertext=obj.encrypt(plain) |
---|
403 | if (ciphertext!=cipher): |
---|
404 | die('DES3 failed on entry '+`entry`) |
---|
405 | for i in ciphertext: |
---|
406 | if verbose: print hex(ord(i)), |
---|
407 | if verbose: print |
---|
408 | for entry in testdata.des3_cbc: |
---|
409 | key, iv, plain, cipher=entry |
---|
410 | key, iv, cipher=binascii.a2b_hex(key),binascii.a2b_hex(iv),binascii.a2b_hex(cipher) |
---|
411 | obj1=ciph.new(key, ciph.MODE_CBC, iv) |
---|
412 | obj2=ciph.new(key, ciph.MODE_CBC, iv) |
---|
413 | ciphertext=obj1.encrypt(plain) |
---|
414 | if (ciphertext!=cipher): |
---|
415 | die('DES3 CBC mode failed on entry '+`entry`) |
---|
416 | |
---|
417 | if 'idea' in args: |
---|
418 | ciph=exerciseBlockCipher('IDEA', verbose) # IDEA block cipher |
---|
419 | if (ciph!=None): |
---|
420 | if verbose: print ' Verifying against test suite...' |
---|
421 | for entry in testdata.idea: |
---|
422 | key,plain,cipher=entry |
---|
423 | key=binascii.a2b_hex(key) |
---|
424 | plain=binascii.a2b_hex(plain) |
---|
425 | cipher=binascii.a2b_hex(cipher) |
---|
426 | obj=ciph.new(key, ciph.MODE_ECB) |
---|
427 | ciphertext=obj.encrypt(plain) |
---|
428 | if (ciphertext!=cipher): |
---|
429 | die('IDEA failed on entry '+`entry`) |
---|
430 | |
---|
431 | if 'rc5' in args: |
---|
432 | # Ronald Rivest's RC5 algorithm |
---|
433 | ciph=exerciseBlockCipher('RC5', verbose) |
---|
434 | if (ciph!=None): |
---|
435 | if verbose: print ' Verifying against test suite...' |
---|
436 | for entry in testdata.rc5: |
---|
437 | key,plain,cipher=entry |
---|
438 | key=binascii.a2b_hex(key) |
---|
439 | plain=binascii.a2b_hex(plain) |
---|
440 | cipher=binascii.a2b_hex(cipher) |
---|
441 | obj=ciph.new(key[4:], ciph.MODE_ECB, |
---|
442 | version =ord(key[0]), |
---|
443 | word_size=ord(key[1]), |
---|
444 | rounds =ord(key[2]) ) |
---|
445 | ciphertext=obj.encrypt(plain) |
---|
446 | if (ciphertext!=cipher): |
---|
447 | die('RC5 failed on entry '+`entry`) |
---|
448 | for i in ciphertext: |
---|
449 | if verbose: print hex(ord(i)), |
---|
450 | if verbose: print |
---|
451 | |
---|
452 | |
---|
453 | |
---|