use anyhow::Result;
const CRYPT_B64: &[u8; 64] = b"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
pub fn hash_password_sha512(password: &str) -> Result<String> {
let mut salt_bytes = [0u8; 16];
openssl::rand::rand_bytes(&mut salt_bytes)?;
let salt: String = salt_bytes
.iter()
.map(|b| CRYPT_B64[(b & 0x3f) as usize] as char)
.collect();
let hash = sha512_crypt(password.as_bytes(), salt.as_bytes());
Ok(format!("$6${salt}${hash}"))
}
fn sha512_crypt(key: &[u8], salt: &[u8]) -> String {
use openssl::sha::Sha512;
const ROUNDS: usize = 5000;
let mut temp_ctx = Sha512::new();
temp_ctx.update(key);
temp_ctx.update(salt);
temp_ctx.update(key);
let temp_result = temp_ctx.finish();
let mut alt_ctx = Sha512::new();
alt_ctx.update(key);
alt_ctx.update(salt);
let mut count = key.len();
while count > 64 {
alt_ctx.update(&temp_result);
count -= 64;
}
alt_ctx.update(&temp_result[..count]);
let mut bits = key.len();
while bits > 0 {
if bits & 1 != 0 {
alt_ctx.update(&temp_result);
} else {
alt_ctx.update(key);
}
bits >>= 1;
}
let mut alt_result = alt_ctx.finish();
let mut p_ctx = Sha512::new();
for _ in 0..key.len() {
p_ctx.update(key);
}
let key_digest = p_ctx.finish();
let p_bytes = tile(&key_digest, key.len());
let mut s_ctx = Sha512::new();
for _ in 0..(16 + alt_result[0] as usize) {
s_ctx.update(salt);
}
let salt_digest = s_ctx.finish();
let s_bytes = tile(&salt_digest, salt.len());
for round in 0..ROUNDS {
let mut ctx = Sha512::new();
if round & 1 != 0 {
ctx.update(&p_bytes);
} else {
ctx.update(&alt_result);
}
if round % 3 != 0 {
ctx.update(&s_bytes);
}
if round % 7 != 0 {
ctx.update(&p_bytes);
}
if round & 1 != 0 {
ctx.update(&alt_result);
} else {
ctx.update(&p_bytes);
}
alt_result = ctx.finish();
}
sha512_crypt_b64(&alt_result)
}
fn tile(digest: &[u8; 64], len: usize) -> Vec<u8> {
let mut out = Vec::with_capacity(len);
let mut cnt = len;
while cnt > 64 {
out.extend_from_slice(digest);
cnt -= 64;
}
out.extend_from_slice(&digest[..cnt]);
out
}
fn sha512_crypt_b64(digest: &[u8; 64]) -> String {
let mut out = String::with_capacity(86);
let mut emit = |hi: u8, mid: u8, lo: u8, count: usize| {
let mut word = (u32::from(hi) << 16) | (u32::from(mid) << 8) | u32::from(lo);
for _ in 0..count {
out.push(CRYPT_B64[(word & 0x3f) as usize] as char);
word >>= 6;
}
};
emit(digest[0], digest[21], digest[42], 4);
emit(digest[22], digest[43], digest[1], 4);
emit(digest[44], digest[2], digest[23], 4);
emit(digest[3], digest[24], digest[45], 4);
emit(digest[25], digest[46], digest[4], 4);
emit(digest[47], digest[5], digest[26], 4);
emit(digest[6], digest[27], digest[48], 4);
emit(digest[28], digest[49], digest[7], 4);
emit(digest[50], digest[8], digest[29], 4);
emit(digest[9], digest[30], digest[51], 4);
emit(digest[31], digest[52], digest[10], 4);
emit(digest[53], digest[11], digest[32], 4);
emit(digest[12], digest[33], digest[54], 4);
emit(digest[34], digest[55], digest[13], 4);
emit(digest[56], digest[14], digest[35], 4);
emit(digest[15], digest[36], digest[57], 4);
emit(digest[37], digest[58], digest[16], 4);
emit(digest[59], digest[17], digest[38], 4);
emit(digest[18], digest[39], digest[60], 4);
emit(digest[40], digest[61], digest[19], 4);
emit(digest[62], digest[20], digest[41], 4);
emit(0, 0, digest[63], 2);
out
}