package com.infendro.hash.md import com.infendro.bytes.ByteOrder.LITTLE_ENDIAN import com.infendro.bytes.bytearray.toIntArray import com.infendro.bytes.intarray.copyInto import com.infendro.bytes.ulong.copyInto import com.infendro.hash.HashFunction import com.infendro.hash.util.align object MD5 : HashFunction { override val bytes: Int get() = 16 override val block: Int get() = 64 private val initial = intArrayOf( +0x67452301, -0x10325477, -0x67452302, +0x10325476, ) private val c = intArrayOf( -0x28955b88, -0x173848aa, +0x242070db, -0x3e423112, -0x0a83f051, +0x4787c62a, -0x57cfb9ed, -0x02b96aff, +0x698098d8, -0x74bb0851, -0x0000a44f, -0x76a32842, +0x6b901122, -0x02678e6d, -0x5986bc72, +0x49b40821, -0x09e1da9e, -0x3fbf4cc0, +0x265e5a51, -0x16493856, -0x29d0efa3, +0x02441453, -0x275e197f, -0x182c0438, +0x21e1cde6, -0x3cc8f82a, -0x0b2af279, +0x455a14ed, -0x561c16fb, -0x03105c08, +0x676f02d9, -0x72d5b376, -0x0005c6be, -0x788e097f, +0x6d9d6122, -0x021ac7f4, -0x5b4115bc, +0x4bdecfa9, -0x0944b4a0, -0x41404390, +0x289b7ec6, -0x155ed806, -0x2b10cf7b, +0x04881d05, -0x262b2fc7, -0x1924661b, +0x1fa27cf8, -0x3b53a99b, -0x0bd6ddbc, +0x432aff97, -0x546bdc59, -0x036c5fc7, +0x655b59c3, -0x70f3336e, -0x00100b83, -0x7a7ba22f, +0x6fa87e4f, -0x01d31920, -0x5cfebcec, +0x4e0811a1, -0x08ac817e, -0x42c50dcb, +0x2ad7d2bb, -0x14792c6f, ) private val shifts = intArrayOf( 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, ) override fun hashInto(value: ByteArray, destination: ByteArray, destinationOffset: Int) { val input = pad(value) val h = initial.copyOf() for (i in input.indices step block) { val block = input.sliceArray(i..<(i + block)) process(h, block) } h.copyInto(destination, destinationOffset, order = LITTLE_ENDIAN) } private fun pad(value: ByteArray): ByteArray { val length = value.size.toULong() * 8UL val size = (value.size + 9).align(block) return ByteArray(size).also { value.copyInto(it) it[value.size] = 0x80U.toByte() length.copyInto(it, size - 8, order = LITTLE_ENDIAN) } } private fun process(h: IntArray, block: ByteArray) { val w = block.toIntArray(LITTLE_ENDIAN) val v = h.copyOf() repeat(64) { i -> var f = when (i) { in 0..<16 -> (v[1] and v[2]) or (v[1].inv() and v[3]) in 16..<32 -> (v[3] and v[1]) or (v[3].inv() and v[2]) in 32..<48 -> v[1] xor v[2] xor v[3] else -> v[2] xor (v[1] or v[3].inv()) } val g = when (i) { in 0..<16 -> i in 16..<32 -> (5 * i + 1) % 16 in 32..<48 -> (3 * i + 5) % 16 else -> (7 * i) % 16 } f += v[0] + c[i] + w[g] v[0] = v[3] v[3] = v[2] v[2] = v[1] v[1] += f.rotateLeft(shifts[i]) } for (i in 0..<4) { h[i] += v[i] } } }