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)
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.\"
curl https://sh.rustup.rs -sSf \| sh
pip install --upgrade pipor
pip install setuptools-rust. Note: setuptool-rust is not required if you have pip version 10.0 or above
pip 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
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
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)
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)
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
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)
RandomSaltis 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 default
RandomSaltwith length of 32 bytes, but you can use your own
RandomSaltof custom length. When you instantiate a
RandomSalt, 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)
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)
argonautica is licensed under either of:
at your option.