1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
pub mod error;
use error::Error;
use base64;
use md5;
use std::convert::{TryFrom, TryInto};
#[derive(Debug)]
pub struct PhPass<'a> {
passes: usize,
salt: &'a str,
hash: [u8; 16],
}
const CRYPT: &str = r"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
impl<'a> TryFrom<&'a str> for PhPass<'a> {
type Error = Error;
fn try_from(s: &'a str) -> Result<Self, Self::Error> {
if s.len() < 34 {
return Err(Error::OldWPFormat);
}
if &s[0..3] != "$P$" {
return Err(Error::InvalidId(s[0..3].to_string()));
}
let passes = s.chars().nth(3);
let passes = 1
<< CRYPT
.find(passes.ok_or(Error::InvalidPasses(passes))?)
.ok_or(Error::InvalidPasses(passes))?;
let encoded = &s[12..];
let len = encoded.len();
let hash = base64::decode_config(
std::iter::repeat(b'.')
.take(3 - len % 3)
.chain(encoded.bytes().rev())
.collect::<Vec<_>>(),
base64::CRYPT,
)?
.iter()
.rev()
.take(16)
.copied()
.collect::<Vec<_>>()
.as_slice()
.try_into()?;
Ok(Self {
passes,
salt: &s[4..12],
hash,
})
}
}
impl PhPass<'_> {
pub fn verify<T: AsRef<[u8]>>(&self, pass: T) -> Result<(), Error> {
let pass = pass.as_ref();
let salt = self.salt.as_bytes();
let checksum = (0..self.passes).fold(md5::compute([salt, pass].concat()), |a, _| {
md5::compute([&a.0, pass].concat())
});
if self.hash == checksum.0 {
Ok(())
} else {
Err(Error::VerificationError)
}
}
}