bcrypt_bsd/
lib.rs

1//! Rust wrapper around the BCrypt hashing algorithm implementation [written in C][src].
2//!
3//! The C implementation is embedded into this crate and compiled using build script.
4//!
5//! # Example
6//!
7//! ```
8//! use bcrypt_bsd::{gen_salt, hash, to_str};
9//! 
10//! let salt = gen_salt(12).unwrap();
11//! let bcrypt_hash = hash("Password", &salt).unwrap();
12//! 
13//! println!("bcrypt salt: {}", to_str(&salt).unwrap());
14//! println!("bcrypt hash: {}", to_str(&bcrypt_hash).unwrap());
15//! 
16//! ```
17//!
18//! [src]: http://www.openwall.com/crypt/
19//!
20
21extern crate libc;
22extern crate rand;
23
24use std::str::from_utf8_unchecked;
25use std::ffi::{CString, CStr};
26use std::error::Error;
27use std::fmt;
28use libc::{c_int, strerror};
29use rand::Rng;
30use rand::os::OsRng;
31
32extern "C" {
33    // result is a 30-byte C string of base64 code
34    fn c_gen_salt(cost: u8, random16: *const u8, result: *mut u8) -> c_int;
35
36    // result is a 61-byte C string of base64 code
37    fn c_hash(key: *const u8, salt: *const u8, result: *mut u8) -> c_int;
38}
39
40/// Generate salt for a BCrypt hash.
41///
42/// 
43pub fn gen_salt(cost: u8) -> Result<[u8; 30], CryptError> {
44    let mut salt: [u8; 30] = [0; 30];
45    let mut random: [u8; 16] = [0; 16];
46
47    fill_random(&mut random);
48
49    unsafe {
50        match c_gen_salt(cost, random.as_ptr(), salt.as_mut_ptr()) {
51            0 => Ok(salt),
52            errno => Err(CryptError::new(errno, None)),
53        }
54    }
55}
56
57// NOTE: password longer than 72 characters are truncated
58/// Compute BCrypt hash from a password and salt.
59///
60///
61pub fn hash(password: &str, salt: &[u8]) -> Result<[u8; 61], CryptError> {
62    if password.len() == 0 {
63        return Err(CryptError::invalid_arg("password cannot be empty".into()));
64    }
65    if password.len() > 72 {
66        return Err(CryptError::invalid_arg("password length must not exceed 72".into()));
67    }
68    if salt.len() < 30 {
69        return Err(CryptError::invalid_arg("salt must be at least 30 bytes long".into()));
70    }
71
72    let c_password = if let Ok(c) = CString::new(password) {
73        c
74    } else {
75        return Err(CryptError::invalid_arg("password must not contain NULL characters".into()));
76    };
77
78    unsafe {
79        let mut result: [u8; 61] = [0; 61];
80        match c_hash(c_password.as_bytes().as_ptr(),
81                     salt.as_ptr(),
82                     result.as_mut_ptr()) {
83            0 => Ok(result),
84            errno => Err(CryptError::new(errno, None)),
85        }
86    }
87}
88
89/// Convert a nul-terminated byte slice into a borrowed string.
90pub fn to_str<'a>(bytes: &'a [u8]) -> Result<&'a str, CryptError> {
91    match CStr::from_bytes_with_nul(bytes) {
92        Ok(c_str) => {
93            match c_str.to_str() {
94                Ok(s) => Ok(s),
95                Err(e) => Err(CryptError::invalid_arg(e.to_string())),
96            }
97        }
98        Err(e) => Err(CryptError::invalid_arg(e.to_string())),
99    }
100}
101
102
103/// BCrypt hashing error.
104///
105/// 
106#[derive(Debug)]
107pub struct CryptError {
108    /// C error code
109    errno: c_int,
110
111    /// optional message
112    desc: Option<String>,
113}
114
115impl CryptError {
116    pub fn new(errno: c_int, desc: Option<String>) -> CryptError {
117        CryptError { errno, desc }
118    }
119
120    pub fn invalid_arg(desc: String) -> CryptError {
121        CryptError {
122            errno: libc::EINVAL,
123            desc: Some(desc),
124        }
125    }
126
127    pub fn errno(&self) -> c_int {
128        self.errno
129    }
130}
131
132impl Error for CryptError {
133    fn description(&self) -> &str {
134        "bcrypt error"
135    }
136}
137
138impl fmt::Display for CryptError {
139    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
140        let desc = if let Some(ref desc) = self.desc {
141            desc.clone()
142        } else {
143            unsafe { errno_to_string(self.errno) }
144        };
145
146        write!(f, "bcrypt error: {} ({})", desc, self.errno)
147    }
148}
149
150fn fill_random(random: &mut [u8]) {
151    let mut rng = OsRng::new().unwrap();
152    rng.fill_bytes(random);
153}
154
155unsafe fn errno_to_string(errno: c_int) -> String {
156    from_utf8_unchecked(CStr::from_ptr(strerror(errno)).to_bytes()).into()
157}
158
159
160#[cfg(test)]
161mod tests {
162    use super::{to_str, gen_salt, hash};
163
164    #[test]
165    fn test_to_str() {
166        assert_eq!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 110, 0]).unwrap(),
167                   "rustacean");
168        assert!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 110]).is_err());
169        assert!(to_str(&[114, 117, 115, 116, 097, 099, 101, 097, 0, 110, 0]).is_err());
170    }
171
172    #[test]
173    fn test_gen_salt() {
174        let salt = gen_salt(4).unwrap();
175        assert_eq!(to_str(&salt).unwrap().len(), 29);
176    }
177
178    #[test]
179    fn test_hash() {
180        let pwhash = hash("Password", &gen_salt(4).unwrap()).unwrap();
181        assert_eq!(to_str(&pwhash).unwrap().len(), 60);
182
183        assert!(hash("Password", &[0; 15]).is_err());
184        assert!(hash("", &gen_salt(4).unwrap()).is_err());
185    }
186}