package com.infendro.hash.md import com.infendro.bytes.ByteOrder.LITTLE_ENDIAN import com.infendro.bytes.bytearray.copyInto 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 = uintArrayOf( 0x67452301U, 0xefcdab89U, 0x98badcfeU, 0x10325476U, ).asIntArray() private val c = uintArrayOf( 0xd76aa478U, 0xe8c7b756U, 0x242070dbU, 0xc1bdceeeU, 0xf57c0fafU, 0x4787c62aU, 0xa8304613U, 0xfd469501U, 0x698098d8U, 0x8b44f7afU, 0xffff5bb1U, 0x895cd7beU, 0x6b901122U, 0xfd987193U, 0xa679438eU, 0x49b40821U, 0xf61e2562U, 0xc040b340U, 0x265e5a51U, 0xe9b6c7aaU, 0xd62f105dU, 0x02441453U, 0xd8a1e681U, 0xe7d3fbc8U, 0x21e1cde6U, 0xc33707d6U, 0xf4d50d87U, 0x455a14edU, 0xa9e3e905U, 0xfcefa3f8U, 0x676f02d9U, 0x8d2a4c8aU, 0xfffa3942U, 0x8771f681U, 0x6d9d6122U, 0xfde5380cU, 0xa4beea44U, 0x4bdecfa9U, 0xf6bb4b60U, 0xbebfbc70U, 0x289b7ec6U, 0xeaa127faU, 0xd4ef3085U, 0x04881d05U, 0xd9d4d039U, 0xe6db99e5U, 0x1fa27cf8U, 0xc4ac5665U, 0xf4292244U, 0x432aff97U, 0xab9423a7U, 0xfc93a039U, 0x655b59c3U, 0x8f0ccc92U, 0xffeff47dU, 0x85845dd1U, 0x6fa87e4fU, 0xfe2ce6e0U, 0xa3014314U, 0x4e0811a1U, 0xf7537e82U, 0xbd3af235U, 0x2ad7d2bbU, 0xeb86d391U, ).asIntArray() 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() val w = IntArray(16) val v = IntArray(4) for (i in input.indices step block) { process(h, input, i, w, v) } 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) val result = ByteArray(size) value.copyInto(result) result[value.size] = 0x80U.toByte() length.copyInto(result, result.size - 8, order = LITTLE_ENDIAN) return result } private fun process(h: IntArray, value: ByteArray, offset: Int, w: IntArray, v: IntArray) { value.copyInto(w, startIndex = offset, endIndex = offset + block, order = LITTLE_ENDIAN) h.copyInto(v) 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] } } }