@@ -52,6 +52,7 @@ $cdata = array_values(unpack('N*', 'OrpheanBeholderScryDoubt'));
52
52
for ($j = 0; $j < 6; $j += 2) { // count($cdata) == 6
53
53
#
54
54
#-----[ FIND ]------------------------------------------
55
+ # in 3.0.39+ this'll be V* - not L*
55
56
#
56
57
return pack('L*', ...$cdata);
57
58
#
@@ -108,4 +109,193 @@ function encodeBase64($input)
108
109
return $output;
109
110
}
110
111
```
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