Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/racum/rust-djangohashers
A Rust port of the password primitives used in Django Project.
https://github.com/racum/rust-djangohashers
algorithm argon2 bcrypt cryptography django hashes password password-hash pbkdf2 rust rust-port
Last synced: 7 days ago
JSON representation
A Rust port of the password primitives used in Django Project.
- Host: GitHub
- URL: https://github.com/racum/rust-djangohashers
- Owner: Racum
- License: other
- Created: 2015-11-13T13:07:27.000Z (almost 9 years ago)
- Default Branch: master
- Last Pushed: 2024-03-12T15:26:21.000Z (8 months ago)
- Last Synced: 2024-10-01T00:56:00.566Z (about 1 month ago)
- Topics: algorithm, argon2, bcrypt, cryptography, django, hashes, password, password-hash, pbkdf2, rust, rust-port
- Language: Rust
- Homepage:
- Size: 176 KB
- Stars: 55
- Watchers: 3
- Forks: 8
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
- awesome-rust-cn - racum/rust-djangohashers - ci.org/Racum/rust-djangohashers.svg?branch=master">](https://travis-ci.org/Racum/rust-djangohashers) (Libraries / Cryptography)
- awesome-rust - racum/rust-djangohashers - ci.org/Racum/rust-djangohashers.svg?branch=master">](https://travis-ci.org/Racum/rust-djangohashers) (Libraries / Cryptography)
- awesome-rust - racum/rust-djangohashers
- awesome-rust-cn - racum/rust-djangohashers
- awesome-rust-zh - racum/rust-djangohashers - Django 项目中使用的密码原语的 Rust 端口。它不需要 Django,只根据其样式,哈希并验证密码。[<img src="https://api.travis-ci.org/Racum/rust-djangohashers.svg?branch=master">](https://travis-ci.org/Racum/rust-djangohashers) (库 / 加密)
- awesome-rust - racum/rust-djangohashers - Port of the password primitives used in the Django Project. It doesn't require Django, only hashes and validates passwords according to its style. (Libraries / Cryptography)
- awesome-rust - racum/rust-djangohashers - ci.org/Racum/rust-djangohashers.svg?branch=master">](https://travis-ci.org/Racum/rust-djangohashers) (库 Libraries / 密码学 Cryptography)
- fucking-awesome-rust - racum/rust-djangohashers - Port of the password primitives used in the Django Project. It doesn't require Django, only hashes and validates passwords according to its style. (Libraries / Cryptography)
- fucking-awesome-rust - racum/rust-djangohashers - Port of the password primitives used in the Django Project. It doesn't require Django, only hashes and validates passwords according to its style. (Libraries / Cryptography)
README
# Rust DjangoHashers
A Rust port of the password primitives used in [Django Project](https://www.djangoproject.com).
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`:
```rust
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$390000$7HRd1YJBZvYj$Rc3BW6f7ss3CShWkULiXI9Rxj7CDdstBeoyCgFFQaK0="
Is valid: true
```## Installation
Add the dependency to your `Cargo.toml`:
```toml
[dependencies]
djangohashers = "^1.7"
```Reference and import:
```rust
extern crate djangohashers;// Everything (it's not much):
use djangohashers::*;// Or, just what you need:
use djangohashers::{check_password, make_password, Algorithm};
```## Compiling Features
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**.
* `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](https://www.cryptologie.net/article/281/pbkdf2-performance-matters/): the package [fastpbkdf2](https://github.com/ctz/rust-fastpbkdf2) uses a C-binding of a [library](https://github.com/ctz/fastpbkdf2) 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:
```toml
[dependencies.djangohashers]
version = "^1.7"
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:
```
$ brew install openssl
$ export LIBRARY_PATH="$(brew --prefix openssl)/lib"
$ export CFLAGS="-I$(brew --prefix openssl)/include"
$ cargo ...
```For other OSs and package managers, [follow the guide](https://cryptography.io/en/latest/installation/) 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.1.5 on Python 3.11.1 | 189ms | 100% (baseline)
djangohashers with ring::pbkdf2 (default) | 145ms | 76.7% 🐇
djangohashers with fastpbkdf2 | 119ms | 62.9 🐇On a Apple M1:
Method | Encode or Check | Performance
------- | --------------- | -------
Django 4.1.5 on Python 3.11.1 | 65ms | 100% (baseline)
djangohashers with ring::pbkdf2 (default) | 38ms | 58.5% 🐇
djangohashers with fastpbkdf2 | 26ms | 40.0% 🐇Replicate test above with Docker:
```
$ docker build -t rs-dj-hashers-profile .
...$ docker run -t rs-dj-hashers-profile
Hashing time: 65ms (Python 3.11.1, Django 4.1.5).
Hashing time: 38ms (Vanilla PBKDF2).
Hashing time: 26ms (Fast PBKDF2).
```## Compatibility
DjangoHashers passes all relevant unit tests from Django 1.4 to 5.0 (and beta of 5.1), there is even a [line-by-line translation](https://github.com/Racum/rust-djangohashers/blob/master/tests/django.rs) of [tests/auth_tests/test_hashers.py](https://github.com/django/django/blob/e403f22/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](https://docs.rs/djangohashers/), thanks to **docs.rs** project!
### Verifying a Hashed Password
Function signatures:
```rust
pub fn check_password(password: &str, encoded: &str) -> Result {}
pub fn check_password_tolerant(password: &str, encoded: &str) -> bool {}
```Complete version:
```rust
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:
```rust
if check_password_tolerant(password, encoded) {
// Log the user in.
} else {
// Ask the user to try again.
}
```### Generating a Hashed Password
Function signatures:
```rust
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::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):
```rust
let encoded = make_password("KRONOS");
// Returns something like:
// pbkdf2_sha256$24000$go9s3b1y1BTe$Pksk4EptJ84KDnI7ciocmhzFAb5lFoFwd6qlPOwwW4Q=
```Using a defined algorithm (random salt):
```rust
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):
```rust
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]*$`).
### Generating a Hashed Password based on a Django version
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.
```rust
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 (`5.0` for DjangoHashers `1.7.3`).
* `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
* `DjangoVersion::V4_2` Django 4.2
* `DjangoVersion::V5_0` Django 5.0
* `DjangoVersion::V5_0` Django 5.1### Verifying a Hash Format (pre-crypto)
Function signature:
```rust
pub fn is_password_usable(encoded: &str) -> bool {}
```You can check if the password hash is properly formatted before running the expensive cryto stuff:
```rust
let encoded = "pbkdf2_sha256$24000$..."; // Fetched from DB.if is_password_usable(encoded) {
// Go ahead.
} else {
// Check your database or report an issue.
}
```## 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](https://github.com/rust-lang-nursery/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"*.