Skip to content

Instantly share code, notes, and snippets.

@OneOfOne
Last active January 5, 2024 21:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save OneOfOne/0d93d950f69fa256ec3047acbc7be684 to your computer and use it in GitHub Desktop.
Save OneOfOne/0d93d950f69fa256ec3047acbc7be684 to your computer and use it in GitHub Desktop.
hashed password in rust
use base64::{
engine::{general_purpose, GeneralPurpose},
Engine as _,
};
use rand::{thread_rng, Rng as _};
use sha2::{Digest as _, Sha256};
const B64: GeneralPurpose = general_purpose::URL_SAFE_NO_PAD;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SaltyPassword {
pub pass: [u8; 32],
pub salt: [u8; 8],
pub cost: u8,
}
impl SaltyPassword {
pub fn new(pass: impl AsRef<str>, cost: u8) -> Self {
let mut salt = [0u8; 8];
thread_rng().fill(&mut salt[..]);
Self::new_with_salt(pass, salt, cost)
}
pub fn new_with_salt(pass: impl AsRef<str>, salt: [u8; 8], cost: u8) -> Self {
let pass = pass.as_ref().as_bytes();
let mut h = Sha256::new();
h.update(&salt);
h.update(&pass);
for _ in 0..cost {
h.update(&salt);
h.update(&pass);
}
Self {
pass: h.finalize().into(),
salt,
cost,
}
}
pub fn from_hashed(hashed: impl AsRef<str>) -> Result<Self, &'static str> {
let hashed = hashed.as_ref();
let parts = hashed.split("$").collect::<Vec<&str>>();
if parts.len() != 3 {
return Err("invalid hashed password");
}
let cost = parts[0].parse::<u8>().or(Err("invalid cost"))?;
let salt = B64.decode(parts[1]).or(Err("invalid salt bytes"))?;
let salt: [u8; 8] = salt.try_into().or(Err("invalid salt"))?;
let pass = B64.decode(parts[2]).or(Err("invalid pass bytes"))?;
let pass: [u8; 32] = pass.try_into().or(Err("invalid pass"))?;
Ok(Self { pass, salt, cost })
}
pub fn salt_b64(self) -> String {
B64.encode(self.salt)
}
pub fn pass_b64(self) -> String {
B64.encode(self.pass)
}
pub fn as_string(self) -> String {
format!("{}${}${}", self.cost, self.salt_b64(), self.pass_b64())
}
pub fn validate(
hashed: impl AsRef<str>,
password: impl AsRef<str>,
) -> Result<bool, &'static str> {
let p1 = Self::from_hashed(hashed)?;
let p2 = Self::new_with_salt(password, p1.salt, p1.cost);
Ok(p1 == p2)
}
}
impl PartialEq<&str> for SaltyPassword {
fn eq(&self, pass: &&str) -> bool {
let other = Self::new_with_salt(pass, self.salt, self.cost);
self == &other
}
}
impl PartialEq<String> for SaltyPassword {
fn eq(&self, pass: &String) -> bool {
self == &pass.as_str()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hashed_password() {
let pass = SaltyPassword::new("password", 10);
assert!(pass == "password");
assert!(pass != "wrong password");
}
}
@OneOfOne
Copy link
Author

OneOfOne commented Jan 5, 2024

test tests::hashed_password ... ok

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment