Here we give an overview of the core functionality of libpasta. Examples can be viewed in different languages, with the full list of language support found in other languages.
The full Rust API documentation can be found here.
A common scenario is that a particular user has password, which a service will check on each login to authenticate the user.
extern crate libpasta;
// We re-export the rpassword crate for CLI password input.
use libpasta::rpassword::*;
fn main() {
let password = prompt_password_stdout("Please enter your password:").unwrap();
let password_hash = libpasta::hash_password(&password);
println!("The hashed password is: '{}'", password_hash);
}
The above code randomly generates a salt, and outputs the hash in the following format:
$$scrypt-mcf$log_n=14,r=8,p=1$pfJFg/hVSthuA5l...
.
Details for how this is serialized can be found in the technical details chapter. This adheres to libpasta’s strong defaults principle.
However, for using libpasta
one only needs to know that hash_password
outputs a variable-length string.
Now that you have the hashed output, verifying that an inputted password is correct can be done as follows:
extern crate libpasta;
use libpasta::rpassword::*;
struct User {
// ...
password_hash: String,
}
fn auth_user(user: &User) {
let password = prompt_password_stdout("Enter password:").unwrap();
if libpasta::verify_password(&user.password_hash, &password) {
println!("The password is correct!");
// ~> Handle correct password
} else {
println!("Incorrect password.");
// ~> Handle incorrect password
}
}
One of the key features of libpasta
is the ability to easily migrate passwords
to new algorithms.
Suppose we previously have bcrypt hashes in the following form:
$2a$10$175ikf/E6E.73e83....
.
This a bcrypt hash, structured as $<bcrypt identifier>$<cost>$<salthash>
.
libpasta
includes a simple work flow for migrating passwords to a new
algorithm (or new parameterization of an existing algorithm).
First, wrap existing hashes in the new algorithm to ensure their
security immediately. Second, as users log in, update the wrapped hashes to just
use the new algorithm. Wrapping simply takes an existing hash and re-hashes it
with the new algorithm.
The following code first wraps an existing hash, and then a move to just using the new algorithm:
extern crate libpasta;
use libpasta::rpassword::*;
use libpasta::{migrate_hash, HashUpdate};
struct User {
// ...
password_hash: String,
}
fn migrate_users(users: Vec<&mut User>) {
// Step 1: Wrap old hash
for user in users {
let hash = user.password_hash;
if let HashUpdate::Verified(Some(new_hash)) = migrate_hash(hash) {
user.password_hash = new_hash;
}
}
}
fn auth_user(user: &mut User) {
// Step 2: Update algorithm during log in
let password = prompt_password_stdout("Enter password:").unwrap();
match libpasta::verify_password_update_hash(&user.password_hash, password) {
HashUpdate::Verified(res) => {
if let Some(new_hash) = res {
user.password_hash = new_hash;
}
println!("Password correct, new hash: \n{}", user.password_hash);
},
HashUpdate::Failed => {
println!("Password incorrect, hash unchanged: \n{}", user.password_hash);
}
}
}
In the first step, we do not need the user’s password (and can therefore apply this to all user passwords when desired). However, the password hash is now comprised of both a bcrypt computation AND an argon2 computation.
In the second step, if the user correctly enters their password, then a new hash is computed from scratch with a fresh salt using the new algorithm. This requires updating the stored version of the hash.
More detailed information of password migration can be found here.
libpasta
is designed to work out-of-the box with strong defaults. However,
other configurations are supported through use of the Config
object.
In particular, this is necessary to use keyed functions.
This comes with the additional overheard that the config must be explicitly used.
For example, suppose we wish to use bcrypt with cost=15
as the default
algorithm.
extern crate libpasta;
use libpasta::primitives::Bcrypt;
use libpasta::config::Config;
fn main() {
let config = Config::with_primitive(Bcrypt::new(15));
let password_hash = config.hash_password("hunter2");
println!("The hashed password is: '{}'", password_hash);
// Prints bcrypt hash
}
Additionally, values may be set using a configuration file. Written in YAML, these look as follows:
default: Custom
keyed: ~
primitive:
id: "scrypt"
params:
ln: 11
r: 8
p: 1
This specifies the algorithm to use, in this case, scrypt. Similar to the above,
to use this config use the Config::from_file
method.
libpasta
also has a parameter selection tool which
can optionally output configuration values.