// MD5 (RFC 1321) implemented in pure MoonBit.
//
// MoonBit has no `to_uint32` conversion, so the algorithm runs on `UInt64`
// with every intermediate value masked back to 32 bits. That keeps the
// modular-2^32 semantics MD5 requires while using only APIs that exist in
// this toolchain.
//
// MD5 is not collision resistant and must not be used where that matters. It
// is here because it is the fingerprint format the Windows tooling and the
// file listings of the target platforms use, so it can be compared against
// what the user already has. The package integrity itself is protected by the
// SHA-256 checksum of every entry.
///|
const MD5_M32 : UInt64 = 0xffffffffUL
///|
/// Per-round shift amounts.
fn md5_s() -> Array[Int32] {
[
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,
]
}
///|
/// Per-round additive constants: floor(2^32 * abs(sin(i + 1))).
fn md5_t() -> Array[UInt64] {
[
0xd76aa478UL, 0xe8c7b756UL, 0x242070dbUL, 0xc1bdceeeUL, 0xf57c0fafUL, 0x4787c62aUL,
0xa8304613UL, 0xfd469501UL, 0x698098d8UL, 0x8b44f7afUL, 0xffff5bb1UL, 0x895cd7beUL,
0x6b901122UL, 0xfd987193UL, 0xa679438eUL, 0x49b40821UL, 0xf61e2562UL, 0xc040b340UL,
0x265e5a51UL, 0xe9b6c7aaUL, 0xd62f105dUL, 0x02441453UL, 0xd8a1e681UL, 0xe7d3fbc8UL,
0x21e1cde6UL, 0xc33707d6UL, 0xf4d50d87UL, 0x455a14edUL, 0xa9e3e905UL, 0xfcefa3f8UL,
0x676f02d9UL, 0x8d2a4c8aUL, 0xfffa3942UL, 0x8771f681UL, 0x6d9d6122UL, 0xfde5380cUL,
0xa4beea44UL, 0x4bdecfa9UL, 0xf6bb4b60UL, 0xbebfbc70UL, 0x289b7ec6UL, 0xeaa127faUL,
0xd4ef3085UL, 0x04881d05UL, 0xd9d4d039UL, 0xe6db99e5UL, 0x1fa27cf8UL, 0xc4ac5665UL,
0xf4292244UL, 0x432aff97UL, 0xab9423a7UL, 0xfc93a039UL, 0x655b59c3UL, 0x8f0ccc92UL,
0xffeff47dUL, 0x85845dd1UL, 0x6fa87e4fUL, 0xfe2ce6e0UL, 0xa3014314UL, 0x4e0811a1UL,
0xf7537e82UL, 0xbd3af235UL, 0x2ad7d2bbUL, 0xeb86d391UL,
]
}
///|
/// Add two 32-bit words, truncated to 32 bits.
fn md5_add32(x : UInt64, y : UInt64) -> UInt64 {
(x + y) & MD5_M32
}
///|
/// Rotate a 32-bit word left by `n` bits (1 <= n <= 31).
fn md5_rotl32(x : UInt64, n : Int32) -> UInt64 {
((x << n) | (x >> (32 - n))) & MD5_M32
}
///|
/// Lowercase hex MD5 digest of `data` (32 characters).
pub fn md5_hex(data : Bytes) -> String {
let s = md5_s()
let t = md5_t()
let n = data.length()
// Message length in bits, as a 64-bit value.
let bitlen = n.to_int64() * 8
// Build the padded message: data || 0x80 || zeros || 64-bit little-endian
// length.
let msg : Array[Byte] = []
let mut i = 0
while i < n {
msg.push(data.at((i : Int32)))
i = i + 1
}
msg.push(b'\x80')
while msg.length() % 64 != 56 {
msg.push(b'\x00')
}
// MD5 stores the length little-endian, unlike SHA-256.
let mut j = 0
while j < 8 {
msg.push(((bitlen >> (j * 8)) & 0xffL).to_byte())
j = j + 1
}
let mut a0 : UInt64 = 0x67452301UL
let mut b0 : UInt64 = 0xefcdab89UL
let mut c0 : UInt64 = 0x98badcfeUL
let mut d0 : UInt64 = 0x10325476UL
// Compress each 64-byte block.
let mut off = 0
while off < msg.length() {
let x : Array[UInt64] = []
let mut k = 0
while k < 16 {
let b0b = msg[off + k * 4].to_uint64()
let b1b = msg[off + k * 4 + 1].to_uint64()
let b2b = msg[off + k * 4 + 2].to_uint64()
let b3b = msg[off + k * 4 + 3].to_uint64()
// The words are read little-endian.
x.push(b0b | (b1b << 8) | (b2b << 16) | (b3b << 24))
k = k + 1
}
let mut a = a0
let mut b = b0
let mut c = c0
let mut d = d0
k = 0
while k < 64 {
let (f, g) = if k < 16 {
// F(b, c, d) = (b AND c) OR (NOT b AND d)
((b & c) | ((b ^ MD5_M32) & d), k)
} else if k < 32 {
// G(b, c, d) = (b AND d) OR (c AND NOT d)
((b & d) | (c & (d ^ MD5_M32)), (5 * k + 1) % 16)
} else if k < 48 {
// H(b, c, d) = b XOR c XOR d
(b ^ c ^ d, (3 * k + 5) % 16)
} else {
// I(b, c, d) = c XOR (b OR NOT d)
(c ^ (b | (d ^ MD5_M32)), 7 * k % 16)
}
let tmp = d
d = c
c = b
let sum = md5_add32(md5_add32(md5_add32(a, f), t[k]), x[g])
b = md5_add32(b, md5_rotl32(sum, s[k]))
a = tmp
k = k + 1
}
a0 = md5_add32(a0, a)
b0 = md5_add32(b0, b)
c0 = md5_add32(c0, c)
d0 = md5_add32(d0, d)
off = off + 64
}
// The digest is the four state words in little-endian order.
let mut out = ""
for v in [a0, b0, c0, d0] {
let mut byte_idx = 0
while byte_idx < 4 {
let byte = ((v >> (byte_idx * 8)) & 0xffUL).to_int()
out = out +
String::make(1, hex_digit(byte >> 4)) +
String::make(1, hex_digit(byte & 0x0f))
byte_idx = byte_idx + 1
}
}
out
}
///|
/// Lowercase hex MD5 of the UTF-8 encoding of `s`.
pub fn md5_hex_of_string(s : String) -> String {
md5_hex(string_to_utf8_bytes(s))
}
///|
/// Hex digit of a nibble.
fn hex_digit(n : Int32) -> Char {
match n {
0 => '0'
1 => '1'
2 => '2'
3 => '3'
4 => '4'
5 => '5'
6 => '6'
7 => '7'
8 => '8'
9 => '9'
10 => 'a'
11 => 'b'
12 => 'c'
13 => 'd'
14 => 'e'
15 => 'f'
_ => '0'
}
}