Class: Passlib::BcryptSHA256
- Defined in:
- lib/passlib/bcrypt_sha256.rb
Overview
Handles bcrypt-sha256 password hashing via the bcrypt gem.
A hybrid scheme that works around bcrypt’s 72-byte password truncation limit: the password is first run through HMAC-SHA256 (keyed with the bcrypt salt), the resulting 32-byte digest is base64-encoded, and the base64 string is then hashed with standard bcrypt. Passwords of any length are handled correctly, and the output is a self-contained MCF string that embeds all parameters needed for verification.
Hash format: $bcrypt-sha256$v=2,t=2b,r=12$<salt22>$<digest31>
This format is compatible with Python’s passlib.hash.bcrypt_sha256.
Constant Summary
Constants included from Internal::DSL
Instance Attribute Summary
Attributes inherited from Password
Class Method Summary collapse
-
.create(secret, **options) ⇒ BcryptSHA256
Creates a new bcrypt-sha256 hash.
Instance Method Summary collapse
-
#create_comparable(secret) ⇒ BcryptSHA256
Re-hashes
secretwith the same salt and cost. -
#upgrade? ⇒ Boolean
trueif the stored cost differs from the configured cost.
Methods inherited from Password
available?, #initialize, #inspect, load, #pretty_print, #verify
Methods included from Internal::DSL
Constructor Details
This class inherits a constructor from Passlib::Password
Class Method Details
.create(secret, **options) ⇒ BcryptSHA256
Creates a new bcrypt-sha256 hash.
33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/passlib/bcrypt_sha256.rb', line 33 class BcryptSHA256 < Password identifier :bcrypt_sha256 external "bcrypt", "~> 3.0" register mcf: "bcrypt-sha256" :cost, :salt FORMAT = /\A\$bcrypt-sha256\$v=2,t=2b,r=(\d+)\$([.\/A-Za-z0-9]{22})\$([.\/A-Za-z0-9]{31})\z/ private_constant :FORMAT # Re-hashes +secret+ with the same salt and cost. # @param secret [String] the plaintext password # @return [BcryptSHA256] def create_comparable(secret) = self.class.create(secret, salt: @bcrypt_salt) # @return [Boolean] +true+ if the stored cost differs from the configured cost def upgrade? @cost != (config.cost || ::BCrypt::Engine::DEFAULT_COST) end private def load(string) m = FORMAT.match(string) or raise ArgumentError, "invalid bcrypt-sha256 hash: #{string.inspect}" @cost = m[1].to_i @bcrypt_salt = "$2b$#{m[1].rjust(2, "0")}$#{m[2]}" string end def create(secret) cost = config.cost || ::BCrypt::Engine::DEFAULT_COST @bcrypt_salt = config.salt || ::BCrypt::Engine.generate_salt(cost).sub(/\A\$2[a-z]\$/, "$2b$") parts = @bcrypt_salt.split("$") # ["", "2b", "NN", "22chars"] @cost = parts[2].to_i salt22 = parts[3] digest31 = bcrypt_digest(secret, @bcrypt_salt) "$bcrypt-sha256$v=2,t=2b,r=#{@cost}$#{salt22}$#{digest31}" end # Computes the bcrypt digest of the HMAC-SHA256 pre-hash. # # 1. HMAC-SHA256(key: bcrypt_salt, msg: UTF-8 password) → 32 bytes # 2. Standard base64-encode → 44-char ASCII string (with = padding) # 3. BCrypt-hash that string with the same salt → take the last 31 chars def bcrypt_digest(secret, bcrypt_salt) hmac = OpenSSL::HMAC.digest("SHA256", bcrypt_salt, secret.encode("UTF-8")) encoded = [hmac].pack("m0") # standard base64, no newlines, with = padding ::BCrypt::Engine.hash_secret(encoded, bcrypt_salt)[-31..] end end |
Instance Method Details
#create_comparable(secret) ⇒ BcryptSHA256
Re-hashes secret with the same salt and cost.
45 |
# File 'lib/passlib/bcrypt_sha256.rb', line 45 def create_comparable(secret) = self.class.create(secret, salt: @bcrypt_salt) |
#upgrade? ⇒ Boolean
Returns true if the stored cost differs from the configured cost.
48 49 50 |
# File 'lib/passlib/bcrypt_sha256.rb', line 48 def upgrade? @cost != (config.cost || ::BCrypt::Engine::DEFAULT_COST) end |