{"id":13438896,"url":"https://github.com/racum/rust-djangohashers","last_synced_at":"2026-04-04T12:57:55.655Z","repository":{"id":49535250,"uuid":"46122505","full_name":"Racum/rust-djangohashers","owner":"Racum","description":"A Rust port of the password primitives used in Django Project.","archived":false,"fork":false,"pushed_at":"2025-01-05T12:33:20.000Z","size":186,"stargazers_count":57,"open_issues_count":1,"forks_count":8,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-07T14:18:26.720Z","etag":null,"topics":["algorithm","argon2","bcrypt","cryptography","django","hashes","password","password-hash","pbkdf2","rust","rust-port"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Racum.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2015-11-13T13:07:27.000Z","updated_at":"2025-03-04T21:19:13.000Z","dependencies_parsed_at":"2025-01-05T13:25:02.526Z","dependency_job_id":"c5e97d45-dddb-4f3f-a44e-8531b21998bf","html_url":"https://github.com/Racum/rust-djangohashers","commit_stats":{"total_commits":123,"total_committers":3,"mean_commits":41.0,"dds":0.1382113821138211,"last_synced_commit":"a84e33609c9927d5715518c3b5cbfd35f46a8d16"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Racum%2Frust-djangohashers","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Racum%2Frust-djangohashers/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Racum%2Frust-djangohashers/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Racum%2Frust-djangohashers/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Racum","download_url":"https://codeload.github.com/Racum/rust-djangohashers/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244565654,"owners_count":20473316,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["algorithm","argon2","bcrypt","cryptography","django","hashes","password","password-hash","pbkdf2","rust","rust-port"],"created_at":"2024-07-31T03:01:09.443Z","updated_at":"2026-04-04T12:57:55.649Z","avatar_url":"https://github.com/Racum.png","language":"Rust","funding_links":[],"categories":["Libraries","库 Libraries","库"],"sub_categories":["Cryptography","密码学 Cryptography","加密"],"readme":"# Rust DjangoHashers\n\nA Rust port of the password primitives used in [Django Project](https://www.djangoproject.com).\n\nDjango'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.\n\nThis 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.\n\n## TL;DR\n\nContent of `examples/tldr.rs`:\n\n```rust\nextern crate djangohashers;\nuse djangohashers::*;\n\nfn main() {\n    let encoded = make_password(\"K2jitmJ3CBfo\");\n    println!(\"Hash: {:?}\", encoded);\n    let is_valid = check_password(\"K2jitmJ3CBfo\", \u0026encoded).unwrap();\n    println!(\"Is valid: {:?}\", is_valid);\n}\n```\n\nOutput:\n\n```\n$ cargo run --quiet --example tldr\nHash: \"pbkdf2_sha256$1000000$pQE1Pfr1CUpS$gDLIrbspb7isluj1zxcItegXxrE1BJP3sdg61S+72rw=\"\nIs valid: true\n```\n\n\n## Installation\n\nAdd the dependency to your `Cargo.toml`:\n\n```toml\n[dependencies]\ndjangohashers = \"^1.8\"\n```\n\nReference and import:\n\n```rust\nextern crate djangohashers;\n\n// Everything (it's not much):\nuse djangohashers::*;\n\n// Or, just what you need:\nuse djangohashers::{check_password, make_password, Algorithm};\n```\n\n## Compiling Features\n\nBy default all the hashers are enabled, but you can pick only the hashers that you need to avoid unneeded dependencies.\n\n* `default`: all hashers.\n* `with_pbkdf2`: only **PBKDF2** and **PBKDF2SHA1**.\n* `with_argon2`: only **Argon2**.\n* `with_scrypt`: only **Scrypt**.\n* `with_bcrypt`: only **BCrypt** and **BCryptSHA256**.\n* `with_legacy`: only **SHA1**, **MD5**, **UnsaltedSHA1**, **UnsaltedMD5** and **Crypt**.\n* `fpbkdf2`: enables **Fast PBKDF2** (requires OpenSSL, see below).\n* `fuzzy_tests`: only for development, enables fuzzy tests.\n\n## Fast PBKDF2 Version\n\nDepending 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.\n\n### Installation\n\nAdd the dependency to your `Cargo.toml` declaring the feature:\n\n```toml\n[dependencies.djangohashers]\nversion = \"^1.8\"\nfeatures = [\"fpbkdf2\"]\n```\n\nYou 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:\n\n```\n$ brew install openssl\n$ export LIBRARY_PATH=\"$(brew --prefix openssl)/lib\"\n$ export CFLAGS=\"-I$(brew --prefix openssl)/include\"\n$ cargo ...\n```\n\nFor 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.\n\n### Performance\n\nOn a Apple M4 Pro:\n\nMethod  | Encode or Check | Performance\n------- | --------------- | -------\nDjango 5.2.0 on Python 3.13.2 | 136ms | 100% (baseline)\ndjangohashers with ring::pbkdf2 (default) | 77ms | 56.6% 🐇\ndjangohashers with fastpbkdf2 | 49ms | 36.0% 🐇\n\nReplicate test above with Docker:\n\n```\n$ docker build -t rs-dj-hashers-profile .\n...\n\n$ docker run -t rs-dj-hashers-profile\nHashing time: 136ms (Python 3.13.2, Django 5.2.0).\nHashing time: 77ms (Vanilla PBKDF2).\nHashing time: 49ms (Fast PBKDF2).\n```\n\n## Compatibility\n\nDjangoHashers passes all relevant unit tests from Django 1.4 to 6.0 (and betas up to 6.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).\n\nWhat is **not** covered:\n\n* Upgrade/Downgrade callbacks.\n* Any 3rd-party hasher outside Django's code.\n* Some tests that makes no sense in idiomatic Rust.\n\n## Usage\n\n[API Documentation](https://docs.rs/djangohashers/), thanks to **docs.rs** project!\n\n### Verifying a Hashed Password\n\nFunction signatures:\n\n```rust\npub fn check_password(password: \u0026str, encoded: \u0026str) -\u003e Result\u003cbool, HasherError\u003e {}\npub fn check_password_tolerant(password: \u0026str, encoded: \u0026str) -\u003e bool {}\n```\n\nComplete version:\n\n```rust\nlet password = \"KRONOS\"; // Sent by the user.\nlet encoded = \"pbkdf2_sha256$24000$...\"; // Fetched from DB.\n\nmatch check_password(password, encoded) {\n    Ok(valid) =\u003e {\n        if valid {\n            // Log the user in.\n        } else {\n            // Ask the user to try again.\n        }\n    }\n    Err(error) =\u003e {\n        // Deal with the error.\n    }\n}\n```\n\nPossible Errors:\n\n* `HasherError::UnknownAlgorithm`: anything not recognizable as an algorithm.\n* `HasherError::BadHash`: Hash string is corrupted.\n* `HasherError::InvalidIterations`: number of iterations is not a positive integer.\n* `HasherError::EmptyHash`: hash string is empty.\n* `HasherError::InvalidArgon2Salt`: Argon2 salt should be Base64 encoded.\n\n\nIf you want to automatically assume all errors as *\"invalid password\"*, there is a shortcut for that:\n\n```rust\nif check_password_tolerant(password, encoded) {\n\t// Log the user in.\n} else {\n\t// Ask the user to try again.\n}\n```\n\n\n### Generating a Hashed Password\n\nFunction signatures:\n\n```rust\npub fn make_password(password: \u0026str) -\u003e String {}\npub fn make_password_with_algorithm(password: \u0026str, algorithm: Algorithm) -\u003e String {}\npub fn make_password_with_settings(password: \u0026str, salt: \u0026str, algorithm: Algorithm) -\u003e String {}\n```\n\nAvailable algorithms:\n\n* `Algorithm::PBKDF2` (default)\n* `Algorithm::PBKDF2SHA1`\n* `Algorithm::Argon2`\n* `Algorithm::Scrypt`\n* `Algorithm::BCryptSHA256`\n* `Algorithm::BCrypt`\n* `Algorithm::SHA1`\n* `Algorithm::MD5`\n* `Algorithm::UnsaltedSHA1`\n* `Algorithm::UnsaltedMD5`\n* `Algorithm::Crypt`\n\nThe algorithms follow the same Django naming model, minus the `PasswordHasher` suffix.\n\nUsing default settings (PBKDF2 algorithm, random salt):\n\n```rust\nlet encoded = make_password(\"KRONOS\");\n// Returns something like:\n// pbkdf2_sha256$24000$go9s3b1y1BTe$Pksk4EptJ84KDnI7ciocmhzFAb5lFoFwd6qlPOwwW4Q=\n```\n\nUsing a defined algorithm (random salt):\n\n```rust\nlet encoded = make_password_with_algorithm(\"KRONOS\", Algorithm::BCryptSHA256);\n// Returns something like:\n// bcrypt_sha256$$2b$12$e5C3zfswn.CowOBbbb7ngeYbxKzJePCDHwo8AMr/SZeZCoGrk7oue\n```\n\nUsing a defined algorithm and salt (not recommended, use it only for debug):\n\n```rust\nlet encoded = make_password_with_settings(\"KRONOS\", \"seasalt\", Algorithm::PBKDF2SHA1);\n// Returns exactly this (remember, the salt is fixed!):\n// pbkdf2_sha1$24000$seasalt$F+kiWNHXbMBcwgxsvSKFCWHnZZ0=\n```\n\n**Warning**: `make_password_with_settings` and `make_password_core` will both panic if salt is not only letters and numbers (`^[A-Za-z0-9]*$`).\n\n### Generating a Hashed Password based on a Django version\n\nDjango 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.\n\n```rust\nuse djangohashers::{Django, DjangoVersion};\n\nlet django = Django {version: DjangoVersion::V1_8};  // Django 1.8.\nlet encoded = django.make_password(\"KRONOS\");\n// Returns something like:\n// pbkdf2_sha256$20000$u0C1E8jrnAYx$7KIo/fAuBJpswQyL7pTxO06ccrSjGdIe7iSqzdVub1w=\n//               |||||\n// ...notice the 20000 iterations, used in Django 1.8.\n```\n\nAvailable versions:\n\n* `DjangoVersion::CURRENT` Current Django version (`6.0` for DjangoHashers `1.8.4`).\n* `DjangoVersion::V1_4` Django 1.4\n* `DjangoVersion::V1_5` Django 1.5\n* `DjangoVersion::V1_6` Django 1.6\n* `DjangoVersion::V1_7` Django 1.7\n* `DjangoVersion::V1_8` Django 1.8\n* `DjangoVersion::V1_9` Django 1.9\n* `DjangoVersion::V1_10` Django 1.10\n* `DjangoVersion::V1_11` Django 1.11\n* `DjangoVersion::V2_0` Django 2.0\n* `DjangoVersion::V2_1` Django 2.1\n* `DjangoVersion::V2_2` Django 2.2\n* `DjangoVersion::V3_0` Django 3.0\n* `DjangoVersion::V3_1` Django 3.1\n* `DjangoVersion::V3_2` Django 3.2\n* `DjangoVersion::V4_0` Django 4.0\n* `DjangoVersion::V4_1` Django 4.1\n* `DjangoVersion::V4_2` Django 4.2\n* `DjangoVersion::V5_0` Django 5.0\n* `DjangoVersion::V5_1` Django 5.1\n* `DjangoVersion::V5_2` Django 5.2\n* `DjangoVersion::V6_0` Django 6.0\n* `DjangoVersion::V6_1` Django 6.1\n\n### Verifying a Hash Format (pre-crypto)\n\nFunction signature:\n\n```rust\npub fn is_password_usable(encoded: \u0026str) -\u003e bool {}\n```\n\nYou can check if the password hash is properly formatted before running the expensive cryto stuff:\n\n```rust\nlet encoded = \"pbkdf2_sha256$24000$...\"; // Fetched from DB.\n\nif is_password_usable(encoded) {\n    // Go ahead.\n} else {\n    // Check your database or report an issue.\n}\n```\n\n## Contributing\n\n* Be patient with me, I’m new to Rust and this is my first project.\n* Don't go nuts with your *mad-rust-skillz*, legibility is a priority.\n* Please use [rustfmt](https://github.com/rust-lang-nursery/rustfmt) in your code.\n* Always include some test case.\n\n## License\n\nRust DjangoHashers is released under the **3-Clause BSD License**.\n\n**tl;dr**: *\"free to use as long as you credit me\"*.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fracum%2Frust-djangohashers","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fracum%2Frust-djangohashers","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fracum%2Frust-djangohashers/lists"}