ripemd160.py raw

   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)
 131