Hubbry Logo
BcryptBcryptMain
Open search
Bcrypt
Community hub
Bcrypt
logo
7 pages, 0 posts
0 subscribers
Be the first to start a discussion here.
Be the first to start a discussion here.
Contribute something
Bcrypt
Bcrypt
from Wikipedia
bcrypt
General
DesignersNiels Provos, David Mazières
First published1999
Derived fromBlowfish (cipher)
Detail
Digest sizes184 bits
Roundsvariable via cost parameter

bcrypt is a password-hashing function designed by Niels Provos and David Mazières. It is based on the Blowfish cipher and presented at USENIX in 1999.[1] Besides incorporating a salt to protect against rainbow table attacks, bcrypt is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power.

The bcrypt function is the default password hash algorithm for OpenBSD,[2][non-primary source needed] and was the default for some Linux distributions such as SUSE Linux.[3]

There are implementations of bcrypt in C, C++, C#, Embarcadero Delphi, Elixir,[4] Go,[5] Java,[6][7] JavaScript,[8] Perl, PHP, Ruby, Python, Rust,[9] V (Vlang),[10] Zig[11] and other languages.

Background

[edit]

Blowfish is notable among block ciphers for its expensive key setup phase. It starts off with subkeys in a standard state, then uses this state to perform a block encryption using part of the key, and uses the result of that encryption (which is more accurate at hashing) to replace some of the subkeys. Then it uses this modified state to encrypt another part of the key, and uses the result to replace more of the subkeys. It proceeds in this fashion, using a progressively modified state to hash the key and replace bits of state, until all subkeys have been set.

Provos and Mazières took advantage of this, and took it further. They developed a new key setup algorithm for Blowfish, dubbing the resulting cipher "Eksblowfish" ("expensive key schedule Blowfish"). The key setup begins with a modified form of the standard Blowfish key setup, in which both the salt and password are used to set all subkeys. There are then a number of rounds in which the standard Blowfish keying algorithm is applied, using alternatively the salt and the password as the key, each round starting with the subkey state from the previous round. In theory, this is no stronger than the standard Blowfish key schedule, but the number of rekeying rounds is configurable; this process can therefore be made arbitrarily slow, which helps deter brute-force attacks upon the hash or salt.

Description

[edit]

The input to the bcrypt function is the password string (up to 72 bytes), a numeric cost, and a 16-byte (128-bit) salt value. The salt is typically a random value. The bcrypt function uses these inputs to compute a 24-byte (192-bit) hash. The final output of the bcrypt function is a string of the form:

$2<a/b/x/y>$[cost]$[22 character salt][31 character hash]

For example, with input password abc123xyz, cost 12, and a random salt, the output of bcrypt is the string

$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
\__/\/ \____________________/\_____________________________/
Alg Cost      Salt                        Hash

Where:

  • $2a$: The hash algorithm identifier (bcrypt)
  • 12: Input cost (212 i.e. 4096 rounds)
  • R9h/cIPz0gi.URNNX3kh2O: A base-64 encoding of the input salt
  • PST9/PgBkqquzi.Ss7KIUgO2t0jWMUW: A base-64 encoding of the first 23 bytes of the computed 24 byte hash

The base-64 encoding in bcrypt uses the table ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789,[12] which differs from RFC 4648 Base64 encoding.

Versioning history

[edit]

$2$ (1999)

The original bcrypt specification defined a prefix of $2$. This follows the Modular Crypt Format[13] format used when storing passwords in the OpenBSD password file:

  • $1$: MD5-based crypt ('md5crypt')
  • $2$: Blowfish-based crypt ('bcrypt')
  • $sha1$: SHA-1-based crypt ('sha1crypt')
  • $5$: SHA-256-based crypt ('sha256crypt')
  • $6$: SHA-512-based crypt ('sha512crypt')

$2a$

The original specification did not define how to handle non-ASCII characters, nor how to handle a null terminator. The specification was revised to specify that when hashing strings:

  • the string must be UTF-8 encoded
  • the null terminator must be included

With this change, the version was changed to $2a$.[14]

$2x$, $2y$ (June 2011)

In June 2011, a bug was discovered in crypt_blowfish, a PHP implementation of bcrypt. It was mis-handling characters with the 8th bit set.[15] They suggested that system administrators update their existing password database, replacing $2a$ with $2x$, to indicate that those hashes are bad (and need to use the old broken algorithm). They also suggested the idea of having crypt_blowfish emit $2y$ for hashes generated by the fixed algorithm.

Nobody else, including Canonical and OpenBSD, adopted the idea of 2x/2y. This version marker change was limited to crypt_blowfish.

$2b$ (February 2014)

A bug was discovered in the OpenBSD implementation of bcrypt. It was using an unsigned 8-bit value to hold the length of the password.[14][16][17] For passwords longer than 255 bytes, instead of being truncated at 72 bytes the password would be truncated at the lesser of 72 or the length modulo 256. For example, a 260 byte password would be truncated at 4 bytes rather than truncated at 72 bytes. When OpenBSD fixed this issue, they changed the version to $2b$.

Algorithm

[edit]

The bcrypt function below encrypts the text "OrpheanBeholderScryDoubt" 64 times using Blowfish. In bcrypt the usual Blowfish key setup function is replaced with an expensive key setup (EksBlowfishSetup) function:

