Class: Passlib::SHA1Crypt
- Defined in:
- lib/passlib/sha1_crypt.rb
Overview
SHA1-crypt is a legacy algorithm. It is supported here for verifying existing hashes and migrating users to a stronger scheme. Do not use it for new hashes.
Handles SHA1-crypt password hashing (NetBSD’s crypt-sha1).
SHA1-crypt applies iterated HMAC-SHA1 to derive a password hash. It was designed by Simon Gerraty for NetBSD as an alternative to DES-crypt that supports long passwords. It is a legacy algorithm — supported here for verifying existing hashes and migrating users to a stronger scheme.
Hash format: $sha1$<rounds>$<salt>$<checksum>
-
rounds— decimal integer, iteration count (1–4,294,967,295) -
salt— 1–64 characters from./0-9A-Za-z(default: 8 random chars) -
checksum— 28 characters from./0-9A-Za-z
This format is compatible with the sha1_crypt as implemented in Python’s passlib.
Constant Summary
Constants included from Internal::DSL
Instance Attribute Summary
Attributes inherited from Password
Class Method Summary collapse
-
.create(secret, **options) ⇒ SHA1Crypt
Creates a new SHA1-crypt hash.
Instance Method Summary collapse
-
#create_comparable(secret) ⇒ SHA1Crypt
A new instance hashed with the same salt and rounds.
-
#upgrade? ⇒ Boolean
trueif the stored iteration count differs from the configured target.
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) ⇒ SHA1Crypt
Creates a new SHA1-crypt hash.
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 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/passlib/sha1_crypt.rb', line 38 class SHA1Crypt < Password register :sha1_crypt, mcf: "sha1" :rounds, :salt HASH_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" DEFAULT_ROUNDS = 480_000 MIN_ROUNDS = 1 MAX_ROUNDS = 4_294_967_295 MAX_SALT_LEN = 64 # Byte-index groups for encoding the 20-byte SHA1 digest into 28 characters. # The last group wraps around to reuse byte 0, following the sha1crypt spec. ENCODE_OFFSETS = [ [0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14], [15, 16, 17], [18, 19, 0], ].freeze LOAD_PATTERN = %r{\A\$sha1\$(\d+)\$([./0-9A-Za-z]{1,64})\$([./0-9A-Za-z]{28})\z} private_constant :HASH_CHARS, :DEFAULT_ROUNDS, :MIN_ROUNDS, :MAX_ROUNDS, :MAX_SALT_LEN, :ENCODE_OFFSETS, :LOAD_PATTERN # @param secret [String] the plaintext password to re-hash # @return [SHA1Crypt] a new instance hashed with the same salt and rounds def create_comparable(secret) self.class.create(secret, salt: @salt, rounds: @rounds) end # @return [Boolean] +true+ if the stored iteration count differs from the # configured target def upgrade? @rounds != (config.rounds || DEFAULT_ROUNDS).clamp(MIN_ROUNDS, MAX_ROUNDS) end private def create(secret) @rounds = (config.rounds || DEFAULT_ROUNDS).clamp(MIN_ROUNDS, MAX_ROUNDS) @salt = (config.salt || random_salt)[0, MAX_SALT_LEN] "$sha1$#{@rounds}$#{@salt}$#{sha1_digest(secret)}" end def load(string) m = LOAD_PATTERN.match(string) or raise ArgumentError, "invalid sha1_crypt hash: #{string.inspect}" @rounds = m[1].to_i @salt = m[2] string end def sha1_digest(secret) key = secret.encode("UTF-8").b digest = OpenSSL::HMAC.digest("SHA1", key, "#{@salt}$sha1$#{@rounds}") (@rounds - 1).times { digest = OpenSSL::HMAC.digest("SHA1", key, digest) } encode(digest) end # Encodes the 20-byte digest into 28 characters using the sha1crypt # big-endian 24-bit grouping with wrap-around for the final group. def encode(digest) b = digest.bytes ENCODE_OFFSETS.map { |i, j, k| v = (b[i] << 16) | (b[j] << 8) | b[k] 4.times.map { c = HASH_CHARS[v & 0x3f]; v >>= 6; c }.join }.join end def random_salt Internal.random_bytes(6).bytes.map { HASH_CHARS[it & 63] }.join end end |
Instance Method Details
#create_comparable(secret) ⇒ SHA1Crypt
Returns a new instance hashed with the same salt and rounds.
62 63 64 |
# File 'lib/passlib/sha1_crypt.rb', line 62 def create_comparable(secret) self.class.create(secret, salt: @salt, rounds: @rounds) end |
#upgrade? ⇒ Boolean
Returns true if the stored iteration count differs from the configured target.
68 69 70 |
# File 'lib/passlib/sha1_crypt.rb', line 68 def upgrade? @rounds != (config.rounds || DEFAULT_ROUNDS).clamp(MIN_ROUNDS, MAX_ROUNDS) end |