module Crypto::Argon2

argon2id password hashes (RFC 9106), through the RustCrypto argon2 crate: what a server stores for a password, as a PHC string such as $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash> (the salt and the 32-byte hash in unpadded Base64), which names its parameters, so that stored hashes stay readable when the parameters change.

hash = Crypto::Argon2.hash("correct horse")
hash.start_with?("$argon2id$v=19$m=19456,t=2,p=1$")   # => true
Crypto::Argon2.verify?("correct horse", hash)         # => true
Crypto::Argon2.verify?("wrong horse", hash)           # => false
Crypto::Argon2.needs_rehash?(hash)                    # => false

A hash with Params::DEFAULT takes tens of milliseconds and 19 MiB (memory_kib), on a helper thread: only the calling task waits. Each hash at once holds its memory, so a server that hashes many passwords at once (logins) should bound how many it runs together.

Only argon2id, version 19, without a secret or associated data: verify? refuses argon2i and argon2d hashes. bcrypt and scrypt are not here; Digest::PBKDF2 is, where PBKDF2 is required.

<std:crypto>:168

Classes and modules

Class methods

def self.hash(password: String | Bytes, params: Crypto::Argon2::Params = …, salt: Bytes? = …) -> String

The argon2id PHC string of password with params, and salt (16 to 48 bytes, ArgumentError otherwise), a fresh random 16 bytes unless given. Two hashes of one password differ by their salts.

salt = Bytes.from((0...16).to_a)
cheap = Crypto::Argon2::Params.new(memory_kib: 64, iterations: 1, parallelism: 1)
Crypto::Argon2.hash("pw", params: cheap, salt: salt) == Crypto::Argon2.hash("pw", params: cheap, salt: salt)   # => true

<std:crypto>:235

def self.verify?(password: String | Bytes, hash: String) -> Bool

Whether password is the one hash (an argon2id PHC string, with the parameters it names) was made of: false for a wrong password, and the comparison takes the same time wherever they differ. Crypto::Error for a string that is not an argon2id v=19 hash (another scheme or variant, another version, a parameter other than m, t and p, one out of Params's bounds, a string that does not parse), so that stored garbage is noticed rather than taken for a wrong password; its message never includes the password. Runs on a helper thread, as hash does.

<std:crypto>:252

def self.needs_rehash?(hash: String, params: Crypto::Argon2::Params = …) -> Bool

Whether hash should be made again with params, the next time the password is at hand (after verify? succeeds): true when one of its parameters is below params's, and for a string of another scheme (a $pbkdf2-sha256$ one, argon2i), so that a caller rehashes whatever it finds. Crypto::Error for an argon2id string that does not parse.

old = "$argon2id$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0c2FsdA$fxbFVdPGPQ1NJoy87CaTabyrXOKZepZ9SGBFwPkPJ28"
Crypto::Argon2.needs_rehash?(old)                                  # => true
Crypto::Argon2.needs_rehash?(old, Crypto::Argon2::Params.of(old))  # => false
Crypto::Argon2.needs_rehash?("$pbkdf2-sha256$...")                 # => true

<std:crypto>:267