Function bcrypt
   Input:
      cost:     Number (4..31)                      log2(Iterations). e.g. 12 ==> 212 = 4,096 iterations
      salt:     array of Bytes (16 bytes)           random salt
      password: array of Bytes (1..72 bytes)        UTF-8 encoded password
   Output: 
      hash:     array of Bytes (24 bytes)

   //Initialize Blowfish state with expensive key setup algorithm
   //P: array of 18 subkeys (UInt32[18])
   //S: Four substitution boxes (S-boxes), S0...S3. Each S-box is 1,024 bytes (UInt32[256])
   P, S ← EksBlowfishSetup(password, salt, cost)   

   //Repeatedly encrypt the text "OrpheanBeholderScryDoubt" 64 times
   ctext"OrpheanBeholderScryDoubt"  //24 bytes ==> three 64-bit blocks
   repeat (64)
      ctext ← EncryptECB(P, S, ctext) //encrypt using standard Blowfish in ECB mode

   //24-byte ctext is resulting password hash
   return Concatenate(cost, salt, ctext)

Expensive key setup

[edit]

The bcrypt algorithm depends heavily on its "Eksblowfish" key setup algorithm, which runs as follows:

Function EksBlowfishSetup
   Input:
      password: array of Bytes (1..72 bytes)   UTF-8 encoded password
      salt:     array of Bytes (16 bytes)      random salt
      cost:     Number (4..31)                 log2(Iterations). e.g. 12 ==> 212 = 4,096 iterations
   Output: 
      P:        array of UInt32                array of 18 per-round subkeys
      S1..S4:   array of UInt32                array of four SBoxes; each SBox is 256 UInt32 (i.e. each SBox is 1 KiB)

   //Initialize P (Subkeys), and S (Substitution boxes) with the hex digits of pi 
   P, S ← InitialState() 
 
   //Permute P and S based on the password and salt     
   P, S ← ExpandKey(P, S, password, salt)

   //This is the "Expensive" part of the "Expensive Key Setup".
   //Otherwise the key setup is identical to Blowfish.
   repeat (2cost)
      P, S ← ExpandKey(P, S, password, 0)
      P, S ← ExpandKey(P, S, salt, 0)

   return P, S

InitialState works as in the original Blowfish algorithm, populating the P-array and S-box entries with the fractional part of in hexadecimal.

Expand key

[edit]

The ExpandKey function does the following:

Function ExpandKey
   Input:
      P:        array of UInt32               Array of 18 subkeys
      S1..S4:   UInt32[1024]                  Four 1 KB SBoxes
      password: array of Bytes (1..72 bytes)  UTF-8 encoded password
      salt:     Byte[16]                      random salt
   Output: 
      P:        array of UInt32               Array of 18 per-round subkeys
      S1..S4:   UInt32[1024]                  Four 1 KB SBoxes       
 
   //Mix password into the P subkeys array
   for n ← 1 to 18 do
      Pn ← Pn xor password[32(n-1)..32n-1] //treat the password as cyclic
 
   //Treat the 128-bit salt as two 64-bit halves (the Blowfish block size).
   saltHalf[0] ← salt[0..63]  //Lower 64-bits of salt
   saltHalf[1] ← salt[64..127]  //Upper 64-bits of salt

   //Initialize an 8-byte (64-bit) buffer with all zeros.
   block ← 0

   //Mix internal state into P-boxes   
   for n ← 1 to 9 do
      //xor 64-bit block with a 64-bit salt half
      blockblock xor saltHalf[(n-1) mod 2] //each iteration alternating between saltHalf[0], and saltHalf[1]

      //encrypt block using current key schedule
      block ← Encrypt(P, S, block) 
      P2nblock[0..31]      //lower 32-bits of block
      P2n+1block[32..63]  //upper 32-bits block

   //Mix encrypted state into the internal S-boxes of state
   for i ← 1 to 4 do
      for n ← 0 to 127 do
         block ← Encrypt(state, block xor saltHalf[(n-1) mod 2]) //as above
         Si[2n]   ← block[0..31]  //lower 32-bits
         Si[2n+1] ← block[32..63]  //upper 32-bits
    return state

Hence, ExpandKey(state, key, 0) is the same as regular Blowfish key schedule since all XORs with the all-zero salt value are ineffectual. ExpandKey(state, salt, 0) is similar, but uses the salt as a 128-bit key.

User input

[edit]

Many implementations of bcrypt truncate the password to the first 72 bytes, following the OpenBSD implementation.

The mathematical algorithm itself requires initialization with 18 32-bit subkeys (equivalent to 72 octets/bytes). The original specification of bcrypt does not mandate any one particular method for mapping text-based passwords from userland into numeric values for the algorithm. One brief comment in the text mentions, but does not mandate, the possibility of simply using the ASCII encoded value of a character string: "Finally, the key argument is a secret encryption key, which can be a user-chosen password of up to 56 bytes (including a terminating zero byte when the key is an ASCII string)."[1]

Note that the quote above mentions passwords "up to 56 bytes" even though the algorithm itself makes use of a 72 byte initial value. Although Provos and Mazières do not state the reason for the shorter restriction, they may have been motivated by the following statement from Bruce Schneier's original specification of Blowfish, "The 448 [bit] limit on the key size ensures that the [sic] every bit of every subkey depends on every bit of the key."[18]

