Introduction

The encryption algorithm I want to introduce to you today is called bcrypt. bcrypt is a password hash function designed by Niels Provos and David Mazières. It is based on the Blowfish password and was proposed on USENIX in 1999.

In addition to adding salt to resist rainbow table attacks, a very important feature of bcrypt is self-adaptability, which can ensure that the encryption speed is within a specific range. Even if the computing power of the computer is very high, you can increase the number of iterations. , Which slows down the encryption speed, which can resist brute force search attacks.

The bcrypt function is the default password hashing algorithm for OpenBSD and other systems, including some Linux distributions (such as SUSE Linux).

How bcrypt works

Let's review the encryption principle of Blowfish first. Blowfish first needs to generate the K array and S-box for encryption. It takes a certain amount of time for blowfish to generate the final K array and S-box. Each new key needs to be preprocessed with about 4 KB of text. Compared with other block cipher algorithms, this will be very slow. But once the generation is complete, or the key is unchanged, blowfish is still a very fast block encryption method.

Is there any benefit to being slow?

Of course there is, because for a normal application, the key is not often changed. So the preprocessing will only be generated once. It will be very fast when used later.

For a malicious attacker, each time a new key is tried requires a long preprocessing, so it is very uneconomical for the attacker to crack the blowfish algorithm. So blowfish can resist dictionary attacks.

Provos and Mazières took advantage of this and developed it further. They developed a new key setting algorithm for Blowfish, and called the resulting password "Eksblowfish" ("expensive key schedule Blowfish"). This is an improved algorithm for Blowfish. In the initial key setting of bcrypt, both salt and password are used to set the subkey. Then after a round of the standard Blowfish algorithm, by alternately using salt and password as the key, each round depends on the state of the previous wheel's key. Although theoretically, the strength of the bcrypt algorithm is not better than that of blowfish, but because the number of rounds to reset the key in bcrpyt is configurable, you can better resist brute force attacks by increasing the number of rounds.

bcrypt algorithm implementation

Simply put, the bcrypt algorithm is the of 64 blowfish encryption OrpheanBeholderScryDoubt 1614a801c4657a. Some friends will ask, isn't bcrypt used to encrypt passwords? How to encrypt a string?

Don't worry, bcrpyt uses the password as a factor to encrypt the string, and it also has the effect of encryption. Let's look at the basic algorithm implementation of bcrypt:

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(cost, salt, password)   

   //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)

The above function bcrypt has 3 inputs and 1 output.

In the input part, cost represents the number of rounds. We can specify this by ourselves. The more rounds, the encryption will be slow.

Salt is the salt for encryption, which is used to obfuscate the use of passwords.

password is the password we want to encrypt.

The final output is the encrypted result hash.

With 3 inputs, we will call the EksBlowfishSetup function to initialize 18 subkeys and 4 1K S-boxes to reach the final P and S.

Then use P and S to perform 64 blowfish operations on "OrpheanBeholderScryDoubt", and finally get the result.

Next, look at the algorithm implementation of the EksBlowfishSetup method:

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. 1024 KB)

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

   //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, 0, password)
      P, S  <- ExpandKey(P, S, 0, salt)

   return P, S

The code is very simple. EksBlowfishSetup receives our 3 parameters above and returns the final P containing 18 subkeys and 4 Sboxes of 1k size.

First initialize, get the initial P and S.

Then call ExpandKey, pass in salt and password, and generate P and S for the first round.

Then loop the cost of 2 times, take turns using password and salt as parameters to generate P and S, and finally return.

Finally, look at the implementation of ExpandKey:

Function ExpandKey
   Input:
      password: array of Bytes (1..72 bytes)  UTF-8 encoded password
      salt:     Byte[16]                      random salt
      P:        array of UInt32               Array of 18 subkeys
      S1..S4:   UInt32[1024]                  Four 1 KB SBoxes
   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
      block   <-  block 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) 
      P2n   <-  block[0..31]      //lower 32-bits of block
      P2n+1   <- block[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 salt[64(n-1)..64n-1]) //as above
         Si[2n]     <- block[0..31]  //lower 32-bits
         Si[2n+1]   <-  block[32..63]  //upper 32-bits
    return state

ExpandKey is mainly used to generate P and S, the algorithm generation is more complicated, you can study it in detail if you are interested.

The structure of bcrypt hash

We can use bcrypt to encrypt the password, and finally save it in the system in the form of bcrypt hash. The format of a bcrypt hash is as follows:

$2b$[cost]$[22 character salt][31 character hash]

for example:

$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
\__/\/ \____________________/\_____________________________/
 Alg Cost      Salt                        Hash

In the above example, $2a$ represents the unique sign of the hash algorithm. Here is the bcrypt algorithm.

10 represents the cost factor, here is 2 to the 10th power, which is 1024 rounds.

N9qo8uLOickgx2ZMRZoMye is a 22-length character obtained by base64 encoding a 16-byte (128bits) salt.

The final IjZAgcfl7p92ldGxad68LJZdL17lhWy is a 24-byte (192bits) hash, 31-length characters obtained through bash64 encoding.

The history of hash

This hash format follows the Modular Crypt Format format used when storing passwords in OpenBSD password files. At the beginning, the format definition was as follows:

  • $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')

But the original specification did not define how to handle non-ASCII characters, nor did it define how to handle the null terminator. The revised specification stipulates that when hashing strings:

  • String must be UTF-8 encoded
  • Must contain the null terminator

Because of these changes, the version number of bcrypt has been modified to $2a$ .

But in June 2011, because crypt_blowfish , they suggested that system administrators update their existing password database and replace $2a$ $2x$ to indicate that these hash values are bad (requires Use the old algorithm). $2y$ for the hash value generated by the new algorithm. Of course, this change is limited to PHP crypt_blowfish .

Then in February 2014, a bug was found in OpenBSD's bcrypt implementation. They stored the length of the string in an unsigned char (ie 8-bit Byte). If the length of the password exceeds 255 characters, it will overflow.

Because bcrypt was created for OpenBSD. So when a bug appeared in their library, they decided to upgrade the version number to $2b$ .

This article has been included in http://www.flydean.com/37-bcrypt/

The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", know technology, know you better!


flydean
890 声望432 粉丝

欢迎访问我的个人网站:www.flydean.com