Rust DjangoHashers
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.
TL;DR
Content of examples/tldr.rs
:
Output:
Installation
Add the dependency to your Cargo.toml
:
Reference and import:
Compiling Features
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_scrypt
: only Scrypt. (new in1.5.0
) -
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.
Fast PBKDF2 Version
Depending on your platform, OS and version of libraries, it is possible that DjangoHashers can be slower than Python/Django's reference implementation. If performance is critical for your case, there is an alternatice implementation: the package fastpbkdf2 uses a C-binding of a library that requires OpenSSL. If ring's implementation of PBKDF2 reaches this level of optiomization, the fastpbkdf2 version will be deprecated.
Installation
Add the dependency to your Cargo.toml
declaring the feature:
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:
For other OSs and package managers, follow the guide of how to install Python’s Cryptography dependencies, that also links against OpenSSL.
Performance
On a Quad-Core Intel Core i7:
Method | Encode or Check | Performance |
---|---|---|
Django 4.0.1 on Python 3.10.1 | 153ms | 100% (baseline) |
djangohashers with ring::pbkdf2 (default) | 162ms | 105.9% 🐢 |
djangohashers with fastpbkdf2 | 95ms | 62.1% 🐇 |
On a Apple M1:
Method | Encode or Check | Performance |
---|---|---|
Django 4.0.1 on Python 3.10.1 | 57ms | 100% (baseline) |
djangohashers with ring::pbkdf2 (default) | 30ms | 52.6% 🐇 |
djangohashers with fastpbkdf2 | 21ms | 36.8% 🐇 |
Replicate test above with Docker:
Compatibility
DjangoHashers passes all relevant unit tests from Django 1.4 to 4.1, there is even a line-by-line translation of tests/auth_tests/test_hashers.py.
What is not covered:
- Upgrade/Downgrade callbacks.
- Any 3rd-party hasher outside Django's code.
- Some tests that makes no sense in idiomatic Rust.
Usage
API Documentation, thanks to docs.rs project!
Verifying a Hashed Password
Function signatures:
Complete version:
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:
Generating a Hashed Password
Function signatures:
Available algorithms:
-
Algorithm::PBKDF2
(default) -
Algorithm::PBKDF2SHA1
-
Algorithm::Argon2
-
Algorithm::Scrypt
-
Algorithm::BCryptSHA256
-
Algorithm::BCrypt
-
Algorithm::SHA1
-
Algorithm::MD5
-
Algorithm::UnsaltedSHA1
-
Algorithm::UnsaltedMD5
-
Algorithm::Crypt
The algorithms follow the same Django naming model, minus the PasswordHasher
suffix.
Using default settings (PBKDF2 algorithm, random salt):
Using a defined algorithm (random salt):
Using a defined algorithm and salt (not recommended, use it only for debug):
Warning: make_password_with_settings
and make_password_core
will both panic if salt is not only letters and numbers (^[A-Za-z0-9]*$
).
Generating a Hashed Password based on a Django version
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.
Available versions:
-
DjangoVersion::CURRENT
Current Django version (4.0
for DjangoHashers1.5.0
). -
DjangoVersion::V1_4
Django 1.4 -
DjangoVersion::V1_5
Django 1.5 -
DjangoVersion::V1_6
Django 1.6 -
DjangoVersion::V1_7
Django 1.7 -
DjangoVersion::V1_8
Django 1.8 -
DjangoVersion::V1_9
Django 1.9 -
DjangoVersion::V1_10
Django 1.10 -
DjangoVersion::V1_11
Django 1.11 -
DjangoVersion::V2_0
Django 2.0 -
DjangoVersion::V2_1
Django 2.1 -
DjangoVersion::V2_2
Django 2.2 -
DjangoVersion::V3_0
Django 3.0 -
DjangoVersion::V3_1
Django 3.1 -
DjangoVersion::V3_2
Django 3.2 -
DjangoVersion::V4_0
Django 4.0 -
DjangoVersion::V4_1
Django 4.1
Verifying a Hash Format (pre-crypto)
Function signature:
You can check if the password hash is properly formatted before running the expensive cryto stuff:
Contributing
- Be patient with me, I’m new to Rust and this is my first project.
- Don't go nuts with your mad-rust-skillz, legibility is a priority.
- Please use rustfmt in your code.
- Always include some test case.
License
Rust DjangoHashers is released under the 3-Clause BSD License.
tl;dr: "free to use as long as you credit me".