Implementations have varied in their approach of converting passwords into initial numeric values, including sometimes reducing the strength of passwords containing non-ASCII characters.[19]

Comparison to other password hashing algorithms

[edit]

It is important to note that bcrypt is not a key derivation function (KDF). For example, bcrypt cannot be used to derive a 512-bit key from a password. At the same time, algorithms like pbkdf2, scrypt, and argon2 are password-based key derivation functions - where the output is then used for the purpose of password hashing rather than just key derivation.

Password hashing generally needs to complete < 1000 ms. In this scenario, bcrypt is stronger than pbkdf2, scrypt, and argon2.

  • PBKDF2: pbkdf2 is weaker than bcrypt. The commonly used SHA2 hashing algorithm is not memory-hard. SHA2 is designed to be extremely lightweight so it can run on lightweight devices (e.g. smart cards).[20] This means PBKDF2 is very weak for password storage, as commodity SHA-2 hashing hardware that can perform trillions of hashes per second is easily procured.[citation needed]
  • scrypt: scrypt is weaker than bcrypt for memory requirements less than 4 MB.[21] scrypt requires approximately 1000 times the memory of bcrypt to achieve a comparable level of defense against GPU based attacks (for password storage).
  • argon2: bcrypt is more lightweight than Argon2. This may pose a problem for some web applications where usage of Argon2 would require lowering the security parameters to an unacceptable level in order to still be performant. Specifically, Argon2 is less secure than bcrypt for run times less than 1 second (i.e., for common password authentication). Argon2 does not match or surpass bcrypt's strength until exceeding ≈1000ms runtimes.[citation needed] This may be unsuitable for password hashing, but is perfectly acceptable for key-derivation.[22] In some cases, Argon2 is recommended over bcrypt, if the security parameters are high enough.[23]
  • pufferfish2 is an evolution of bcrypt that uses a tunable memory footprint (like scrypt and argon2), rather than the fixed 4 KB memory footprint of bcrypt. Similar to scrypt or argon2, pufferfish2 gains its difficulty by using more memory. Unlike scrypt and argon2, pufferfish2 only operates in a CPU core's L2 cache. While scrypt and argon2 gain their memory hardness by randomly accessing lots of RAM, pufferfish2 limits itself to just the dedicated L2 cache available to a CPU core. This makes it even harder to implement in custom hardware than scrypt and argon2. The ideal memory footprint of pufferfish2 is the size of the cache available to a core (e.g. 1.25 MB for Intel Alder Lake[24]) This makes pufferfish2 much more resistant to GPU or ASIC.

Criticisms

[edit]

Maximum password length

[edit]

bcrypt has a maximum password length of 72 bytes. This maximum comes from the first operation of the ExpandKey function that uses XOR on the 18 4-byte subkeys (P) with the password:

P1..P18 ← P1..P18 xor passwordBytes

The password (which is UTF-8 encoded), is repeated until it is 72-bytes long. For example, a password of:

correct horse battery staple␀ (29 bytes)

Is repeated until it matches the 72-bytes of the 18 P per-round subkeys:

correct horse battery staple␀correct horse battery staple␀correct horse (72 bytes)

In the worst case a password is limited to 18 characters, when every character requires 4 bytes of UTF-8 encoding. For example:

𐑜𐑝𐑟𐑥𐑷𐑻𐑽𐑾𐑿𐑿𐑰𐑩𐑛𐑙𐑘𐑙𐑒𐑔 (18 characters, 72 bytes)

In 2024 a single-sign-on service by Okta, Inc. announced a vulnerability due to the password being concatenated after the username and the pair hashed with bcrypt, resulting in the password being ignored for logins with a long-enough username.[25]

Password hash truncation

[edit]

The bcrypt algorithm involves repeatedly encrypting the 24-byte text:

OrpheanBeholderScryDoubt (24-bytes)

This generates 24 bytes of ciphertext, e.g.:

85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9 a4 (24-bytes)

The canonical OpenBSD implementation truncates this to 23 bytes:[26]

85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9 (23-bytes)

It is unclear why the canonical implementation deletes 8-bits from the resulting password hash.[citation needed]

These 23 bytes become 31 characters when base-64 encoded:

fQAtluK7q2uGV7HcJYncfII3WbJvIai (31-characters)

base64 encoding alphabet

[edit]

The encoding used by the canonical OpenBSD implementation uses the same Base64 alphabet as crypt, which is ./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.[12] This means the encoding is not compatible with the more common RFC 4648.[citation needed]

See also

[edit]

References

