A Rust port of the password primitives used in Django Project.

Overview

Rust DjangoHashers

Build Status

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:

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

Installation

Add the dependency to your Cargo.toml:

[dependencies]
djangohashers = "^1.4"

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};

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_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:

[dependencies.djangohashers]
version = "^1.4"
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 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 3.1.5 on Python 3.9.1 104ms 100% (baseline)
djangohashers with ring::pbkdf2 (default) 112ms 107.7% 🐢
djangohashers with fastpbkdf2 65ms 62.5% 🐇

On a Apple M1:

Method Encode or Check Performance
Django 3.1.5 on Python 3.9.1 37ms 100% (baseline)
djangohashers with ring::pbkdf2 (default) 22ms 59.5% 🐇
djangohashers with fastpbkdf2 14ms 37.8% 🐇

Replicate test above with Docker:

$ docker build -t rs-dj-hashers-profile .
...

$ docker run -t rs-dj-hashers-profile
Hashing time: 104ms (Python 3.9.1, Django 3.1.5).
Hashing time: 112ms (Vanilla PBKDF2).
Hashing time: 65ms (Fast PBKDF2).

Compatibility

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:

  • 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:

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.
}

Generating a Hashed Password

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::PBKDF2SHA1
  • Algorithm::Argon2
  • 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):

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]*$).

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.

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 (3.1 for DjangoHashers 1.3.1).
  • 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

Verifying a Hash Format (pre-crypto)

Function 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.
}

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".

You might also like...
A lightning-fast password generator and manager written in Rust
A lightning-fast password generator and manager written in Rust

Passlane A lightning-fast password manager for the command line Features Generate passwords Place the generated password into the clipboard Save previ

A pretty simple tool for password generation, written in Rust.

passwdgen A pretty simple tool for password generation, written in Rust. Usage: passwdgen - a pretty simple tool for password generation Usage: passw

Tool written in Rust to perform Password Spraying attacks against Azure/Office 365 accounts

AzurePasswordSprayer Tool written in Rust to perform Password Spraying attacks against Azure/Office 365 accounts. It is multi threaded and makes no co

A very fast Rust tool to crack a password protected PDF (Dangerous ☠️)
A very fast Rust tool to crack a password protected PDF (Dangerous ☠️)

DOCBOT A PDF password cracking tool with multi-threading capabilities, featuring password format generators for commonly used patterns and dictionary

An implementation of the OPAQUE password-authenticated key exchange protocol

The OPAQUE key exchange protocol OPAQUE is an asymmetric password-authenticated key exchange protocol. It allows a client to authenticate to a server

🐴 RusTOTPony — CLI manager of one-time password generators aka Google Authenticator

🐴 RusTOTPony CLI manager of time-based one-time password generators. It is a desktop alternative for Google Authenticator. Installation Arch Linux Pa

A safe implementation of the secure remote password authentication and key-exchange protocol (SRP), SRP6a and legacy are as features available.

Secure Remote Password (SRP 6 / 6a) A safe implementation of the secure remote password authentication and key-exchange protocol (SRP version 6a). Ver

A password entropy calculator.

paspio — pasvorta entropio A (naive) password entropy calculator. Refrain from using this as a sole measure of password strength, it should be used in

Password-Authenticated Key Agreement protocols

RustCrypto: PAKEs Password-Authenticated Key Agreement protocols implementation. Warnings Crates in this repository have not yet received any formal c

