mudge/argon2id
Folders and files
| Name | Name | Last commit date | ||
|---|---|---|---|---|
Repository files navigation
Ruby bindings to Argon2, the password-hashing function that won the 2015 Password Hashing Competition.
Current version: 0.10.0
Bundled Argon2 version: libargon2.1 (20190702)
Argon2id::Password.create("password").to_s#=> "$argon2id$v=19$m=19456,t=2,p=1$agNV6OfDL1OwE44WdrFCJw$ITrBwvCsW4b5GjgZuL67RCcvVMEWBWXtASc9TVyI3rY"password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")password == "password" #=> truepassword == "not password" #=> falsepassword.m_cost #=> 19456password.salt #=> "e-\xA7\x04U\x81\xA6{v\xF0x\xED\xCC\xD3\x96\xE3"Argon2 is a password-hashing function that summarizes the state of the art inthe design of memory-hard functions and can be used to hash passwords forcredential storage, key derivation, or other applications.
It has a simple design aimed at the highest memory filling rate and effectiveuse of multiple computing units, while still providing defense againsttradeoff attacks (by exploiting the cache and memory organization of therecent processors).
— Argon2
Argon2 was the winner of the 2015 Password Hashing Competition. Out of thethree Argon2 versions, use the Argon2id variant since it provides a balancedapproach to resisting both side-channel and GPU-based attacks.
— OWASP Password Storage Cheat Sheet
See also argon2-cffi's "Why 'just use bcrypt' Is Not the Best Answer (Anymore)".
Install argon2id as a dependency:
# In your Gemfilegem "argon2id"# Or without Bundlergem install argon2idInclude in your code:
require "argon2id"Hash a plain text password (e.g. from user input) withArgon2id::Password.create:
password = Argon2id::Password.create("opensesame")The encoded value of the resulting hash is available viaArgon2id::Password#to_s (ideal for persisting somewhere):
password.to_s#=> "$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU"By default, Argon2id::Password.create will use the second set of parametersrecommended by OWASP but these can beoverridden by passing keyword arguments to Argon2id::Password.create:
t_cost: the "time cost" given as a number of iterations (defaults to 2)m_cost: the "memory cost" given in kibibytes (defaults to 19 mebibytes)parallelism: the number of threads and compute lanes to use (defaults to 1)salt_len: the salt size in bytes (defaults to 16)output_len: the desired length of the hash in bytes (defaults to 32)
password = Argon2id::Password.create("opensesame", t_cost: 3, m_cost: 12288)password.to_s#=> "$argon2id$v=19$m=12288,t=3,p=1$uukIsLS6y6etvsgoN20kVg$exMvDX/P9exvEPmnZL2gZClRyMdrnqjqyysLMP/VUWA"If you want to override the parameters for all calls toArgon2id::Password.create, you can set them on Argon2id directly:
Argon2id.t_cost = 3Argon2id.m_cost = 12288Argon2id.parallelism = 1Argon2id.salt_len = 16Argon2id.output_len = 32To verify a password against a hash, use Argon2id::Password#==:
password = Argon2id::Password.create("opensesame")password == "opensesame" #=> truepassword == "notopensesame" #=> falseOr, if you only have the encoded hash (e.g. retrieved from storage):
password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")password == "opensesame" #=> truepassword == "notopensesame" #=> falseWarning
Argon2id::Password.new does not support hashes generated from other Argon2variants such as Argon2i and Argon2d.
For compatibility with bcrypt-ruby, Argon2id::Password#== is aliased to Argon2id::Password.is_password?:
password = Argon2id::Password.new("$argon2id$v=19$m=19456,t=2,p=1$ZS2nBFWBpnt28HjtzNOW4w$SQ+p+dIcWbpzWpZQ/ZZFj8IQkyhYZf127U4QdkRmKFU")password.is_password?("opensesame") #=> truepassword.is_password?("notopensesame") #=> falseCaution
Argon2id::Password#== only works if the plain text password is on the right, e.g. the following behaviour may be surprising:
password = Argon2id::Password.create("password")password == "password" #=> true"password" == password #=> falsepassword == password #=> falseIf you want to avoid this ambiguity, prefer the Argon2id::Password#is_password? alias instead.
The various parts of the encoded hash can be retrieved:
password = Argon2id::Password.new("$argon2id$v=19$m=256,t=2,p=1$c29tZXNhbHQ$nf65EOgLrQMR/uIPnA4rEsF5h7TKyQwu9U1bMCHGi/4")password.version #=> 19password.m_cost #=> 256password.t_cost #=> 2password.parallelism #=> 1password.salt #=> "somesalt"password.output#=> "\x9D\xFE\xB9\x10\xE8\v\xAD\x03\x11\xFE\xE2\x0F\x9C\x0E+\x12\xC1y\x87\xB4\xCA\xC9\f.\xF5M[0!\xC6\x8B\xFE"If you need to check ahead of time whether an encoded password hash is a valid Argon2id hash (e.g. if you're migrating between hashing functions and need to test what kind of password has been stored for a user), you can use Argon2id::Password.valid_hash? like so:
Argon2id::Password.valid_hash?("$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$CTFhFdXPJO1aFaMaO6Mm5c8y7cJHAph8ArZWb2GRPPc")#=> trueArgon2id::Password.valid_hash?("$2a$12$stsRn7Mi9r02.keRyF4OK.Aq4UWOU185lWggfUQfcupAi.b7AI/nS")#=> falseAny errors returned from Argon2 will be raised as Argon2id::Error, e.g.
Argon2id::Password.create("password", salt_len: 0)# Salt is too short (Argon2id::Error)If you're planning to use this with Active Record instead of Rails' ownbcrypt-basedhas_secure_password,you can use the following as a starting point:
require "argon2id"# Schema: User(name: string, password_digest:string)class User < ApplicationRecord attr_reader :password validates :password_digest, presence: true validates :password, confirmation: true, allow_blank: true def password=(unencrypted_password) if unencrypted_password.nil? @password = nil self.password_digest = nil elsif !unencrypted_password.empty? @password = unencrypted_password self.password_digest = Argon2id::Password.create(unencrypted_password) end end def authenticate(unencrypted_password) password_digest? && Argon2id::Password.new(password_digest).is_password?(unencrypted_password) && self end def password_salt Argon2id::Password.new(password_digest).salt if password_digest? endendThis can then be used like so:
user = User.new(name: "alice", password: "", password_confirmation: "diffpassword")user.save #=> false, password requireduser.password = "password"user.save #=> false, confirmation doesn't matchuser.password_confirmation = "password"user.save #=> trueuser.authenticate("notright") #=> falseuser.authenticate("password") #=> userUser.find_by(name: "alice")&.authenticate("notright") #=> falseUser.find_by(name: "alice")&.authenticate("password") #=> userThis gem requires any of the following to run:
- Ruby 3.1 to 4.0
- JRuby 9.4 to 10.0
- TruffleRuby 24.1
Note
The JRuby version of the gem usesJRuby-OpenSSL's implementation ofArgon2 while the others use the reference C implementation.
Where possible, a pre-compiled native gem will be provided for the following platforms:
- Linux
- macOS
x86_64-darwinandarm64-darwin - Windows 2022+
x64-mingw-ucrt - Java: any platform running JRuby 9.4 or higher
SHA256 checksums are included in the releasenotes for each version and can bechecked with sha256sum, e.g.
$ gem fetch argon2id -v 0.7.0Fetching argon2id-0.7.0-arm64-darwin.gemDownloaded argon2id-0.7.0-arm64-darwin$ sha256sum argon2id-0.7.0-arm64-darwin.gem26bba5bcefa56827c728222e6df832aef5c8c4f4d3285875859a1d911477ec68 argon2id-0.7.0-arm64-darwin.gemGPG signatures are attached to each release (theassets ending in .sig) and can be verified if you import our signing key0x39AC3530070E0F75 (or fetch itfrom a public keyserver, e.g. gpg --keyserver keyserver.ubuntu.com --recv-key 0x39AC3530070E0F75):
$ gpg --verify argon2id-0.7.0-arm64-darwin.gem.sig argon2id-0.7.0-arm64-darwin.gemgpg: Signature made Fri 8 Nov 13:45:18 2024 GMTgpg: using RSA key 702609D9C790F45B577D7BEC39AC3530070E0F75gpg: Good signature from "Paul Mucur <mudge@mudge.name>" [unknown]gpg: aka "Paul Mucur <paul@ghostcassette.com>" [unknown]gpg: WARNING: This key is not certified with a trusted signature!gpg: There is no indication that the signature belongs to the owner.Primary key fingerprint: 7026 09D9 C790 F45B 577D 7BEC 39AC 3530 070E 0F75The fingerprint should be as shown above or you can independently verify itwith the ones shown in the footer of https://mudge.name.
Warning
We strongly recommend using the native gems where possible to avoid the needfor compiling the C extension and its dependencies which will take longer andbe less reliable.
If you wish to compile the gem, you will need to explicitly install the ruby platform gem:
# In your Gemfile with Bundler 2.3.18+gem "argon2id", force_ruby_platform: true# With Bundler 2.1+bundle config set force_ruby_platform true# With older versions of Bundlerbundle config force_ruby_platform true# Without Bundlergem install argon2id --platform=rubyYou will need a full compiler toolchain for compiling Ruby C extensions (seeNokogiri's "The CompilerToolchain")plus the toolchain required for compiling the vendored version of Argon2.
- Thanks to Mike Dalessio for his advice andRuby C Extensions Explainedproject
All issues and suggestions should go to GitHubIssues.
This library is licensed under the BSD 3-Clause License, see LICENSE.
Copyright © 2024, Paul Mucur.
The source code of Argon2 is distributed in the gem. This code is copyright© 2015 Daniel Dinu, Dmitry Khovratovich (main authors), Jean-Philippe Aumassonand Samuel Neves, and dual licensed under the CC0 License and the Apache2.0 License.