[edit]
[edit]
Revisions and contributorsEdit on WikipediaRead on Wikipedia
from Grokipedia
Bcrypt is a password hashing function designed by Niels Provos and David Mazières, based on the Blowfish block cipher, and first presented at the 1999 Annual Technical Conference. It employs an expensive key setup process derived from the exponentially keyed Blowfish variant (eksblowfish), making the computation deliberately slow to thwart brute-force and dictionary attacks on password databases. The function includes a configurable cost parameter, or work factor, which determines the number of iterations—for example, 2^6 in the original implementation, and adjustable upward—to ensure adaptability as hardware performance advances, while automatically incorporating a 128-bit salt to prevent precomputed table attacks like rainbow tables. Developed under The Project to address limitations in earlier hashing methods like DES-based , bcrypt formalizes desirable properties for secure password schemes, including resistance to and key search optimizations. Its output format encodes the algorithm identifier ("2a", "2b", or "2y" variants), cost factor, salt, and hash in a modular, human-readable string of 60 characters, facilitating easy storage and verification. Bcrypt gained prominence through open-source implementations and has been integrated into numerous systems, including as the default hashing mechanism in 's library since version 2.1. Although newer memory-hard functions like and have emerged to counter GPU-accelerated attacks, bcrypt remains a cornerstone of password due to its balance of , simplicity, and widespread availability in libraries across languages such as , , and , as well as via the popular 'bcrypt' crate, which is stable and maintained for production use with no major known CVEs, though Argon2 is now preferred for new implementations. It is recommended by standards for legacy applications where modern alternatives are unavailable, with ongoing use in production environments emphasizing its enduring role in defending against offline .

History and Development

Origins and Creation

Bcrypt was developed by Niels Provos and David Mazières in the late 1990s as a secure password hashing mechanism for the OpenBSD operating system. The algorithm was first implemented by the pair and imported into the OpenBSD codebase on February 13, 1997, with its initial release as part of OpenBSD 2.1 in June 1997. This early integration marked bcrypt's debut in a production environment, serving as the default password hashing function for OpenBSD's authentication system. The creation of bcrypt stemmed from concerns over the growing vulnerability of traditional password hashing schemes to advances in computational hardware. Existing methods, such as the DES-based function in UNIX systems, were computationally inexpensive and thus susceptible to offline brute-force attacks as processor speeds increased. Provos and Mazières sought to design a "future-adaptable" scheme that could scale its computational cost to match evolving threats, emphasizing deliberate slowness to deter attackers while remaining feasible for legitimate authentication. To achieve this, they adapted the Blowfish block cipher's , transforming it into an expensive setup phase that dominates the hashing process. Bcrypt's formal introduction occurred in the 1999 Annual Technical Conference paper titled "A Future-Adaptable Password Scheme," where Provos and Mazières detailed the algorithm's design and provided for its core operations. By this time, the scheme had undergone refinement within , solidifying its role as a robust defense against and influencing subsequent security practices.

Initial Adoption and Implementations

Bcrypt was integrated into the operating system's login capabilities as the default password hashing mechanism starting with OpenBSD 2.1 in 1997, establishing it as a core security feature by 2000 and influencing its adoption across other systems, including and , where it became a supported option for password storage in /etc/shadow files. In 2009, version 5.3 introduced native support for bcrypt through an extension to the crypt() function, specifically via the CRYPT_BLOWFISH algorithm, which facilitated its integration into and spurred adoption in popular frameworks such as —via the bcrypt-ruby gem, first released in 2007—and Python, through libraries like py-bcrypt. Key milestones in bcrypt's dissemination included its availability in Linux distributions around 2006, notably through packages like libcrypt-blowfish-perl in , which provided Blowfish-based hashing compatible with bcrypt for system-level password management, and ongoing efforts to reference it in IETF drafts on password storage best practices, such as draft-ietf-kitten-password-storage, although it has not achieved full standardization. Early security evaluations, including the foundational 1999 analysis by creator Provos, affirmed bcrypt's design resistance to hardware-accelerated brute-force attacks by emphasizing its adaptable cost factor to counter increasing computational power.

Overview and Design Principles

Purpose and Core Objectives

Bcrypt was designed specifically as a hashing function to resist offline brute-force and attacks by incorporating a deliberately computationally expensive process, thereby making it impractical for attackers to crack large numbers of passwords even with significant resources. This approach leverages the expensive of the Blowfish to create a hash that scales with hardware capabilities. The core objectives of bcrypt include providing an adaptive work factor, which allows system administrators to increase the computational cost over time to counteract hardware improvements following , ensuring long-term resistance to faster processors. It also mandates the inclusion of a unique salt for each to prevent precomputation attacks, and its output format embeds the salt, work factor, and hash value in a modular, verifiable string for straightforward rehashing and validation during . In contrast to general-purpose cryptographic hash functions like or , which prioritize speed for tasks such as data integrity verification, bcrypt emphasizes deliberate slowness—typically tuned to require 100 milliseconds or more per hash on target hardware—to impose a significant burden on attackers while remaining feasible for legitimate login processes. Bcrypt emerged to replace insecure password storage practices common in early web applications, where fast, unsalted hashes like or enabled rapid offline cracking; a notable example is the 2012 breach, in which over 6.5 million unsalted password hashes were stolen and subsequently cracked en masse, exposing user accounts to compromise.

Key Features and Parameters

