@@ -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#
5657return 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