Class: Passlib::BcryptSHA256

Inherits:
Password show all
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.

Examples:

hash = Passlib::BcryptSHA256.create("hunter2")
hash.verify("hunter2")  # => true
hash.to_s               # => "$bcrypt-sha256$v=2,t=2b,r=12$..."

Constant Summary

Constants included from Internal::DSL

Internal::DSL::Config

Instance Attribute Summary

Attributes inherited from Password

#config, #string

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Password

available?, #initialize, #inspect, load, #pretty_print, #verify

Methods included from Internal::DSL

#identifier

Constructor Details

This class inherits a constructor from Passlib::Password

Class Method Details

.create(secret, **options) ⇒ BcryptSHA256

Creates a new bcrypt-sha256 hash.

Parameters:

  • secret (String)

    the plaintext password

Options Hash (**options):

  • :cost (Integer)

    bcrypt cost factor, 4–31 (default: BCrypt::Engine::DEFAULT_COST)

  • :salt (String)

    custom bcrypt salt string (normally auto-generated; must be in standard bcrypt format $2b$NN$<22chars>)

Returns:



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"
  options :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.

Parameters:

  • secret (String)

    the plaintext password

Returns:



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.

Returns:

  • (Boolean)

    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