Bcrypt incorporates a configurable cost factor that determines the computational expense of the hashing process, expressed as the base-2 logarithm of the number of iterations performed during the expensive . This parameter, typically ranging from 4 to 31, allows the work factor to be tuned such that a value of n results in 2n rounds, enabling administrators to balance security against performance as hardware advances. A core feature is the generation of a 128-bit salt using cryptographically secure pseudorandom number generators, such as those seeded by kernel sources in implementations like OpenBSD's. This salt, which is unique per password, prevents precomputation attacks like rainbow tables by ensuring that identical passwords produce distinct hashes. The output of bcrypt is encoded in the Modular Crypt Format, a string beginning with &#36;2a$ (indicating the algorithm version), followed by the two-digit cost factor, a $ , 22 characters representing the base64-encoded 128-bit salt, followed by the 31-character base64-encoded 192-bit hash, embedding all necessary data for verification without separate storage. Many bcrypt libraries support automatic rehashing, where upon successful password verification, if the stored hash uses an outdated cost factor, the password is rehashed with the current higher cost and updated in storage, facilitating gradual security improvements without user intervention. Bcrypt's design, rooted in the Blowfish block cipher's key derivation without reliance on Merkle-Damgård constructions, inherently resists length-extension attacks that plague certain hash functions like SHA-256.

Version History

Original and 2a Release

The original bcrypt implementation was introduced in 1999 within the operating system, using the $2$ prefix in its output format. Developed by Niels Provos and David Mazières, it adapted the Blowfish block cipher—featuring standard 4 KB S-boxes—for password hashing through an expensive that incorporates 64 rounds of the cipher's core function to resist brute-force attacks. In 2011, the released the 2a variant to resolve a critical flaw in certain implementations, notably PHP's crypt() function, where embedded null bytes (or other 8-bit characters) within the password could cause premature termination in certain implementations like PHP's crypt(), leading to incorrect hashing. The 2a variant fixes this by properly handling 8-bit characters, including null bytes, treating the password as a binary string truncated to exactly 72 bytes without termination artifacts. The 2a specification retains the foundational Blowfish structure, including the 4 KB S-boxes and 64-round modified setup from the original, but adjusts the key expansion process to mitigate the 8-bit character without altering the overall model. This 2a release quickly established itself as the for bcrypt deployments, with libraries such as bcrypt-ruby integrating support for it by late 2011 to ensure and enhanced in production environments. The original $2$ prefix is now considered obsolete.

Subsequent Variants (2b, 2x, 2y)

In February 2014, the $2b variant was introduced in the [OpenBSD](/page/OpenBSD) bcrypt implementation to resolve a critical [bug](/page/Bug!) affecting [password](/page/Password) lengths. The original code stored the password length in an unsigned 8-bit [integer](/page/Integer) (char), which caused lengths exceeding 255 bytes to overflow and wrap around to a small value, resulting in only the first few bytes of the [password](/page/Password) being hashed instead of the intended first 72 bytes, thereby weakening security for very long [password](/page/Password)s. The fix expanded support for [password](/page/Password)s up to the full 72-byte effective limit, with the new &#36;2b prefix signaling the corrected behavior while preserving compatibility for existing $2a$ hashes generated under the buggy regime. The $2x and &#36;2y variants originated in June 2011 within the crypt_blowfish library, a PHP-specific bcrypt implementation maintained by Solar Designer. An earlier version of this library incorrectly processed passwords containing 8-bit characters (values 128–255, often non-ASCII like accented letters in international text), leading to hashing errors or unintended collisions in edge cases such as truncated or malformed outputs. To distinguish these problematic hashes without invalidating them, the $2x prefix was assigned to outputs from the buggy code, while &#36;2y marked the patched version that properly handles 8-bit characters; this allowed verification libraries to detect and apply the appropriate legacy logic. Unlike the changes, these prefixes were not adopted in the canonical implementation but remain supported in many libraries to ensure interoperability with legacy PHP-generated hashes. All bcrypt variants maintain functional equivalence in their core , producing identical hash outputs for the same password, salt, and factor when using corrected implementations. Modern libraries, such as those in Python's Passlib or Java's BCrypt, recognize prefixes from $2a, &#36;2b, $2x, and &#36;2y, automatically applying any necessary fixes (e.g., simulating for $2a verification or 8-bit mishandling for &#36;2x) to validate legacy hashes without recomputation issues. For new applications, the $2b or &#36;2y prefix is recommended to guarantee robust handling of long passwords and diverse character sets, minimizing risks from historical bugs.

Algorithm Details

High-Level Operation

Bcrypt operates as a password-hashing function that derives a fixed-size output from an input , a random salt, and a configurable cost factor, leveraging the to enforce computational expense. The algorithm begins by parsing the inputs: a , a 128-bit salt, and a (typically ranging from 4 to 31), where the logarithmically controls the number of iterations performed, specifically 2^ rounds. This setup ensures the hashing process is deliberately slow to resist brute-force attacks by increasing the time required per attempt. The process proceeds in several key steps. First, the password is preprocessed by truncating it to 72 bytes if longer or padding it with zero bytes if shorter to form a 72-byte array used as input to the key derivation. Next, an expensive key schedule (EKS), based on the Blowfish cipher's key setup, modifies the cipher's subkeys and S-boxes using the preprocessed password and salt; this phase, known as eksblowfish-setup, performs 2^cost iterations of subkey mixing to amplify computational cost early in the process. Following setup, the Blowfish cipher—now keyed with the derived state—is used in electronic codebook (ECB) mode to repeatedly encrypt a fixed 24-byte magic string, "OrpheanBeholderScryDoubt", for exactly 64 additional times, though the primary work is front-loaded in the EKS. The resulting 192-bit ciphertext is then truncated to 184 bits for encoding, encoded in a modified base-64 alphabet (using characters from "./0-9A-Za-z" to fit traditional crypt output constraints), and formatted as a string prefixed with "$2a$", the decimal cost value, the 22-character salt encoding, and the 31-character hash. This high-level flow can be outlined in pseudocode as follows:

function bcrypt(password, salt, cost): preprocessed_password = truncate_or_pad(password, 72 bytes) // Truncate to 72 bytes or pad with zeros state = eksblowfish_setup(cost, preprocessed_password, salt) // Expensive key schedule with 2^cost iterations ciphertext = "OrpheanBeholderScryDoubt" // 24-byte fixed plaintext for i = 1 to 64: ciphertext = blowfish_encrypt(state, ciphertext) // ECB mode encryption hash = encode_base64(truncate(ciphertext, 184 bits)) // Modified base-64 output = "&#36;2a$" + to_decimal(cost) + encode_base64(salt, 22 chars) + hash // 60-character result return output

function bcrypt(password, salt, cost): preprocessed_password = truncate_or_pad(password, 72 bytes) // Truncate to 72 bytes or pad with zeros state = eksblowfish_setup(cost, preprocessed_password, salt) // Expensive key schedule with 2^cost iterations ciphertext = "OrpheanBeholderScryDoubt" // 24-byte fixed plaintext for i = 1 to 64: ciphertext = blowfish_encrypt(state, ciphertext) // ECB mode encryption hash = encode_base64(truncate(ciphertext, 184 bits)) // Modified base-64 output = "&#36;2a$" + to_decimal(cost) + encode_base64(salt, 22 chars) + hash // 60-character result return output

The Blowfish cipher serves here as an iterated block cipher, with its 64-bit block size and variable key length enabling the adaptive slowdown without relying on the full encryption for primary security. This design prioritizes the EKS phase for most of the computational burden, making bcrypt suitable for password storage where verification involves recomputing the hash with the stored salt and cost.

Expensive Key Schedule (EKS)

The Expensive Key Schedule (EKS), also known as Eksblowfish, forms the foundational phase of bcrypt's operation by transforming the Blowfish cipher's into a deliberately time-consuming that integrates the password and salt to derive a secure set of subkeys. This modification ensures that the entire cipher state, including the 4 KB of S-boxes, must be fully recomputed for each unique password-salt combination, preventing efficient precomputation or attacks. By extending the standard Blowfish setup, EKS achieves its security through computational expense rather than secrecy, allowing the cost to scale with advancing hardware capabilities. The EKS process begins with the standard Blowfish key schedule initialization, where the 18 P-array entries (P1 to P18) and four 256-entry arrays are loaded with fixed constants derived from the fractional digits of π. These 1042 32-bit words (totaling 4168 bytes) form the initial subkey array. The preprocessed 72-byte password and the 128-bit salt are then used to modify this array via the ExpandKey function. In the initial call, the P-array is XORed with cycling chunks of the password, followed by a series of 64-bit block s where the input to each encryption (alternating between the two 64-bit halves of the salt XORed with the previous ) replaces pairs of subkeys, propagating through all 18 P-entries and then the 1024 entries (replacing two 32-bit words per encryption, for a total of 521 encryptions per ExpandKey call). This ensures dependence on both inputs across all subkeys, including the S-boxes which remain fixed in unmodified Blowfish. To enforce the cost, after the initial ExpandKey call with the salt and , the ExpandKey function is alternately called with the salt (and zero key) and then with the (and zero salt) for exactly 2^ additional iterations. In these repeated calls, the XOR step applies only when a non-zero key is provided (affecting the P-array), while the chain always updates the entire state using the provided salt parameter (or zero). This chained process propagates the influences of the and salt throughout the array across multiple iterations. The primary purpose of EKS is to inflate the key setup time, rendering it approximately 2cost2^{\text{cost}} times slower than a conventional while enforcing complete 4 KB reinitialization. This tunable expense, achieved through the iterative nature of the ExpandKey updates, directly contributes to bcrypt's resistance against brute-force and parallelized attacks without compromising the underlying cipher's strength.

Key Expansion and Cipher Setup

Following the expensive key schedule, the modified Blowfish cipher state is utilized to produce the final hash through a series of encryptions on a fixed input. The 192-bit constant "OrpheanBeholderScryDoubt"—equivalent to 24 bytes—is encrypted 64 times in Electronic Codebook (ECB) mode using the established cipher state. Each iteration takes the output ciphertext from the previous encryption as input, creating a chained effect that further mixes the derived keys without altering the subkey array itself. This process can be expressed in pseudocode as follows:

ctext ← "OrpheanBeholderScryDoubt" // 192-bit (24-byte) constant for i ← 1 to 64: ctext ← Encrypt<sub>ECB</sub>(state, ctext) // Encrypt entire 192 bits (3 × 64-bit blocks) output ← ctext // 24 bytes of final ciphertext (later truncated to 23 bytes for encoding)