Comments
  • Add fuzzy tests using quickcheck

    Add fuzzy tests using quickcheck

    This uses Quickcheck to verify for each algorithm that passwords can be encoded and decoded. It will test various combinations of password/salt in order to identify flaws in the implementation.

    These tests are rather slow to execute and are disabled by default. They are hidden behind feature fuzzy-tests. To include them in your tests run, execute the following command:

    cargo test --release --features fuzzy-tests
    

    New BCrypt fuzzy tests currently do not pass and were therefore left commented out.

    opened by fbecart 4
  • Won't compile on aarch64 because of cargon crate.

    Won't compile on aarch64 because of cargon crate.

    Love this library, does exactly what I need. However I am now porting my app to aarch64 and I can't get the library to compile. I tried disabling cargon by using the below dependency config but it still tries to compile cargon:

    [dependencies.djangohashers]
    version = "1.0.1"
    features = ["fpbkdf2"]
    

    Here is the cargon error:

    running: "cc" "-O3" "-ffunction-sections" "-fdata-sections" "-fPIC" "-g" "-I" "phc-winner-argon2/include" "-march=native" "-Wall" "-Wextra" "-o" "/home/android/galileo-client/target/release/build/cargon-77514a8c699451ee/out/phc-winner-argon2/src/opt.o" "-c" "phc-winner-argon2/src/opt.c"
    cargo:warning=In file included from phc-winner-argon2/src/opt.c:19:0:
    cargo:warning=phc-winner-argon2/src/opt.h:18:10: fatal error: emmintrin.h: No such file or directory
    cargo:warning= #include <emmintrin.h>
    cargo:warning=          ^~~~~~~~~~~~~
    cargo:warning=compilation terminated.
    exit code: 1
    

    Looks like it is trying to compile optimizations that don't apply for ARM?

    Is there any quick fix I can do to make my app compile?

    Thanks, Mike

    opened by bentwire 2
  • Is there any on purpose delay here?

    Is there any on purpose delay here?

    I noticed particular delay when using check_password function in my project. I tried example from README file and got the same issue: src/main.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);
    }
    

    and

      ➜  /tmp/test1 ‹master*› » time cargo run
        Finished dev [unoptimized + debuginfo] target(s) in 0.05s
         Running `target/debug/test1`
    Hash: "pbkdf2_sha256$100000$zqJOl4RZKQvb$nairAYznfnf54QGK60x4K2TYW4WgUCo205PbjZtoN68="
    Is valid: true
    cargo run  5.70s user 0.02s system 99% cpu 5.713 total
      ➜  /tmp/test1 ‹master*› » rustc --version
    rustc 1.28.0 (9634041f0 2018-07-30)
    

    Is it supposed to be like this?

    opened by saks 2
  • Add Cargo Categories

    Add Cargo Categories

    Add to Cargo.toml:

    categories = ["algorithms", "authentication", "cryptography"]
    

    List of available categories: https://crates.io/category_slugs Guide about the format: http://doc.crates.io/manifest.html#package-metadata

    opened by Racum 0
Releases(v1.0.0)
Owner
Ronaldo Ferreira
Ronaldo Ferreira
The underlying cryptographic primitives for Manta Ecosystem

manta crypto The underlying cryptography that manta ecosystem relies on. It comes with the following traits: checksum: definitions for message digest.

Manta Network 10 Nov 10, 2021
Fuel cryptographic primitives

Fuel Crypto Fuel cryptographic primitives. Compile features std: Unless set, the crate will link to the core-crate instead of the std-crate. More info

Fuel Labs 19 Sep 8, 2022
This is the Repo used to learn blockchain development in conjusction with the CyberGen NFT Project.

Environment Setup Install Rust from https://rustup.rs/ Install Solana from https://docs.solana.com/cli/install-solana-cli-tools#use-solanas-install-to

null 1 Nov 3, 2021
Master Password in Pure Rust

Master Password •••| This is the Rust version of the original found here. This can be used as a drop-in replacement for the reference C version, offer

Rust India 34 Apr 13, 2022
The simple password manager for geeks, built with Rust.

Rooster Rooster is a simple password manager for geeks (it works in the terminal). Rooster is made available free of charge. You can support its devel

Conrad Kleinespel 131 Dec 25, 2022
A simple password manager written in rust

Passman - A password manager written in rust. How to use?: USAGE: passman option Currently available options are: new - initalize passman with a new m

Strawkage 7 Aug 26, 2021
A simple password manager written in Rust

ripasso A simple password manager written in Rust. The root crate ripasso is a library for accessing and decrypting passwords stored in pass format (G

Joakim Lundborg 550 Dec 30, 2022
Ruo is a dictionary-based password cracker written in rust 🦀 .

Ruo is a dictionary-based password cracker written in rust ?? . The primary purpose is to crack weak hashes/commonly used passwords.

Asjid Kalam 10 Mar 6, 2022
A password manager coded in rust

pasman A password manager coded in rust Install Dependency rust Shell git clone https://github.com/AMTitan/pasman.git cd pasman cargo build --release

Arthur Melton 4 Nov 8, 2021
Rust-based password mutator for brute force attacks

PWFuzz-RS A Rust-based password mutator for brute force attacks Disclaimer This tool works, but was mainly an experiment. Please do not expect frequen

Michael Taggart 6 Oct 31, 2022