Skip to content

Commit 351e84c

Browse files
committed
expand bcrypt discussion
1 parent 34f38bc commit 351e84c

File tree

1 file changed

+191
-1
lines changed

1 file changed

+191
-1
lines changed

docs/bcrypt.md

Lines changed: 191 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ $cdata = array_values(unpack('N*', 'OrpheanBeholderScryDoubt'));
5252
for ($j = 0; $j < 6; $j += 2) { // count($cdata) == 6
5353
#
5454
#-----[ FIND ]------------------------------------------
55+
# in 3.0.39+ this'll be V* - not L*
5556
#
5657
return pack('L*', ...$cdata);
5758
#
@@ -108,4 +109,193 @@ function encodeBase64($input)
108109
return $output;
109110
}
110111
```
111-
The two key takeaways from this are: (1) `Blowfish::bcrypt_hash()` expects 64 byte inputs whereas the normal bcrypt uses 8 byte salts and variable length passwords and (2) the last byte of `Blowfish::bcrypt_hash()` need to be removed.
112+
The two key takeaways from this are: (1) `Blowfish::bcrypt_hash()` expects 64 byte inputs whereas the normal bcrypt uses 16 byte salts and variable length passwords and (2) the last byte of `Blowfish::bcrypt_hash()` need to be removed.
113+
114+
If one wanted to make `Blowfish::bcrypt_hash()` support variable length passwords and 16 byte salts the following additional changes would need to be made:
115+
116+
```
117+
#
118+
#-----[ FIND ]------------------------------------------
119+
# in bcrypt_hash()
120+
#
121+
$sha2pass = array_values(unpack('N*', $sha2pass));
122+
#
123+
#-----[ BEFORE, ADD ]-----------------------------------
124+
#
125+
// per the $2a$ bit we append a null byte and then "treat the password as cyclic"
126+
$sha2pass.= "\0";
127+
while (strlen($sha2pass) < 72) {
128+
$sha2pass.= $sha2pass;
129+
}
130+
$sha2pass = substr($sha2pass, 0, 72);
131+
#
132+
#-----[ FIND ]------------------------------------------
133+
# in expand0state()
134+
#
135+
$p = [
136+
$p[0] ^ $key[0],
137+
$p[1] ^ $key[1],
138+
$p[2] ^ $key[2],
139+
$p[3] ^ $key[3],
140+
$p[4] ^ $key[4],
141+
$p[5] ^ $key[5],
142+
$p[6] ^ $key[6],
143+
$p[7] ^ $key[7],
144+
$p[8] ^ $key[8],
145+
$p[9] ^ $key[9],
146+
$p[10] ^ $key[10],
147+
$p[11] ^ $key[11],
148+
$p[12] ^ $key[12],
149+
$p[13] ^ $key[13],
150+
$p[14] ^ $key[14],
151+
$p[15] ^ $key[15],
152+
$p[16] ^ $key[0],
153+
$p[17] ^ $key[1]
154+
];
155+
#
156+
#-----[ REPLACE WITH ]----------------------------------
157+
# this is a documented change; we can't keep the loop unrolled version
158+
# unless we expand the salt out to 72 bytes as well to match the password
159+
# size
160+
#
161+
for ($i = 0; $i < 18; $i++) {
162+
$p[$i]^= $key[$i % count($key)];
163+
}
164+
#
165+
#-----[ FIND ]------------------------------------------
166+
# in expandstate()
167+
#
168+
$p[16] ^ $key[0],
169+
$p[17] ^ $key[1]
170+
#
171+
#-----[ REPLACE WITH ]----------------------------------
172+
# this is a documented change
173+
#
174+
$p[16] ^ $key[16],
175+
$p[17] ^ $key[17]
176+
#
177+
#-----[ FIND ]------------------------------------------
178+
# in expandstate()
179+
#
180+
// @codingStandardsIgnoreStart
181+
list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox, $p);
182+
list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
183+
list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 4] ^ $p[ 2], $data[ 5] ^ $p[ 3], $sbox, $p);
184+
list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 6] ^ $p[ 4], $data[ 7] ^ $p[ 5], $sbox, $p);
185+
list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 8] ^ $p[ 6], $data[ 9] ^ $p[ 7], $sbox, $p);
186+
list($p[10], $p[11]) = self::encryptBlockHelperFast($data[10] ^ $p[ 8], $data[11] ^ $p[ 9], $sbox, $p);
187+
list($p[12], $p[13]) = self::encryptBlockHelperFast($data[12] ^ $p[10], $data[13] ^ $p[11], $sbox, $p);
188+
list($p[14], $p[15]) = self::encryptBlockHelperFast($data[14] ^ $p[12], $data[15] ^ $p[13], $sbox, $p);
189+
list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
190+
// @codingStandardsIgnoreEnd
191+
192+
list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
193+
for ($i = 2, $j = 4; $i < 1024; $i += 2, $j = ($j + 2) % 16) { // instead of 16 maybe count($data) would be better?
194+
list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($data[$j] ^ $sbox[$i - 2], $data[$j + 1] ^ $sbox[$i - 1], $sbox, $p);
195+
}
196+
#
197+
#-----[ REPLACE WITH ]----------------------------------
198+
# this is a documented change; instead of doing $data[0]..$data[15] we're doing $data[0]..$data[3] cyclically
199+
#
200+
// @codingStandardsIgnoreStart
201+
list( $p[0], $p[1]) = self::encryptBlockHelperFast($data[ 0] , $data[ 1] , $sbox, $p);
202+
list( $p[2], $p[3]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 0], $data[ 3] ^ $p[ 1], $sbox, $p);
203+
list( $p[4], $p[5]) = self::encryptBlockHelperFast($data[ 0] ^ $p[ 2], $data[ 1] ^ $p[ 3], $sbox, $p);
204+
list( $p[6], $p[7]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 4], $data[ 3] ^ $p[ 5], $sbox, $p);
205+
list( $p[8], $p[9]) = self::encryptBlockHelperFast($data[ 0] ^ $p[ 6], $data[ 1] ^ $p[ 7], $sbox, $p);
206+
list($p[10], $p[11]) = self::encryptBlockHelperFast($data[ 2] ^ $p[ 8], $data[ 3] ^ $p[ 9], $sbox, $p);
207+
list($p[12], $p[13]) = self::encryptBlockHelperFast($data[ 0] ^ $p[10], $data[ 1] ^ $p[11], $sbox, $p);
208+
list($p[14], $p[15]) = self::encryptBlockHelperFast($data[ 2] ^ $p[12], $data[ 3] ^ $p[13], $sbox, $p);
209+
list($p[16], $p[17]) = self::encryptBlockHelperFast($data[ 0] ^ $p[14], $data[ 1] ^ $p[15], $sbox, $p);
210+
// @codingStandardsIgnoreEnd
211+
212+
list($sbox[0], $sbox[1]) = self::encryptBlockHelperFast($data[2] ^ $p[16], $data[3] ^ $p[17], $sbox, $p);
213+
for ($i = 2, $j = 0; $i < 1024; $i += 2, $j%= 4) { // instead of 16 maybe count($data) would be better?
214+
list($sbox[$i], $sbox[$i + 1]) = self::encryptBlockHelperFast($data[$j++] ^ $sbox[$i - 2], $data[$j++] ^ $sbox[$i - 1], $sbox, $p);
215+
}
216+
```
217+
218+
## Making Wikipedia match OpenSSH
219+
220+
The following changes (notated using the [phpBB MOD Text Template](phpbb.md#actions)) are for [revision 08:07, 14 May 2024 of wikipedia's bcrypt article](https://en.wikipedia.org/w/index.php?title=Bcrypt&oldid=1223800920#Algorithm). They may or may not work on other revision.
221+
222+
```
223+
#
224+
#-----[ FIND ]------------------------------------------
225+
# in Function bcrypt
226+
#
227+
Input:
228+
password: array of Bytes (1..72 bytes) UTF-8 encoded password
229+
salt: array of Bytes (16 bytes) random salt
230+
cost: Number (4..31) log2(Iterations). e.g. 12 ==> 2**12 = 4,096 iterations
231+
#
232+
#-----[ REPLACE WITH ]----------------------------------
233+
# this is a documented change; well, the cost isn't, but everything else is
234+
#
235+
Input:
236+
password: array of Bytes (64 bytes) UTF-8 encoded password
237+
salt: array of Bytes (64 bytes) random salt
238+
cost: Number (6) 64 iterations
239+
#
240+
#-----[ FIND ]------------------------------------------
241+
# in Function bcrypt
242+
#
243+
ctext ← "OrpheanBeholderScryDoubt" //24 bytes ==> three 64-bit blocks
244+
#
245+
#-----[ REPLACE WITH ]----------------------------------
246+
# this is a documented change
247+
#
248+
ctext ← "OxychromaticBlowfishSwatDynamite" //32 bytes ==> four 64-bit blocks
249+
#
250+
#-----[ FIND ]------------------------------------------
251+
# in Function EksBlowfishSetup
252+
#
253+
P, S ← ExpandKey(P, S, password, 0)
254+
P, S ← ExpandKey(P, S, salt, 0)
255+
#
256+
#-----[ REPLACE WITH ]----------------------------------
257+
# this is an undocumented change; we're just swapping the order
258+
#
259+
P, S ← ExpandKey(P, S, salt, 0)
260+
P, S ← ExpandKey(P, S, password, 0)
261+
#
262+
#-----[ FIND ]------------------------------------------
263+
# in Function ExpandKey
264+
#
265+
//Treat the 128-bit salt as two 64-bit halves (the Blowfish block size).
266+
saltHalf[0] ← salt[0..63] //Lower 64-bits of salt
267+
saltHalf[1] ← salt[64..127] //Upper 64-bits of salt
268+
#
269+
#-----[ REPLACE WITH ]----------------------------------
270+
# this is a documented change
271+
#
272+
saltChunk[0] ← salt[0..63]
273+
saltChunk[1] ← salt[64..127]
274+
saltChunk[2] ← salt[128..191]
275+
saltChunk[3] ← salt[192..255]
276+
saltChunk[4] ← salt[256..319]
277+
saltChunk[5] ← salt[320..383]
278+
saltChunk[6] ← salt[384..447]
279+
saltChunk[7] ← salt[448..511]
280+
#
281+
#-----[ FIND ]------------------------------------------
282+
# in Function ExpandKey
283+
#
284+
block ← block xor saltHalf[(n-1) mod 2] //each iteration alternating between saltHalf[0], and saltHalf[1]
285+
#
286+
#-----[ REPLACE WITH ]----------------------------------
287+
# this is a documented change
288+
#
289+
block ← block xor saltChunk[n-1]
290+
#
291+
#-----[ FIND ]------------------------------------------
292+
# in Function ExpandKey
293+
#
294+
block ← Encrypt(state, block xor saltHalf[(n-1) mod 2]) //as above
295+
#
296+
#-----[ REPLACE WITH ]----------------------------------
297+
# this is a documented change
298+
#
299+
block ← Encrypt(state, block xor saltChunk[(n-1) mod 8])
300+
```
301+
Being pseudo code this doesn't quite capture everything. For example it doesn't capture the change in endianness.

0 commit comments

Comments
 (0)