ctext ← "OrpheanBeholderScryDoubt" // 192-bit (24-byte) constant for i ← 1 to 64: ctext ← Encrypt<sub>ECB</sub>(state, ctext) // Encrypt entire 192 bits (3 × 64-bit blocks) output ← ctext // 24 bytes of final ciphertext (later truncated to 23 bytes for encoding)

The resulting 24-byte value serves as the core of the bcrypt hash before encoding and concatenation with the salt and cost factor. Since Blowfish operates on 64-bit blocks, each full encryption of the 192-bit input requires three block encryptions, totaling 192 block operations across the 64 iterations.

Input Processing and Output Format

Password and Salt Handling

Bcrypt processes the input password as a sequence of bytes, typically encoded in to handle international characters, converting non-ASCII characters into their corresponding multi-byte representations before further processing. The password is then treated as a null-terminated C-style string and truncated to a maximum of 72 bytes, including the null terminator; any additional bytes are discarded. The salt in bcrypt is a randomly generated 128-bit value, selected uniformly to ensure uniqueness for each hashing operation and to thwart precomputation attacks like rainbow tables. This salt is incorporated directly into the hashing process and, for output purposes, encoded into 22 characters using a modified alphabet consisting of the characters ./0-9A-Za-z. Edge cases in password handling include empty inputs, which are processed as a single null byte (the terminator for a zero-length ), resulting in a consistent 1-byte input that produces a reproducible hash for a given salt but varies with different salts. Non-ASCII and special characters are preserved through byte encoding without alteration beyond the truncation limit. The resulting bcrypt hash is formatted as a fixed-length string: &#36;2y$<cost>$<22-character salt><31-character hash>, where $2y$ denotes the algorithm version (with &#36;2a$, &#36;2b$, or &#36;2x$ used in earlier variants), <cost> is a two-digit logarithmic factor (e.g., 10 for 2¹⁰ iterations), the salt occupies the next 22 characters, and the final 31 characters represent the -encoded 192-bit hash output. This structure allows efficient storage and verification while embedding all necessary parameters.

Cost Factor and Iteration Mechanism

The cost factor in bcrypt, also known as the work factor, is a tunable that determines the computational expense of the hashing process by specifying the number of iterations as 2cost2^{\text{cost}}. For instance, a of 10 results in 210=[1024](/page/1024)2^{10} = [1024](/page/1024) iterations of the key expansion setup. This exponential scaling allows bcrypt to adapt to advancing hardware capabilities, ensuring that hashing remains deliberately slow to thwart brute-force attacks. The iteration mechanism applies specifically to the key expansion loop in the eksblowfish setup, where the process alternates between the password-derived key and the salt for each of the 2[cost](/page/Cost)2^{\text{[cost](/page/Cost)}} rounds, without altering the core expensive itself. This design choice enables future-proofing by permitting incremental increases in the factor over time—for example, defaults of 6 to 8 in the late 1990s and early 2000s have evolved to 12 or higher in the to maintain security against faster processors. The sequential nature of these iterations inherently resists parallelization, making it difficult for attackers to leverage GPUs or for acceleration, unlike more parallel-friendly algorithms. In practice, modern implementations recommend a cost of 12, which typically yields a hashing time of around 250 milliseconds on contemporary hardware, balancing with acceptable delays. During verification, the stored hash is recomputed using the provided password, the embedded salt, and the original cost factor; a mismatch results in failure, and many systems respond by rehashing valid credentials with a higher cost for subsequent storage to enhance long-term protection.

Security Analysis

Resistance to Brute-Force Attacks

Bcrypt's primary defense against brute-force attacks lies in its deliberate computational intensity, achieved through an adaptive cost factor that controls the number of iterations in the Blowfish-based key setup. A standard cost factor of 12, recommended for balancing and , typically yields 2 to 5 hashes per second on modern CPUs, a stark contrast to general-purpose hashes like SHA-256, which can exceed 10^9 hashes per second on high-end GPUs. This slowness forces attackers to expend significant resources on each guess, with the cost factor tunable upward to counteract advances in hardware performance, such as those from . The inclusion of a unique 128-bit salt per further bolsters resistance by thwarting attacks, as precomputing tables for the full salt space (2^128 possibilities) is computationally infeasible. This per-user salting ensures that even identical passwords produce distinct hashes, eliminating efficiencies from offline or preimage assaults. Bcrypt's design also exhibits strong resistance to parallelization on GPUs and , owing to its irregular memory access patterns during the 4 KB key setup phase, which hinder efficient vectorization and cache exploitation on specialized hardware. Benchmarks from the 2015 confirm no practical side-channel vulnerabilities, such as timing or attacks, in standard implementations, validating its robustness under scrutiny. In real-world deployments, bcrypt has demonstrated resilience; for instance, during the disclosure of the 2012 Dropbox breach affecting 68 million accounts, the subset of passwords hashed with bcrypt resisted cracking despite exposure, unlike weaker hashes in the same dump. The Open Web Application Security Project () has endorsed bcrypt for password storage since its guidelines, citing its adaptive security as a cornerstone for protecting against offline brute-force attempts.

Comparison with Other Hashing Algorithms

