1 ### -*- mode: python, coding: utf-8 -*-
3 ### Test symmetric algorithms
5 ### (c) 2019 Straylight/Edgeware
8 ###----- Licensing notice ---------------------------------------------------
10 ### This file is part of the Python interface to Catacomb.
12 ### Catacomb/Python is free software: you can redistribute it and/or
13 ### modify it under the terms of the GNU General Public License as
14 ### published by the Free Software Foundation; either version 2 of the
15 ### License, or (at your option) any later version.
17 ### Catacomb/Python is distributed in the hope that it will be useful, but
18 ### WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
20 ### General Public License for more details.
22 ### You should have received a copy of the GNU General Public License
23 ### along with Catacomb/Python. If not, write to the Free Software
24 ### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
27 ###--------------------------------------------------------------------------
34 ###--------------------------------------------------------------------------
37 def bad_key_size(ksz):
38 if isinstance(ksz, C.KeySZAny): return None
39 elif isinstance(ksz, C.KeySZRange):
40 if ksz.mod != 1: return ksz.min + 1
41 elif ksz.max != 0: return ksz.max + 1
42 elif ksz.min != 0: return ksz.min - 1
44 elif isinstance(ksz, C.KeySZSet):
45 for sz in sorted(ksz.set):
46 if sz + 1 not in ksz.set: return sz + 1
47 assert False, "That should have worked."
51 def different_key_size(ksz, sz):
52 if isinstance(ksz, C.KeySZAny): return sz + 1
53 elif isinstance(ksz, C.KeySZRange):
54 if sz > ksz.min: return sz - ksz.mod
55 elif ksz.max == 0 or sz < ksz.max: return sz + ksz.mod
57 elif isinstance(ksz, C.KeySZSet):
58 for sz1 in sorted(ksz.set):
59 if sz != sz1: return sz1
64 class HashBufferTestMixin (U.TestCase):
65 """Mixin class for testing all of the various `hash...' methods."""
67 def check_hashbuffer_hashn(me, w, bigendp, makefn, hashfn):
70 ## Check encoding an integer.
71 h0, donefn0 = makefn(w + 2)
72 hashfn(h0.hashu8(0x00), T.bytes_as_int(w, bigendp)).hashu8(w + 1)
73 h1, donefn1 = makefn(w + 2)
74 h1.hash(T.span(w + 2))
75 me.assertEqual(donefn0(), donefn1())
77 ## Check overflow detection.
79 me.assertRaises((OverflowError, ValueError),
82 def check_hashbuffer_bufn(me, w, bigendp, makefn, hashfn):
83 """Check `hashbufN'."""
85 ## Go through a number of different sizes.
86 for n in [0, 1, 7, 8, 19, 255, 12345, 65535, 123456]:
87 if n >= 1 << 8*w: continue
88 h0, donefn0 = makefn(2 + w + n)
89 hashfn(h0.hashu8(0x00), T.span(n)).hashu8(0xff)
90 h1, donefn1 = makefn(2 + w + n)
91 h1.hash(T.prep_lenseq(w, n, bigendp, True))
92 me.assertEqual(donefn0(), donefn1())
94 ## Check blocks which are too large for the length prefix.
98 me.assertRaises((ValueError, OverflowError, TypeError),
99 hashfn, h0, C.ByteString.zero(n))
101 def check_hashbuffer(me, makefn):
102 """Test the various `hash...' methods."""
105 me.check_hashbuffer_hashn(1, True, makefn, lambda h, n: h.hashu8(n))
106 me.check_hashbuffer_hashn(2, True, makefn, lambda h, n: h.hashu16(n))
107 me.check_hashbuffer_hashn(2, True, makefn, lambda h, n: h.hashu16b(n))
108 me.check_hashbuffer_hashn(2, False, makefn, lambda h, n: h.hashu16l(n))
109 if hasattr(makefn(0)[0], "hashu24"):
110 me.check_hashbuffer_hashn(3, True, makefn, lambda h, n: h.hashu24(n))
111 me.check_hashbuffer_hashn(3, True, makefn, lambda h, n: h.hashu24b(n))
112 me.check_hashbuffer_hashn(3, False, makefn, lambda h, n: h.hashu24l(n))
113 me.check_hashbuffer_hashn(4, True, makefn, lambda h, n: h.hashu32(n))
114 me.check_hashbuffer_hashn(4, True, makefn, lambda h, n: h.hashu32b(n))
115 me.check_hashbuffer_hashn(4, False, makefn, lambda h, n: h.hashu32l(n))
116 if hasattr(makefn(0)[0], "hashu64"):
117 me.check_hashbuffer_hashn(8, True, makefn, lambda h, n: h.hashu64(n))
118 me.check_hashbuffer_hashn(8, True, makefn, lambda h, n: h.hashu64b(n))
119 me.check_hashbuffer_hashn(8, False, makefn, lambda h, n: h.hashu64l(n))
122 me.check_hashbuffer_bufn(1, True, makefn, lambda h, x: h.hashbuf8(x))
123 me.check_hashbuffer_bufn(2, True, makefn, lambda h, x: h.hashbuf16(x))
124 me.check_hashbuffer_bufn(2, True, makefn, lambda h, x: h.hashbuf16b(x))
125 me.check_hashbuffer_bufn(2, False, makefn, lambda h, x: h.hashbuf16l(x))
126 if hasattr(makefn(0)[0], "hashbuf24"):
127 me.check_hashbuffer_bufn(3, True, makefn, lambda h, x: h.hashbuf24(x))
128 me.check_hashbuffer_bufn(3, True, makefn, lambda h, x: h.hashbuf24b(x))
129 me.check_hashbuffer_bufn(3, False, makefn, lambda h, x: h.hashbuf24l(x))
130 me.check_hashbuffer_bufn(4, True, makefn, lambda h, x: h.hashbuf32(x))
131 me.check_hashbuffer_bufn(4, True, makefn, lambda h, x: h.hashbuf32b(x))
132 me.check_hashbuffer_bufn(4, False, makefn, lambda h, x: h.hashbuf32l(x))
133 if hasattr(makefn(0)[0], "hashbuf64"):
134 me.check_hashbuffer_bufn(8, True, makefn, lambda h, x: h.hashbuf64(x))
135 me.check_hashbuffer_bufn(8, True, makefn, lambda h, x: h.hashbuf64b(x))
136 me.check_hashbuffer_bufn(8, False, makefn, lambda h, x: h.hashbuf64l(x))
138 ###--------------------------------------------------------------------------
139 class TestKeysize (U.TestCase):
143 ## A typical one-byte spec.
145 me.assertEqual(type(ksz), C.KeySZAny)
146 me.assertEqual(ksz.default, 20)
147 me.assertEqual(ksz.min, 0)
148 me.assertEqual(ksz.max, 0)
149 for n in [0, 12, 20, 5000]:
150 me.assertTrue(ksz.check(n))
151 me.assertEqual(ksz.best(n), n)
153 ## A typical two-byte spec. (No published algorithms actually /need/ a
154 ## two-byte key-size spec, but all of the HMAC variants use one anyway.)
155 ksz = C.sha256_hmac.keysz
156 me.assertEqual(type(ksz), C.KeySZAny)
157 me.assertEqual(ksz.default, 32)
158 me.assertEqual(ksz.min, 0)
159 me.assertEqual(ksz.max, 0)
160 for n in [0, 12, 20, 5000]:
161 me.assertTrue(ksz.check(n))
162 me.assertEqual(ksz.best(n), n)
164 ## Check construction.
166 me.assertEqual(ksz.default, 15)
167 me.assertEqual(ksz.min, 0)
168 me.assertEqual(ksz.max, 0)
169 me.assertRaises(ValueError, lambda: C.KeySZAny(-8))
170 me.assertEqual(C.KeySZAny(0).default, 0)
173 ## Note that no published algorithm uses a 16-bit `set' spec.
176 ksz = C.salsa20.keysz
177 me.assertEqual(type(ksz), C.KeySZSet)
178 me.assertEqual(ksz.default, 32)
179 me.assertEqual(ksz.min, 10)
180 me.assertEqual(ksz.max, 32)
181 me.assertEqual(set(ksz.set), set([10, 16, 32]))
182 for x, best, pad in [(9, None, 10), (10, 10, 10), (11, 10, 16),
183 (15, 10, 16), (16, 16, 16), (17, 16, 32),
184 (31, 16, 32), (32, 32, 32), (33, 32, None)]:
185 if x == best == pad: me.assertTrue(ksz.check(x))
186 else: me.assertFalse(ksz.check(x))
187 if best is None: me.assertRaises(ValueError, ksz.best, x)
188 else: me.assertEqual(ksz.best(x), best)
190 ## Check construction.
192 me.assertEqual(ksz.default, 7)
193 me.assertEqual(set(ksz.set), set([7]))
194 me.assertEqual(ksz.min, 7)
195 me.assertEqual(ksz.max, 7)
196 ksz = C.KeySZSet(7, [3, 6, 9])
197 me.assertEqual(ksz.default, 7)
198 me.assertEqual(set(ksz.set), set([3, 6, 7, 9]))
199 me.assertEqual(ksz.min, 3)
200 me.assertEqual(ksz.max, 9)
203 ## Note that no published algorithm uses a 16-bit `range' spec, or an
204 ## unbounded `range'.
207 ksz = C.rijndael.keysz
208 me.assertEqual(type(ksz), C.KeySZRange)
209 me.assertEqual(ksz.default, 32)
210 me.assertEqual(ksz.min, 4)
211 me.assertEqual(ksz.max, 32)
212 me.assertEqual(ksz.mod, 4)
213 for x, best in [(3, None), (4, 4), (5, 4),
214 (15, 12), (16, 16), (17, 16),
215 (31, 28), (32, 32), (33, 32)]:
216 if x == best: me.assertTrue(ksz.check(x))
217 else: me.assertFalse(ksz.check(x))
218 if best is None: me.assertRaises(ValueError, ksz.best, x)
219 else: me.assertEqual(ksz.best(x), best)
221 ## Check construction.
222 ksz = C.KeySZRange(28, 21, 35, 7)
223 me.assertEqual(ksz.default, 28)
224 me.assertEqual(ksz.min, 21)
225 me.assertEqual(ksz.max, 35)
226 me.assertEqual(ksz.mod, 7)
227 me.assertRaises(ValueError, C.KeySZRange, 29, 21, 35, 7)
228 me.assertRaises(ValueError, C.KeySZRange, 28, 20, 35, 7)
229 me.assertRaises(ValueError, C.KeySZRange, 28, 21, 34, 7)
230 me.assertRaises(ValueError, C.KeySZRange, 28, -7, 35, 7)
231 me.assertRaises(ValueError, C.KeySZRange, 28, 35, 21, 7)
232 me.assertRaises(ValueError, C.KeySZRange, 35, 21, 28, 7)
233 me.assertRaises(ValueError, C.KeySZRange, 21, 28, 35, 7)
235 def test_conversions(me):
236 me.assertEqual(C.KeySZ.fromec(256), 128)
237 me.assertEqual(C.KeySZ.fromschnorr(256), 128)
238 me.assertEqual(round(C.KeySZ.fromdl(2958.6875)), 128)
239 me.assertEqual(round(C.KeySZ.fromif(2958.6875)), 128)
240 me.assertEqual(C.KeySZ.toec(128), 256)
241 me.assertEqual(C.KeySZ.toschnorr(128), 256)
242 me.assertEqual(C.KeySZ.todl(128), 2958.6875)
243 me.assertEqual(C.KeySZ.toif(128), 2958.6875)
245 ###--------------------------------------------------------------------------
246 class TestCipher (T.GenericTestMixin):
247 """Test basic symmetric ciphers."""
249 def _test_cipher(me, ccls):
251 ## Check the class properties.
252 me.assertEqual(type(ccls.name), str)
253 me.assertTrue(isinstance(ccls.keysz, C.KeySZ))
254 me.assertEqual(type(ccls.blksz), int)
256 ## Check round-tripping.
257 k = T.span(ccls.keysz.default)
258 iv = T.span(ccls.blksz)
263 except ValueError: can_setiv = False
267 c0 = enc.encrypt(m[0:57])
269 c1 = enc.encrypt(m[57:189])
272 except ValueError: can_bdry = False
276 c2 = enc.encrypt(m[189:253])
278 me.assertEqual(len(c0) + len(c1) + len(c2), len(m))
279 me.assertEqual(m0, m[0:57])
280 me.assertEqual(m1, m[57:189])
281 me.assertEqual(m2, m[189:253])
283 ## Check the `enczero' and `deczero' methods.
285 me.assertEqual(dec.decrypt(c3), C.ByteString.zero(32))
287 me.assertEqual(enc.encrypt(m4), C.ByteString.zero(32))
289 ## Check that ciphers which support a `boundary' operation actually
293 if can_setiv: dec.setiv(iv)
294 m01 = dec.decrypt(c0 + c1)
295 me.assertEqual(m01, m[0:189])
297 ## Check that the boundary actually does something.
300 if can_setiv: dec.setiv(iv)
301 m012 = dec.decrypt(c0 + c1 + c2)
302 me.assertNotEqual(m012, m)
304 ## Check that bad key lengths are rejected.
305 badlen = bad_key_size(ccls.keysz)
306 if badlen is not None: me.assertRaises(ValueError, ccls, T.span(badlen))
308 TestCipher.generate_testcases((name, C.gcciphers[name]) for name in
309 ["des-ecb", "rijndael-cbc", "twofish-cfb", "serpent-ofb",
310 "blowfish-counter", "rc4", "seal", "salsa20/8", "shake128-xof"])
312 ###--------------------------------------------------------------------------
313 class BaseTestHash (HashBufferTestMixin):
314 """Base class for testing hash functions."""
316 def check_hash(me, hcls, need_bufsz = True):
318 Check hash class HCLS.
320 If NEED_BUFSZ is false, then don't insist that HCLS have working `bufsz',
321 `name', or `hashsz' attributes. This test is mostly reused for MACs,
322 which don't have these attributes.
324 ## Check the class properties.
326 me.assertEqual(type(hcls.name), str)
327 me.assertEqual(type(hcls.bufsz), int)
328 me.assertEqual(type(hcls.hashsz), int)
330 ## Set some initial values.
332 h = hcls().hash(m).done()
334 ## Check that hash length comes out right.
335 if need_bufsz: me.assertEqual(len(h), hcls.hashsz)
337 ## Check that we get the same answer if we split the message up.
338 me.assertEqual(h, hcls().hash(m[0:73]).hash(m[73:131]).done())
340 ## Check the `check' method.
341 me.assertTrue(hcls().hash(m).check(h))
342 me.assertFalse(hcls().hash(m).check(h ^ len(h)*C.bytes("aa")))
344 ## Check the menagerie of random hashing methods.
348 me.check_hashbuffer(mkhash)
350 class TestHash (BaseTestHash, T.GenericTestMixin):
351 """Test hash functions."""
352 def _test_hash(me, hcls): me.check_hash(hcls, need_bufsz = True)
354 TestHash.generate_testcases((name, C.gchashes[name]) for name in
355 ["md5", "sha", "whirlpool", "sha256", "sha512/224", "sha3-384", "shake256",
358 ###--------------------------------------------------------------------------
359 class TestMessageAuthentication (BaseTestHash, T.GenericTestMixin):
360 """Test message authentication codes."""
362 def _test_mac(me, mcls):
364 ## Check the MAC properties.
365 me.assertEqual(type(mcls.name), str)
366 me.assertTrue(isinstance(mcls.keysz, C.KeySZ))
367 me.assertEqual(type(mcls.tagsz), int)
370 k = T.span(mcls.keysz.default)
372 me.check_hash(key, need_bufsz = False)
374 ## Check that bad key lengths are rejected.
375 badlen = bad_key_size(mcls.keysz)
376 if badlen is not None: me.assertRaises(ValueError, mcls, T.span(badlen))
378 TestMessageAuthentication.generate_testcases \
379 ((name, C.gcmacs[name]) for name in
380 ["sha-hmac", "rijndael-cmac", "twofish-pmac1", "kmac128"])
382 class TestPoly1305 (HashBufferTestMixin):
383 """Check the Poly1305 one-time message authentication function."""
385 def test_poly1305(me):
387 ## Check the MAC properties.
388 me.assertEqual(C.poly1305.name, "poly1305")
389 me.assertEqual(type(C.poly1305.keysz), C.KeySZSet)
390 me.assertEqual(C.poly1305.keysz.default, 16)
391 me.assertEqual(set(C.poly1305.keysz.set), set([16]))
392 me.assertEqual(C.poly1305.tagsz, 16)
393 me.assertEqual(C.poly1305.masksz, 16)
395 ## Set some initial values.
400 t = key(u).hash(m).done()
402 ## Check the key properties.
403 me.assertEqual(len(t), 16)
405 ## Check that we get the same answer if we split the message up.
406 me.assertEqual(t, key(u).hash(m[0:86]).hash(m[86:149]).done())
408 ## Check the `check' method.
409 me.assertTrue(key(u).hash(m).check(t))
410 me.assertFalse(key(u).hash(m).check(t ^ 16*C.bytes("cc")))
412 ## Check the menagerie of random hashing methods.
416 me.check_hashbuffer(mkhash)
418 ## Check that we can't complete hashing without a mask.
419 me.assertRaises(ValueError, key().hash(m).done)
422 h0 = key().hash(m[0:96])
423 h1 = key().hash(m[96:117])
424 me.assertEqual(t, key(u).concat(h0, h1).hash(m[117:149]).done())
426 me.assertRaises(TypeError, key().concat, key1().hash(m[0:96]), h1)
427 me.assertRaises(TypeError, key().concat, h0, key1().hash(m[96:117]))
428 me.assertRaises(ValueError, key().concat, key().hash(m[0:93]), h1)
430 ###--------------------------------------------------------------------------
431 class TestHLatin (U.TestCase):
432 """Test the `hsalsa20' and `hchacha20' functions."""
435 kk = [T.span(sz) for sz in [32]]
439 for fn in [C.hsalsa208_prf, C.hsalsa2012_prf, C.hsalsa20_prf,
440 C.hchacha8_prf, C.hchacha12_prf, C.hchacha20_prf]:
443 me.assertEqual(len(h), 32)
444 me.assertRaises(ValueError, fn, bad_k, n)
445 me.assertRaises(ValueError, fn, k, bad_n)
447 ###--------------------------------------------------------------------------
448 class TestKeccak (HashBufferTestMixin):
449 """Test the Keccak-p[1600, n] sponge function."""
453 ## Make a state and feed some stuff into it.
454 m0 = T.bin("some initial string")
455 m1 = T.bin("awesome follow-up string")
457 me.assertEqual(st0.nround, 24)
460 ## Make another step with a different round count.
461 st1 = C.Keccak1600(23)
463 me.assertNotEqual(st0.extract(32), st1.extract(32))
465 ## Check error conditions.
467 me.assertRaises(ValueError, st0.extract, 201)
469 me.assertRaises(ValueError, st0.mix, T.span(201))
471 def check_shake(me, xcls, c, done_matches_xof = True):
473 Test the SHAKE and cSHAKE XOFs.
475 This is also used for testing KMAC, but that sets DONE_MATCHES_XOF false
476 to indicate that the XOF output is range-separated from the fixed-length
477 outputs (unlike the basic SHAKE functions).
480 ## Check the hash attributes.
482 me.assertEqual(x.rate, 200 - c)
483 me.assertEqual(x.buffered, 0)
484 me.assertEqual(x.state, "absorb")
486 ## Set some initial values.
487 func = T.bin("TESTXOF")
488 perso = T.bin("catacomb-python test")
490 h0 = xcls().hash(m).done(193)
491 me.assertEqual(len(h0), 193)
492 h1 = xcls(func = func, perso = perso).hash(m).done(193)
493 me.assertEqual(len(h1), 193)
494 me.assertNotEqual(h0, h1)
496 ## Check input and output in pieces, and the state machine.
497 if done_matches_xof: h = h0
498 else: h = xcls().hash(m).xof().get(len(h0))
499 x = xcls().hash(m[0:76]).hash(m[76:167]).xof()
500 me.assertEqual(h, x.get(98) + x.get(95))
503 x = xcls().hash(m).xof()
504 me.assertEqual(x.mask(m), C.ByteString(m) ^ C.ByteString(h[0:len(m)]))
506 ## Check the `check' method.
507 me.assertTrue(xcls().hash(m).check(h0))
508 me.assertFalse(xcls().hash(m).check(h1))
510 ## Check the menagerie of random hashing methods.
512 x = xcls(func = func, perso = perso)
513 return x, lambda: x.done(100 - x.rate//2)
514 me.check_hashbuffer(mkhash)
516 ## Check the state machine tracking.
517 x = xcls(); me.assertEqual(x.state, "absorb")
518 x.hash(m); me.assertEqual(x.state, "absorb")
520 h = xx.done(100 - x.rate//2)
521 me.assertEqual(xx.state, "dead")
522 me.assertRaises(ValueError, xx.done, 1)
523 me.assertRaises(ValueError, xx.get, 1)
524 me.assertEqual(x.state, "absorb")
525 me.assertRaises(ValueError, x.get, 1)
526 x.xof(); me.assertEqual(x.state, "squeeze")
527 me.assertRaises(ValueError, x.done, 1)
529 yy = x.copy(); me.assertEqual(yy.state, "squeeze")
531 def test_shake128(me): me.check_shake(C.Shake128, 32)
532 def test_shake256(me): me.check_shake(C.Shake256, 64)
534 def check_kmac(me, mcls, c):
536 me.check_shake(lambda func = None, perso = T.bin(""):
537 mcls(k, perso = perso),
538 c, done_matches_xof = False)
540 def test_kmac128(me): me.check_kmac(C.KMAC128, 32)
541 def test_kmac256(me): me.check_kmac(C.KMAC256, 64)
543 ###--------------------------------------------------------------------------
544 class TestPRP (T.GenericTestMixin):
545 """Test pseudorandom permutations (PRPs)."""
547 def _test_prp(me, pcls):
549 ## Check the PRP properties.
550 me.assertEqual(type(pcls.name), str)
551 me.assertTrue(isinstance(pcls.keysz, C.KeySZ))
552 me.assertEqual(type(pcls.blksz), int)
554 ## Check round-tripping.
555 k = T.span(pcls.keysz.default)
557 m = T.span(pcls.blksz)
559 me.assertEqual(len(c), pcls.blksz)
560 me.assertEqual(m, key.decrypt(c))
562 ## Check that bad key lengths are rejected.
563 badlen = bad_key_size(pcls.keysz)
564 if badlen is not None: me.assertRaises(ValueError, pcls, T.span(badlen))
566 ## Check that bad blocks are rejected.
567 badblk = T.span(pcls.blksz + 1)
568 me.assertRaises(ValueError, key.encrypt, badblk)
569 me.assertRaises(ValueError, key.decrypt, badblk)
571 TestPRP.generate_testcases((name, C.gcprps[name]) for name in
572 ["desx", "blowfish", "rijndael"])
574 ###----- That's all, folks --------------------------------------------------
576 if __name__ == "__main__": U.main()