Idiomatic Argon2 password hashing for Python
Project description
argonautica-py
Overview
argonautica is a Python package for hashing passwords that uses the cryptographically-secure argon2 hashing algorithm.
Argon2 won the Password Hashing Competition in 2015, a several year project to identify a successor to bcrypt, scrypt, and other common hashing algorithms.
argonautica was built with a simple use-case in mind: hashing passwords for storage in a website's database. That said, it's also "feature-complete", meaning anything you can do with the cannonical C implementation of argon2 you can do with argonautica*.
* Indeed, argonautica has a feature that even the cannonical C implementation lacks, i.e. hashing passwords with secret keys (the C implementation implements this, but does not expose it publicly)
Alternatives
There are several Python packages that implement argon2, including the excellent passlib, which uses argon2_cffi, but...
-
AFAIK, argonautica is the only Python implementation of argon2 that supports hashing with secret keys. Not even the cannonical C implementation of argon2 exposes this feature publicly (it's in the code, but unfortunately not accessable via the public API).
-
argonautica is the only Python implementation of argon2 to use SIMD instructions to peform it's hashing algorithm, which means it can be quite fast. The downside is that you have to compile it for your specific machine (this is why the
pip install argonautica
process takes time). That said, on the developer's early 2014 Macbook Air, which has SIMD instruction through AVX2, argonautica runs ~30% faster than passlib on default settings. -
argonautica supports the latest argon2 variant: argon2id, which, unless you have a reason not to, you should be using. A number of Python implementations do not yet support this variant.
-
Finally, argonautica is the only Python implementation of argon2 written in Rust (as opposed to C or C++). Rust is a "systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety."
Requirements
- Python version 3.4 or higher (or PyPy version 3.5 or higher)
- Rust version 1.26 or higher
- LLVM version 3.9 or higher
Installation
- Rust:
- Follow the instructions here, which will just tell you to run the following command in your terminal and follow the on-screen instructions:
curl https://sh.rustup.rs -sSf \| sh
- Follow the instructions here, which will just tell you to run the following command in your terminal and follow the on-screen instructions:
- LLVM:
- argonautica:
pip install --upgrade pip
orpip install setuptools-rust
. Note: setuptool-rust is not required if you have pip version 10.0 or abovepip install argonautica
. Unfortunately, this step may take several minutes, as argonautica needs to compile it's Rust code for your specific CPU (due to its use of SIMD instructions). The upside, however, is that once compiled, argonautica should run blazingly fast
Usage
Hashing
from argonautica import Hasher
hasher = Hasher(secret_key='somesecret')
hash = hasher.hash(password='P@ssw0rd')
print(hash)
# 👆 prints a random hash as the defeault `Hasher` uses a random salt by default
Verifying
from argonautica import Verifier
verifier = Verifier(secret_key='somesecret')
is_valid = verifier.verify(
hash='$argon2id$v=19$m=4096,t=192,p=2$ULwasg5z5byOAork0UEhoTBVxIvAafKuceNz9NdCVXU$YxhaPnqRDys',
password='P@ssw0rd',
)
assert(is_valid)
Configuration
from argonautica import Hasher, Verifier
from argonautica.config import Backend, Variant, Version
hasher = Hasher(secret_key=None)
# 👆 A secret key (passed as a keyword argument) is required to instantiate a
# Hasher, a Verifier, or an Argon2, but you are allowed to pass `None`
# in order to forgo using a secret key (this is not recommended)
hasher.additional_data = None # Default is None
# 👆 Although rarely used, argon2 allows you to hash a password
# with not only salt and a secret key, but also with "additional data",
# which acts as a kind of secondary secret key. Like a secret key, it affects
# the outcome of the hash and is not stored in the string-encoded output, meaning
# later, to verify against a hash created with additional data, you will need to
# supply the same additional data manually to the Verifier (just like you have to
# do with a secret key). Again, this is rarely used.
hasher.backend = Backend.C # Default is Backend.C
# 👆 argonautica was designed to support multiple backends (meaning multiple
# implementations of the underlying argon2 algorithm). Currently only the
# C backend is supported, which uses the cannonical argon2 library written
# in C to actually do the work. In the future a Rust backend will also be
# supported, but, for the moment, you must use Backend.C, which is the
# default. Using Backend.Rust will result in an error (again, for the
# moment).
hasher.hash_len = 32 # Default is 32
# 👆 The hash length in bytes is configurable. The default is 32.
# This is probably a good number to use. 16 is also probably fine.
# You probably shouldn't go below 16
hasher.iterations = 192 # Default is 192
# 👆 Argon2 has a notion of "iterations" or "time cost". All else equal
# and generally speaking, the greater the number of iterations, the
# longer it takes to perform the hash and the more secure the resulting
# hash. More iterations basically means more CPU load. This and "memory
# size" (see below) are the two primary parameters to adjust in order
# to increase or decrease the security of your hash. The default is
# 192 iterations, which was chosen because, along with the default
# memory size of 4096, this leads to a hashing time of approximately
# 300 milliseconds on the early-2014 Macbook Air that is the developer's
# machine. If you're going to use argonautica in production, you should
# probably tweak this parameter (and the memory size parameter) in order
# to increase the time it takes to hash to the maximum you can
# reasonably allow for your use-case (e.g. to probably about 300-500
# milliseconds for the use-case of hashing user passwords for a website)
hasher.lanes = 2 # Default is multiprocessing.cpu_count()
# 👆 Argon2 can break up its work into one or more "lanes" during some parts of
# the hashing algorithm. If you configure it with multiple lanes and you also
# use multiple threads (see below) the hashing algorithm will performed its
# work in parallel in some parts, potentially speeding up the time it takes to
# produce a hash without diminishing the security of the result. By default,
# the number of lanes is set to the number of logical cores on your machine
hasher.memory_size = 4096 # Default is 4096
# 👆 Argon2 has a notion of "memory size" or "memory cost" (in kibibytes). All else
# equal and generally speaking, the greater the memory size, the longer it takes to
# perform the hash and the more secure the resulting hash. More memory size basically
# means more memory used. This and "iterations" (see above) are, again, generally
# speaking, the two parameters to adjust in order to increase or decrease the
# security of your hash. The default is 4096 kibibytes, which was chosen because,
# again, along with the default iterations of 192, this leads to a hashing time of
# approximately 300 milliseconds on the early-2014 Macbook Air that is the
# developer's machine. If you're going to use argonautica in production, you should
# probably tweak this parameter (and the iterations parameter) in order to increase
# the time it takes to hash to the maximum you can reasonably allow for your use-case
# (e.g. to probably about 300-500 milliseconds for the use-case of hashing user
# passwords for a website)
hasher.threads = 2 # Default is multiprocessing.cpu_count()
# 👆 If you have configured a Hasher to use more than one lane (see above), you
# can get the hashing algorithm to run in parallel during some parts of the
# computation by setting the number of threads to be greater than one as well,
# potentially speeding up the time it takes to produce a hash without diminishing
# the security of the result. By default, the number of threads is set to the number
# of logical cores on your machine. If you set the number of threads to a number
# greater than the number of lanes, `Hasher` will automatically reduce the number
# of threads to the number of lanes
hasher.variant = Variant.Argon2id # Default is Variant.Argon2id
# 👆 Argon2 has three variants: Argon2d, Argon2i, and Argon2id. Here is how these
# variants are explained in the RFC: "Argon2 has one primary variant: Argon2id,
# and two supplementary variants: Argon2d and Argon2i. Argon2d uses data-dependent
# memory access, which makes it suitable for ... applications with no threats from
# side-channel timing attacks. Argon2i uses data-independent memory access, which
# is preferred for password hashing and password-based key derivation. Argon2id
# works as Argon2i for the first half of the first iteration over the memory, and
# as Argon2d for the rest, thus providing both side-channel attack protection and
# brute-force cost savings due to time-memory tradeoffs." If you do not know which
# variant to use, use the default, which is Argon2id
hasher.version = Version._0x13 # Default is Version._0x13
# 👆 Argon2 has two versions: 0x10 and 0x13. The latest version is 0x13 (as of 5/18).
# Unless you have a very specific reason not to, you should use the latest
# version (0x13), which is also the default
hash = hasher.hash(
password='P@ssw0rd',
salt='somesalt', # You can set your own salt, or use the default: RandomSalt(32)
)
assert(hash == '$argon2id$v=19$m=4096,t=192,p=2$c29tZXNhbHQ$8nD3gRm+NeOcIiIrlnzDAdnK4iD+K0mVqFXowGs13M4')
verifier = Verifier(secret_key=None)
verifier.additional_data = None # As with Hasher, you can configure a Verifier's additional data
verifier.backend = Backend.C # As with Hasher, you can configure a Verifier's backend
verifier.threads = 2 # As with Hasher, you can configure a Verifier's threads
is_valid = verifier.verify(
hash=hash,
password='P@ssw0rd'
)
assert(is_valid)
Miscellaneous
mypy
- argonautica uses mypy type annotations everywhere in the code, which, in the author's humble opinion, is a very useful form of documentation; so if you're ever confused about what types to use for arguments, just pop open the code and take a look at the function signatures.
Argon2
Argon2
is a convenience class that holds both a Hasher
and a Verifier
. If you'd like to use just one class that knows how both to hash and to verify, instantiate an Argon2
. It works essentially the same way as Hasher
and Verifier
do.
from argonautica import Argon2
argon2 = Argon2(secret_key='somesecret')
hash = argon2.hash(password='P@ssw0rd')
print(hash)
is_valid = argon2.verify(hash=hash, password='P@ssw0rd')
assert(is_valid)
RandomSalt
RandomSalt
is a special kind of salt that will create new random salt bytes before each hash. A RandomSalt knows its length (in number of bytes). The defaultHasher
uses aRandomSalt
with length of 32 bytes, but you can use your ownRandomSalt
of custom length. When you instantiate aRandomSalt
, the constructor takes a length, e.g.my_random_salt = RandomSalt(16)
from argonautica import Hasher
from argonautica.data import RandomSalt
hasher = Hasher(
salt=RandomSalt(16),
# 👆 Here we're using a RandomSalt of length of 16 bytes
# instead of the default, which is a RandomSalt of length 32 bytes
secret_key="somesecret"
)
hash = hasher.hash(password='P@ssw0rd')
print(hash)
HashRaw
- Hashing with argonautica produces a string-encoded hash, but sometimes you might want the "raw material" behind this hash, i.e. the raw hash bytes, the raw salt bytes, or raw parameters, which are the three component parts of a string-encoded hash. To obtain these raw parts...
from argonautica.utils import decode, HashRaw
hash = '$argon2id$v=19$m=4096,t=128,p=2$c29tZXNhbHQ$WwD2/wGGTuw7u4BW8sLM0Q'
# Create a `HashRaw` using the `decode` function
hash_raw = decode(hash)
# Pull out the raw parameters
iterations = hash_raw.iterations # 128
lanes = hash_raw.lanes # 2
memory_size = hash_raw.memory_size # 4096
variant = hash_raw.variant # Variant.Argon2id
version = hash_raw.version # Version._0x13
# Pull out the raw bytes
raw_hash_bytes = hash_raw.raw_hash_bytes # b'[\x00\xf6\xff\x01\x86N\xec;\xbb\x80V\xf2\xc2\xcc\xd1'
raw_salt_bytes = hash_raw.raw_salt_bytes # b'somesalt'
# Turn a `HashRaw` back into a string-encoded hash using the `encode` method
hash2 = hash_raw.encode()
assert(hash == hash2)
License
argonautica is licensed under either of:
at your option.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file argonautica-0.1.5.tar.gz
.
File metadata
- Download URL: argonautica-0.1.5.tar.gz
- Upload date:
- Size: 1.9 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 907428e19a3bd6e47666226786345ac9aa38ab68ce85e899b487b91f0d361c7c |
|
MD5 | c13f83ed4a2c08f22fb5d987272afee9 |
|
BLAKE2b-256 | 00ccb13451e97971a886f47869721077c3fe2480a129c30c1a54bf2e81ab3272 |