Bcrypt represents a significant advancement over earlier cryptographic hash functions like and for password storage, primarily due to its incorporation of a per-password salt to thwart attacks and an adjustable cost factor that enforces computational slowness to resist brute-force attempts. In contrast, and are fast, general-purpose hashes lacking built-in salting or iteration mechanisms, making them highly vulnerable to offline and brute-force attacks even with added salts, as attackers can parallelize computations efficiently on modern hardware. Compared to PBKDF2, defined in RFC 2898, bcrypt employs a modified Blowfish cipher for its key schedule to achieve slowness through a simpler, single-purpose design rather than PBKDF2's reliance on iterated HMAC computations with underlying hashes like SHA-256. Both algorithms provide comparable resistance to brute-force attacks when tuned to similar computational costs, but bcrypt's integrated salting and fixed output format reduce the risk of implementation errors, such as improper salt handling or iteration counts, making it arguably easier to deploy securely in practice. Unlike , introduced by in 2009, bcrypt does not incorporate memory-hardness, relying instead on CPU-intensive operations that can be more readily accelerated on GPUs and , thereby exposing it to higher parallelization in attacks. addresses this by requiring substantial sequential memory access, enhancing resistance to hardware-optimized brute-force efforts and aiming for better ASIC deterrence through tunable memory parameters. Argon2, the winner of the 2015 , builds on these concepts with fully tunable parameters for time, memory, and parallelism, offering superior protection against GPU and ASIC attacks compared to bcrypt's fixed 4 KB memory usage. Modern benchmarks demonstrate 's effectiveness, where it can impose roughly twice the computational burden on GPUs relative to equivalently tuned bcrypt instances, due to its data-dependent memory access patterns that hinder efficient parallelization. The NIST SP 800-63B guidelines (2017, revised 2020) endorse as a baseline for password-based authenticators but accept bcrypt, , and as viable alternatives when configured to require at least 0.5 seconds of processing on verifier hardware.

Limitations and Criticisms

Password Length Restrictions

Bcrypt imposes a maximum password length of 72 bytes, including a null terminator, as defined in its original design. This limit stems from the algorithm's use of a modified Blowfish key schedule, where the password serves as the key material, and only the first 72 bytes are processed to initialize the subkeys efficiently. The restriction traces its roots to the Unix function's 8-character limit, derived from the 64-bit DES key (56 data bits plus 8 parity bits), which bcrypt extends for greater capacity while preserving compatibility with legacy systems. This length cap has notable security implications, especially for long . Passwords exceeding 72 bytes are silently truncated, causing the loss of from any trailing characters and resulting in identical hashes for inputs sharing the same prefix. Consequently, attackers can concentrate brute-force attacks on potential short prefixes, undermining the added intended from extended passphrases, such as multi-word combinations that surpass the byte threshold. For instance, a passphrase like "correct horse battery staple" followed by additional words may offer no extra protection beyond its initial 72 bytes. This truncation risk extends to non-password uses; for example, in October 2024, disclosed a where bcrypt's limit in generating cache keys from long usernames enabled unauthorized access. Bcrypt provides no built-in mitigations for this . To address it, applications should ideally restrict passwords to 72 bytes or fewer, or incorporate a server-side pepper to bolster protection against targeted attacks on truncated inputs. Another approach involves pre-hashing longer passwords with a fast, non-salted function like SHA-256 to fold the full length into a fixed-size input for bcrypt, though this requires careful implementation to avoid introducing vulnerabilities. Modern libraries, including versions from 7.4 (with enhanced around ), issue warnings about truncation during hashing to alert developers and promote awareness. Post-2020 analyses have increasingly emphasized the reduction for multi-word passphrases, critiquing the limit as an outdated constraint that encourages migration to unbounded algorithms like for handling diverse lengths without compromise.

Truncation and Encoding Issues

Bcrypt derives its final hash value from the 192-bit produced by encrypting a fixed ("OrpheanBeholderScryDoubt") using the Blowfish , where the key is derived from the and salt via the expensive (EKS). Although the Blowfish key schedule processes up to 448 bits, only the 192-bit is retained for the hash output, effectively truncating the result relative to the full internal state generated during . This design choice has been critiqued for potentially narrowing the security margin against certain attacks, such as theoretical collisions, though the probability remains negligible at 2^{-192} for random inputs, and Blowfish's strong diffusion properties ensure that the key schedule's computational cost dominates security. The hash, along with the salt and cost parameter, is then encoded into a using a custom base-64 consisting of the 64 characters ./0-9A-Za-z. This replaces the standard base-64's + with . to avoid common encoding conflicts, but retains /, which can introduce issues when bcrypt hashes are embedded in URLs, file paths, or configuration s without proper , as / may be interpreted as a by parsers. In &#36;2a$, this encoding has been noted to enable potential injection vulnerabilities in legacy systems or misconfigured parsers that fail to escape the output adequately, such as when hashes are directly concatenated into SQL queries or HTTP paths. Later s like &#36;2b$ and &#36;2y$ maintain the same but address unrelated flaws, preserving compatibility while mitigating other risks. Recent analyses since 2023 have highlighted encoding challenges in web and contexts, where unescaped bcrypt outputs can disrupt responses or database serialization if integrated into payloads without quoting, particularly in environments enforcing strict safety or validation. These concerns, while not unique to bcrypt, amplify the importance of context-aware encoding in modern web applications.

References

Add your contribution
Related Hubs
Contribute something
User Avatar
No comments yet.