A Rust port of the password primitives used in Django Project.
Django's django.contrib.auth.models.User class has a few methods to deal with passwords, like set_password() and check_password(); DjangoHashers implements the primitive functions behind those methods. All Django's built-in hashers are supported.
This library was conceived for Django integration, but is not limited to it; you can use the password hash algorithm in any Rust project (or FFI integration), since its security model is already battle-tested.
Content of examples/tldr.rs:
extern crate djangohashers;
use djangohashers::*;
fn main() {
let encoded = make_password("K2jitmJ3CBfo");
println!("Hash: {:?}", encoded);
let is_valid = check_password("K2jitmJ3CBfo", &encoded).unwrap();
println!("Is valid: {:?}", is_valid);
}
Output:
$ cargo run --quiet --example tldr
Hash: "pbkdf2_sha256$30000$E2DtC4weM2DY$ZTso63dGXbq+QdVGUwq8Y05RgyUc3AsUSfswqUOZ3xc="
Is valid: true
Add the dependency to your Cargo.toml:
[dependencies]
djangohashers = "^1.0"
Reference and import:
extern crate djangohashers;
// Everything (it's not much):
use djangohashers::*;
// Or, just what you need:
use djangohashers::{check_password, make_password, Algorithm};
New in
0.3.0.
By default all the hashers are enabled, but you can pick only the hashers that you need to avoid unneeded dependencies.
default: all hashers.with_pbkdf2: only PBKDF2 and PBKDF2SHA1.with_argon2: only Argon2.with_bcrypt: only BCrypt and BCryptSHA256.with_legacy: only SHA1, MD5, UnsaltedSHA1, UnsaltedMD5 and Crypt.fpbkdf2: enables Fast PBKDF2 (requires OpenSSL, see below).fuzzy_tests: only for development, enables fuzzy tests.Unfortunately rust-crypto’s implementation of PBKDF2 is not properly optimized: it does not adhere to the loop inlines and buffering used in modern implementations. The package fastpbkdf2 uses a C-binding of a library that requires OpenSSL.
Add the dependency to your Cargo.toml declaring the feature:
[dependencies.djangohashers]
version = "^1.0"
features = ["fpbkdf2"]
You need to install OpenSSL and set the environment variable to make it visible to the compiler; this changes depending on the operation system and package manager, for example, in macOS you may need to do something like this:
Via Homebrew:
$ brew install openssl
$ CFLAGS="-I/usr/local/opt/openssl/include" cargo ...
Via MacPorts:
$ sudo port install openssl
$ CFLAGS="-I/opt/local/include" cargo ...
For other OSs and package managers, follow the guide of how to install Python’s Cryptography dependencies, that also links against OpenSSL.
| Method | Encode or Check | Performance |
|---|---|---|
| Django 1.9.4 | 29.5ms | Baseline |
| djangohashers with rust-crypto 0.2.34 (default) | 41.7ms | 41% slower |
| djangohashers with fastpbkdf2 0.1.0 | 23.1ms | 28% faster |
Notes:
--release.DjangoHashers passes all relevant unit tests from Django 1.4 to 2.2, there is even a line-by-line translation of tests/auth_tests/test_hashers.py.
What is not covered:
API Documentation, thanks to docs.rs project!
Function signatures:
pub fn check_password(password: &str, encoded: &str) -> Result<bool, HasherError> {}
pub fn check_password_tolerant(password: &str, encoded: &str) -> bool {}
Complete version:
let password = "KRONOS"; // Sent by the user.
let encoded = "pbkdf2_sha256$24000$..."; // Fetched from DB.
match check_password(password, encoded) {
Ok(valid) => {
if valid {
// Log the user in.
} else {
// Ask the user to try again.
}
}
Err(error) => {
// Deal with the error.
}
}
Possible Errors:
HasherError::UnknownAlgorithm: anything not recognizable as an algorithm.HasherError::BadHash: Hash string is corrupted.HasherError::InvalidIterations: number of iterations is not a positive integer.HasherError::EmptyHash: hash string is empty.HasherError::InvalidArgon2Salt: Argon2 salt should be Base64 encoded.If you want to automatically assume all errors as "invalid password", there is a shortcut for that:
if check_password_tolerant(password, encoded) {
// Log the user in.
} else {
// Ask the user to try again.
}
Function signatures:
pub fn make_password(password: &str) -> String {}
pub fn make_password_with_algorithm(password: &str, algorithm: Algorithm) -> String {}
pub fn make_password_with_settings(password: &str, salt: &str, algorithm: Algorithm) -> String {}
Available algorithms:
Algorithm::PBKDF2 (default)Algorithm::PBKDF2SHA1Algorithm::Argon2Algorithm::BCryptSHA256Algorithm::BCryptAlgorithm::SHA1Algorithm::MD5Algorithm::UnsaltedSHA1Algorithm::UnsaltedMD5Algorithm::CryptThe algorithms follow the same Django naming model, minus the PasswordHasher suffix.
Using default settings (PBKDF2 algorithm, random salt):
let encoded = make_password("KRONOS");
// Returns something like:
// pbkdf2_sha256$24000$go9s3b1y1BTe$Pksk4EptJ84KDnI7ciocmhzFAb5lFoFwd6qlPOwwW4Q=
Using a defined algorithm (random salt):
let encoded = make_password_with_algorithm("KRONOS", Algorithm::BCryptSHA256);
// Returns something like:
// bcrypt_sha256$$2b$12$e5C3zfswn.CowOBbbb7ngeYbxKzJePCDHwo8AMr/SZeZCoGrk7oue
Using a defined algorithm and salt (not recommended, use it only for debug):
let encoded = make_password_with_settings("KRONOS", "seasalt", Algorithm::PBKDF2SHA1);
// Returns exactly this (remember, the salt is fixed!):
// pbkdf2_sha1$24000$seasalt$F+kiWNHXbMBcwgxsvSKFCWHnZZ0=
Warning: make_password_with_settings and make_password_core will both panic if salt is not only letters and numbers (^[A-Za-z0-9]*$).
New in
0.2.1.
Django versions can have different number of iterations for hashers based on PBKDF2 and BCrypt algorithms; this abstraction makes possible to generate a password with the same number of iterations used in that versions.
use djangohashers::{Django, DjangoVersion};
let django = Django {version: DjangoVersion::V1_8}; // Django 1.8.
let encoded = django.make_password("KRONOS");
// Returns something like:
// pbkdf2_sha256$20000$u0C1E8jrnAYx$7KIo/fAuBJpswQyL7pTxO06ccrSjGdIe7iSqzdVub1w=
// |||||
// ...notice the 20000 iterations, used in Django 1.8.
Available versions:
DjangoVersion::Current Current Django version (2.1 for DjangoHashers 0.3.2).DjangoVersion::V1_4 Django 1.4DjangoVersion::V1_5 Django 1.5DjangoVersion::V1_6 Django 1.6DjangoVersion::V1_7 Django 1.7DjangoVersion::V1_8 Django 1.8DjangoVersion::V1_9 Django 1.9DjangoVersion::V1_10 Django 1.10DjangoVersion::V1_11 Django 1.11DjangoVersion::V2_0 Django 2.0DjangoVersion::V2_1 Django 2.1DjangoVersion::V2_2 Django 2.2Function signature:
pub fn is_password_usable(encoded: &str) -> bool {}
You can check if the password hash is properly formatted before running the expensive cryto stuff:
let encoded = "pbkdf2_sha256$24000$..."; // Fetched from DB.
if is_password_usable(encoded) {
// Go ahead.
} else {
// Check your database or report an issue.
}
Rust DjangoHashers is released under the 3-Clause BSD License.
tl;dr: "free to use as long as you credit me".
$ claude mcp add rust-djangohashers \
-- python -m otcore.mcp_server <graph>