src/checksums/bcrypt

bcrypt is a Blowfish-based password hashing algorithm that is designed to be adaptively expensive to provide resistance against brute force based attacks and additionally includes a salt for lookup table resistance.

Although bcrypt has been around for a long time, dating back to 1999, for many projects it is still a reasonable choice due to its adjustable cost factor that can provide security against all but the most well funded attackers.

This module's design is based loosely on Python's bcrypt module and supports generating the newer version 2b hashes as well as verifying the older 2a and the PHP equivalent of 2b called 2y.

Example:

import src/checksums/bcrypt

# Generate a salt with a specific cost factor and use it to hash a password.
let hashed = bcrypt("correct horse battery stape", generateSalt(8))

Example:

import src/checksums/bcrypt

# Verify a password against a known good hash from i.e. a database.
let knownGood = "$2b$06$LzUyyYdKBoEy9V4NTvxDH.O11KQP30/Zyp5pQAQ.0Cy89WnkD5Jjy"

assert verify("correct horse battery staple", knownGood)

Types

CostFactor = range[4 .. 31]
Adjustable cost factor. The value is a logarithm of 2, which means that a cost of 5 is twice as expensive as a cost of 4, and a cost of 16 is 2048 times more expensive than a cost of 5.
Hash = distinct HashBytes
A 192 bit hash value produced by the bcrypt function.
Salt = object
  costFactor*: CostFactor
A random 128 bit salt used to provide security against rainbow table attacks that also includes the bcrypt version and cost factor.
SaltedHash = tuple[salt: Salt, hash: Hash]

Procs

proc `$`(s: Hash): string {....raises: [], tags: [], forbids: [].}
Renders the given Hash into the canonical bcrypt-type Base64 representation.
proc `$`(s: Salt): string {....raises: [], tags: [], forbids: [].}
Renders the given Salt into the canonical bcrypt-type Base64 representation along with its version and cost factor information.
proc `$`(s: SaltedHash): string {....raises: [], tags: [], forbids: [].}
Renders the given SaltedHash into the canonical bcrypt-type Base64 representation resulting in the actual hash string to be stored.
proc bcrypt(password: openArray[char]; salt: Salt): SaltedHash {....raises: [],
    tags: [], forbids: [].}

Produces a SaltedHash from the given password string and salt.

Be careful when accepting a salt from a source outside of your control as a malicious user could pass in salts with a very high cost factor, resulting in denial of service attack.

Example:

let hashed = bcrypt("correct horse battery stape", generateSalt(8))
proc generateSalt(cost: CostFactor): Salt {....raises: ResourceExhaustedError,
    tags: [], forbids: [].}
Generates a new, random salt with the provided CostFactor. Only salts with subversion 2b are generated since it's the newest and default version of the reference bcrypt implementation.
proc parseSalt(salt: string): Salt {....raises: ValueError, tags: [], forbids: [].}

Parses a Salt from the given string (which may be a full bcrypt hash or only the preamble).

It accepts the 2a, 2b and 2y subversions.

Example:

# Parse full hash
let salt1 = parseSalt "$2b$06$LzUyyYdKBoEy9V4NTvxDH."

# Parse salt part
let salt2 = parseSalt "$2b$06$LzUyyYdKBoEy9V4NTvxDH.PvwrAArbP0DUvDUFf8ChnJl6/79lh3C"

assert $salt1 == "$2b$06$LzUyyYdKBoEy9V4NTvxDH."
assert $salt2 == "$2b$06$LzUyyYdKBoEy9V4NTvxDH."
proc verify(password: openArray[char]; knownGood: string): bool {.
    ...raises: [ValueError], tags: [], forbids: [].}

Verifies a given plaintext password against a hash from a known good source such as a database or other data storage.

Be careful when accepting a hash from a source outside of your control as a malicious user could pass salts with a very high cost factor, resulting in denial of service attack.

Example:

let knownGood = "$2b$06$LzUyyYdKBoEy9V4NTvxDH.O11KQP30/Zyp5pQAQ.0Cy89WnkD5Jjy"

assert verify("correct horse battery staple", knownGood)