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
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
extern crate bcrypt;
extern crate hmac;
extern crate sha2;

use self::{bcrypt::{hash, verify, BcryptError},
           hmac::{Hmac, Mac, crypto_mac::InvalidKeyLength}, sha2::Sha256};
use std::fmt::Write;

#[derive(Debug, PartialEq)]
pub enum PasswordError {
    InvalidKeyLength,
    CostNotAllowed(u32),
    InvalidHash(String),
}

fn hmac_password(
    password: &str,
    hmac_key: &[u8],
) -> Result<String, InvalidKeyLength> {
    let mut mac = Hmac::<Sha256>::new(hmac_key)?;
    mac.input(password.as_bytes());
    let result = mac.result().code();
    let mut result_hex = String::new();
    write!(&mut result_hex, "{:x}", result)
        .expect("The Hmac result should convert to hex.");
    Ok(result_hex)
}

/// HMAC and Bcrypt a password of any arbitrary length
///
/// # Examples
///
/// Usage:
/// ```
/// use easy_password::bcrypt::hash_password;
///
/// let hash = hash_password("my_password", b"secure_key", 12).unwrap();
/// ```
pub fn hash_password(
    password: &str,
    hmac_key: &[u8],
    bcrypt_rounds: u32,
) -> Result<String, PasswordError> {
    let hmac_hex = match hmac_password(password, hmac_key) {
        Ok(result) => result,
        Err(InvalidKeyLength) => {
            return Err(PasswordError::InvalidKeyLength);
        }
    };
    let hashed = hash(hmac_hex.as_str(), bcrypt_rounds);
    match hashed {
        Ok(result) => Ok(result),
        Err(BcryptError::CostNotAllowed(cost)) => {
            Err(PasswordError::CostNotAllowed(cost))
        }
        Err(_) => panic!("Unexpected Bcrypt error."),
    }
}

/// HMAC and Bcrypt a password of any arbitrary length
///
/// # Examples
///
/// Usage:
/// ```
/// use easy_password::bcrypt::{hash_password, verify_password};
///
/// let hash = hash_password("my_password", b"secure_key", 12).unwrap();
/// let result =
///     verify_password("my_password", hash.as_str(), b"secure_key").unwrap();
/// ```
pub fn verify_password(
    password: &str,
    hashed: &str,
    hmac_key: &[u8],
) -> Result<bool, PasswordError> {
    let hmac_hex = match hmac_password(password, hmac_key) {
        Ok(result) => result,
        Err(InvalidKeyLength) => {
            return Err(PasswordError::InvalidKeyLength);
        }
    };
    match verify(hmac_hex.as_str(), hashed) {
        Ok(bool) => Ok(bool),
        Err(BcryptError::InvalidCost(_))
        | Err(BcryptError::InvalidPrefix(_))
        | Err(BcryptError::InvalidHash(_))
        | Err(BcryptError::InvalidBase64(_, _)) => {
            Err(PasswordError::InvalidHash(hashed.to_string()))
        }
        Err(_) => panic!("Unexpected Bcrypt error."),
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_verify_correct() {
        let hash = hash_password("test_password", b"my_key", 4)
            .expect("This should be a valid cost and hmac_key");
        assert!(
            verify_password("test_password", hash.as_str(), b"my_key")
                .expect("Hash and hmac_key should be valid.")
        );
    }

    #[test]
    fn test_verify_incorrect() {
        let hash = hash_password("test_password", b"my_key", 4)
            .expect("This should be a valid cost and hmac_key");
        assert!(
            !verify_password("wrong_password", hash.as_str(), b"my_key")
                .expect("Hash and hmac_key should be valid.")
        );
    }

    #[test]
    fn test_invalid_cost() {
        assert_eq!(
            hash_password("test_password", b"my_key", 1).err(),
            Some(PasswordError::CostNotAllowed(1)),
        );
    }

    #[test]
    fn test_invalid_hash() {
        assert_eq!(
            verify_password("wrong_password", "invalid_hash", b"my_key").err(),
            Some(PasswordError::InvalidHash(
                "invalid_hash".to_string()
            )),
        );
    }
}