1use md5::{Digest, Md5};
2
3const HASH_PREFIX: &[u8] = b"$apr1$";
4
5const DIGEST_SIZE: usize = 16;
6
7fn extract_salt(mut hash: &[u8]) -> Option<&[u8]> {
8 use std::cmp::min;
9
10 if !hash.starts_with(HASH_PREFIX) {
11 return None;
12 }
13
14 hash = &hash[HASH_PREFIX.len()..];
16
17 hash = &hash[..min(hash.len(), 8)];
19
20 let end = hash.iter().position(|&x| x == b'$').unwrap_or(8);
22
23 Some(&hash[..end])
24}
25
26pub fn verify(password: &[u8], hash: &[u8]) -> bool {
27 let Some(salt) = extract_salt(hash) else {
28 return false;
29 };
30
31 assert!(salt.len() == 8);
32
33 let salt = salt.try_into().unwrap();
34
35 let h2 = to_hash_string(encode(password, salt), salt);
36
37 return hash == h2;
38}
39
40pub fn to_hash_string(digest: [u8; DIGEST_SIZE], salt: [u8; 8]) -> Vec<u8> {
41 const LEN_STRING: usize = 37;
43
44 const LEN_HASH: usize = 22;
46
47 let mut out = Vec::with_capacity(LEN_STRING);
48
49 out.extend_from_slice(HASH_PREFIX);
50 out.extend_from_slice(&salt);
51 out.push(b'$');
52
53 let alpha = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
54
55 let n = u128::from_le_bytes(digest);
56
57 for i in 0..LEN_HASH {
58 let idx = (n >> i * 6) as usize & 0x3F;
59 out.push(alpha[idx]);
60 }
61
62 out
63}
64
65pub fn encode(password: &[u8], salt: [u8; 8]) -> [u8; DIGEST_SIZE] {
66 let mut md5 = Md5::new();
67
68 md5.update(password);
69 md5.update(HASH_PREFIX);
70 md5.update(&salt);
71
72 let extra: [u8; DIGEST_SIZE] = {
74 let mut md5 = Md5::new();
75 md5.update(password);
76 md5.update(&salt);
77 md5.update(password);
78 md5.finalize().into()
79 };
80
81 for _ in 0..password.len() / DIGEST_SIZE {
83 md5.update(&extra);
84 }
85
86 md5.update(&extra[..password.len() % DIGEST_SIZE]);
87
88 let mut i = password.len();
89
90 while i != 0 {
91 if i & 1 != 0 {
92 md5.update(&[0]);
93 }
94 else {
95 md5.update(password.get(..1).unwrap_or(&[0]));
98 }
99 i >>= 1;
100 }
101
102 let mut digest: [u8; DIGEST_SIZE] = md5.finalize().into();
103
104 for i in 0..1000 {
105 let mut md5 = Md5::new();
106
107 if i & 1 != 0 {
108 md5.update(password);
109 }
110 else {
111 md5.update(&digest);
112 }
113
114 if i % 3 != 0 {
115 md5.update(&salt);
116 }
117
118 if i % 7 != 0 {
119 md5.update(password);
120 }
121
122 if i & 1 != 0 {
123 md5.update(&digest)
124 }
125 else {
126 md5.update(password);
127 }
128
129 digest = md5.finalize().into();
130 }
131
132 let output = [
133 digest[12], digest[ 6], digest[0],
134 digest[13], digest[ 7], digest[1],
135 digest[14], digest[ 8], digest[2],
136 digest[15], digest[ 9], digest[3],
137 digest[ 5], digest[10], digest[4],
138 digest[11]
139 ];
140
141 output
142}