|
| 1 | +# Copyright (c) 2021 Pieter Wuille |
| 2 | +# Distributed under the MIT software license, see the accompanying |
| 3 | +# file COPYING or http://www.opensource.org/licenses/mit-license.php. |
| 4 | +"""Test-only pure Python RIPEMD160 implementation.""" |
| 5 | + |
| 6 | +import unittest |
| 7 | + |
| 8 | +# Message schedule indexes for the left path. |
| 9 | +ML = [ |
| 10 | + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, |
| 11 | + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, |
| 12 | + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, |
| 13 | + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, |
| 14 | + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 |
| 15 | +] |
| 16 | + |
| 17 | +# Message schedule indexes for the right path. |
| 18 | +MR = [ |
| 19 | + 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, |
| 20 | + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, |
| 21 | + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, |
| 22 | + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, |
| 23 | + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 |
| 24 | +] |
| 25 | + |
| 26 | +# Rotation counts for the left path. |
| 27 | +RL = [ |
| 28 | + 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, |
| 29 | + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, |
| 30 | + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, |
| 31 | + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, |
| 32 | + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 |
| 33 | +] |
| 34 | + |
| 35 | +# Rotation counts for the right path. |
| 36 | +RR = [ |
| 37 | + 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, |
| 38 | + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, |
| 39 | + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, |
| 40 | + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, |
| 41 | + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 |
| 42 | +] |
| 43 | + |
| 44 | +# K constants for the left path. |
| 45 | +KL = [0, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e] |
| 46 | + |
| 47 | +# K constants for the right path. |
| 48 | +KR = [0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0] |
| 49 | + |
| 50 | + |
| 51 | +def fi(x, y, z, i): |
| 52 | + """The f1, f2, f3, f4, and f5 functions from the specification.""" |
| 53 | + if i == 0: |
| 54 | + return x ^ y ^ z |
| 55 | + elif i == 1: |
| 56 | + return (x & y) | (~x & z) |
| 57 | + elif i == 2: |
| 58 | + return (x | ~y) ^ z |
| 59 | + elif i == 3: |
| 60 | + return (x & z) | (y & ~z) |
| 61 | + elif i == 4: |
| 62 | + return x ^ (y | ~z) |
| 63 | + else: |
| 64 | + assert False |
| 65 | + |
| 66 | + |
| 67 | +def rol(x, i): |
| 68 | + """Rotate the bottom 32 bits of x left by i bits.""" |
| 69 | + return ((x << i) | ((x & 0xffffffff) >> (32 - i))) & 0xffffffff |
| 70 | + |
| 71 | + |
| 72 | +def compress(h0, h1, h2, h3, h4, block): |
| 73 | + """Compress state (h0, h1, h2, h3, h4) with block.""" |
| 74 | + # Left path variables. |
| 75 | + al, bl, cl, dl, el = h0, h1, h2, h3, h4 |
| 76 | + # Right path variables. |
| 77 | + ar, br, cr, dr, er = h0, h1, h2, h3, h4 |
| 78 | + # Message variables. |
| 79 | + x = [int.from_bytes(block[4*i:4*(i+1)], 'little') for i in range(16)] |
| 80 | + |
| 81 | + # Iterate over the 80 rounds of the compression. |
| 82 | + for j in range(80): |
| 83 | + rnd = j >> 4 |
| 84 | + # Perform left side of the transformation. |
| 85 | + al = rol(al + fi(bl, cl, dl, rnd) + x[ML[j]] + KL[rnd], RL[j]) + el |
| 86 | + al, bl, cl, dl, el = el, al, bl, rol(cl, 10), dl |
| 87 | + # Perform right side of the transformation. |
| 88 | + ar = rol(ar + fi(br, cr, dr, 4 - rnd) + x[MR[j]] + KR[rnd], RR[j]) + er |
| 89 | + ar, br, cr, dr, er = er, ar, br, rol(cr, 10), dr |
| 90 | + |
| 91 | + # Compose old state, left transform, and right transform into new state. |
| 92 | + return h1 + cl + dr, h2 + dl + er, h3 + el + ar, h4 + al + br, h0 + bl + cr |
| 93 | + |
| 94 | + |
| 95 | +def ripemd160(data): |
| 96 | + """Compute the RIPEMD-160 hash of data.""" |
| 97 | + # Initialize state. |
| 98 | + state = (0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0) |
| 99 | + # Process full 64-byte blocks in the input. |
| 100 | + for b in range(len(data) >> 6): |
| 101 | + state = compress(*state, data[64*b:64*(b+1)]) |
| 102 | + # Construct final blocks (with padding and size). |
| 103 | + pad = b"\x80" + b"\x00" * ((119 - len(data)) & 63) |
| 104 | + fin = data[len(data) & ~63:] + pad + (8 * len(data)).to_bytes(8, 'little') |
| 105 | + # Process final blocks. |
| 106 | + for b in range(len(fin) >> 6): |
| 107 | + state = compress(*state, fin[64*b:64*(b+1)]) |
| 108 | + # Produce output. |
| 109 | + return b"".join((h & 0xffffffff).to_bytes(4, 'little') for h in state) |
| 110 | + |
| 111 | + |
| 112 | +class TestFrameworkKey(unittest.TestCase): |
| 113 | + def test_ripemd160(self): |
| 114 | + """RIPEMD-160 test vectors.""" |
| 115 | + # See https://homes.esat.kuleuven.be/~bosselae/ripemd160.html |
| 116 | + for msg, hexout in [ |
| 117 | + (b"", "9c1185a5c5e9fc54612808977ee8f548b2258d31"), |
| 118 | + (b"a", "0bdc9d2d256b3ee9daae347be6f4dc835a467ffe"), |
| 119 | + (b"abc", "8eb208f7e05d987a9b044a8e98c6b087f15a0bfc"), |
| 120 | + (b"message digest", "5d0689ef49d2fae572b881b123a85ffa21595f36"), |
| 121 | + (b"abcdefghijklmnopqrstuvwxyz", |
| 122 | + "f71c27109c692c1b56bbdceb5b9d2865b3708dbc"), |
| 123 | + (b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", |
| 124 | + "12a053384a9c0c88e405a06c27dcf49ada62eb2b"), |
| 125 | + (b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789", |
| 126 | + "b0e20b6e3116640286ed3a87a5713079b21f5189"), |
| 127 | + (b"1234567890" * 8, "9b752e45573d4b39f4dbd3323cab82bf63326bfb"), |
| 128 | + (b"a" * 1000000, "52783243c1697bdbe16d37f97f68f08325dc1528") |
| 129 | + ]: |
| 130 | + self.assertEqual(ripemd160(msg).hex(), hexout) |
0 commit comments