@amritk/nish-x86_64-linux 0.21.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/INSTALL.md +30 -0
- package/bin/nish +0 -0
- package/docs/AI.md +18 -0
- package/package.json +1 -1
- package/runtime/browser.d.ts +1 -9
- package/runtime/browser.mjs +2 -13
- package/runtime/core/arrays.d.ts +26 -8
- package/runtime/core/arrays.mjs +31 -3
- package/runtime/core/host.d.ts +10 -0
- package/runtime/core/host.mjs +14 -0
- package/runtime/core/numbers.d.ts +14 -1
- package/runtime/core/numbers.mjs +30 -3
- package/runtime/core/result.d.ts +4 -0
- package/runtime/core/result.mjs +13 -0
- package/runtime/core/strings.d.ts +2 -2
- package/runtime/nish.d.ts +2 -2
- package/runtime/node.mjs +3 -0
- package/runtime/runtime-net.c +12 -1
- package/scripts/build.sh +58 -14
- package/std/README.md +2 -2
- package/std/cli.ts +17 -1
- package/std/crypto/bcrypt.ts +869 -72
- package/std/json.ts +4 -7
- package/std/net/http1-server.ts +199 -34
- package/std/net/http1.ts +40 -21
- package/std/net/postgres.ts +471 -52
- package/std/net/prefork.ts +23 -3
- package/std/random.ts +66 -4
package/std/crypto/bcrypt.ts
CHANGED
|
@@ -14,11 +14,23 @@
|
|
|
14
14
|
* // "$2b$12$" + 22 salt characters + 31 digest characters
|
|
15
15
|
* const ok: Result<boolean, string> = bcryptVerify(pw, storedHash)
|
|
16
16
|
*
|
|
17
|
+
* **Four at once.** `bcryptHash4` and `bcryptVerify4` take up to four
|
|
18
|
+
* passwords (lane 0 always, a null lane skipped) and run their Blowfish
|
|
19
|
+
* encryptions interleaved in one loop, so one core does the work of four
|
|
20
|
+
* hashes in well under the time of four; each result is the single path's,
|
|
21
|
+
* byte for byte. `bcryptVerify4` groups lanes by the cost their hash names,
|
|
22
|
+
* and a malformed hash in any lane is an `Err` for the whole call, naming the
|
|
23
|
+
* lane, before anything is hashed.
|
|
24
|
+
*
|
|
17
25
|
* **The password is a `Secret<u8[]>`** (`nish:secret`). The key schedule and
|
|
18
26
|
* the 2^cost rounds run inside `exposeWith`; the Blowfish state (the P-array
|
|
19
27
|
* and the four S-boxes, 1042 words), the expanded key and the key-stream copy
|
|
20
28
|
* are wiped before it returns. `bcryptVerify` rebuilds the whole 60-character
|
|
21
|
-
* hash and compares it with `timingSafeEqual
|
|
29
|
+
* hash and compares it with `timingSafeEqual` inside the same `exposeWith`,
|
|
30
|
+
* then wipes the rebuilt hash and the given hash's bytes; the batch wipes its
|
|
31
|
+
* digests and, in `bcryptVerify4`, each lane's rebuilt and given hash. The
|
|
32
|
+
* hash strings `bcryptHash` and `bcryptHash4` return are public and are not
|
|
33
|
+
* wiped, nor are the bytes they are made from.
|
|
22
34
|
*
|
|
23
35
|
* **The key is the reference's.** The bytes up to the first NUL, then one NUL,
|
|
24
36
|
* repeated until 72 bytes are taken: a password longer than 72 bytes hashes as
|
|
@@ -45,9 +57,12 @@
|
|
|
45
57
|
* rounds, hours of one core. Verify only hashes you stored.
|
|
46
58
|
*
|
|
47
59
|
* **Pure.** No I/O and no C, so it builds for wasm32. A `std/threads` task
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
60
|
+
* may call `bcryptHash`, `bcryptVerify` and the batch: everything they wipe
|
|
61
|
+
* is an array they made, never the caller's `hashes`, and the batch's state is
|
|
62
|
+
* a `Secret` of a fresh array
|
|
63
|
+
* (docs/security/crypto-bcrypt.md). A cost-12 hash takes a few hundred
|
|
64
|
+
* milliseconds of one core; a server runs it off its event loop, on a task
|
|
65
|
+
* (docs/guide/using-nish/password-hashing.md).
|
|
51
66
|
*
|
|
52
67
|
* The algorithm is Provos and Mazières's ("A Future-Adaptable Password
|
|
53
68
|
* Scheme", USENIX 1999). The code is adapted from Solar Designer's
|
|
@@ -101,7 +116,7 @@
|
|
|
101
116
|
*/
|
|
102
117
|
|
|
103
118
|
import { timingSafeEqual } from "nish/crypto/ct"
|
|
104
|
-
import { Secret, exposeWith, wipe } from "nish:secret"
|
|
119
|
+
import { Secret, exposeWith, secret, wipe } from "nish:secret"
|
|
105
120
|
|
|
106
121
|
/** The length of a bcrypt hash string: `$2b$`, two cost digits, `$`, 22 salt and 31 digest characters. */
|
|
107
122
|
export const BCRYPT_HASH_LENGTH: i32 = 60
|
|
@@ -139,6 +154,17 @@ interface BcryptSetting {
|
|
|
139
154
|
safety: u32
|
|
140
155
|
}
|
|
141
156
|
|
|
157
|
+
/**
|
|
158
|
+
* The one argument of the callbacks `exposeWith` runs on the password: the
|
|
159
|
+
* setting, the 29 characters the hash starts with, and for verifying the hash
|
|
160
|
+
* to compare with ("" when hashing). All of it is only read.
|
|
161
|
+
*/
|
|
162
|
+
interface BcryptJob {
|
|
163
|
+
setting: BcryptSetting
|
|
164
|
+
prefix: u8[]
|
|
165
|
+
hash: string
|
|
166
|
+
}
|
|
167
|
+
|
|
142
168
|
/** A parsed hash string: its setting and the 29 characters the rebuilt hash starts with. */
|
|
143
169
|
interface BcryptParsed {
|
|
144
170
|
setting: BcryptSetting
|
|
@@ -188,9 +214,18 @@ const bcryptSextetOf = (c: i32): i32 => {
|
|
|
188
214
|
*/
|
|
189
215
|
const bcryptEncode = (src: u8[], n: i32): u8[] => {
|
|
190
216
|
const out: u8[] = new Array<u8>((n * 4 + 2) / 3)
|
|
217
|
+
bcryptEncodeInto(out, 0, src, n)
|
|
218
|
+
return out
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* `bcryptEncode` into `out` from `at`, as far as `out` reaches, so a digest's
|
|
223
|
+
* characters land in an array the caller made and wipes.
|
|
224
|
+
*/
|
|
225
|
+
const bcryptEncodeInto = (out: u8[], at: i32, src: u8[], n: i32): void => {
|
|
191
226
|
let i: i32 = 0
|
|
192
|
-
let o: i32 =
|
|
193
|
-
while (i + 2 < n && i + 2 < toI32(src.length) && o + 3 < toI32(out.length)) {
|
|
227
|
+
let o: i32 = at
|
|
228
|
+
while (i + 2 < n && i + 2 < toI32(src.length) && o >= 0 && o + 3 < toI32(out.length)) {
|
|
194
229
|
const c1: i32 = toI32(src[i])
|
|
195
230
|
const c2: i32 = toI32(src[i + 1])
|
|
196
231
|
const c3: i32 = toI32(src[i + 2])
|
|
@@ -212,7 +247,6 @@ const bcryptEncode = (src: u8[], n: i32): u8[] => {
|
|
|
212
247
|
out[o + 1] = bcryptCharOf((c1 & 0x03) << 4)
|
|
213
248
|
}
|
|
214
249
|
}
|
|
215
|
-
return out
|
|
216
250
|
}
|
|
217
251
|
|
|
218
252
|
/** `src` copied into `dst` from `at`, as far as `dst` reaches. */
|
|
@@ -391,21 +425,19 @@ const bcryptBody = (st: u32[]): void => {
|
|
|
391
425
|
}
|
|
392
426
|
|
|
393
427
|
/**
|
|
394
|
-
* `BF_set_key
|
|
395
|
-
*
|
|
396
|
-
* repeated. `safety` is 0x10000 for `$2a$`
|
|
397
|
-
* key is also built with each byte
|
|
398
|
-
* and if an 8-bit byte was
|
|
399
|
-
*
|
|
400
|
-
*
|
|
401
|
-
*
|
|
402
|
-
*
|
|
428
|
+
* `BF_set_key`'s key: the 18 expanded-key words into `expanded[0..17]`, and
|
|
429
|
+
* the word P[0] is XORed with into `expanded[18]`, for `password` read as its
|
|
430
|
+
* bytes to the first NUL and one NUL, repeated. `safety` is 0x10000 for `$2a$`
|
|
431
|
+
* and 0 otherwise: for `$2a$` the key is also built with each byte
|
|
432
|
+
* sign-extended, as the old bug built it, and if an 8-bit byte was
|
|
433
|
+
* sign-extended into a word (`sign`) yet the two keys came out equal (`diff`
|
|
434
|
+
* is 0), `expanded[18]` is bit 16, which flips it in P[0] as crypt_blowfish
|
|
435
|
+
* flips it. All of it is computed without a branch on a password byte: only
|
|
436
|
+
* the copy's length and the cycle position, which follow the password's length
|
|
437
|
+
* and NULs, decide a load.
|
|
403
438
|
*/
|
|
404
|
-
const
|
|
405
|
-
if (
|
|
406
|
-
return
|
|
407
|
-
}
|
|
408
|
-
if (expanded.length < 18) {
|
|
439
|
+
const bcryptKeySchedule = (expanded: u32[], password: u8[], safety: u32): void => {
|
|
440
|
+
if (expanded.length < 19) {
|
|
409
441
|
return
|
|
410
442
|
}
|
|
411
443
|
// The key stream: at most 72 bytes of the password and zeros after, so the
|
|
@@ -434,20 +466,39 @@ const bcryptSetKey = (st: u32[], expanded: u32[], password: u8[], safety: u32):
|
|
|
434
466
|
}
|
|
435
467
|
diff |= correct ^ buggy
|
|
436
468
|
expanded[i] = correct
|
|
437
|
-
st[i] ^= correct
|
|
438
469
|
}
|
|
439
470
|
diff |= diff >>> 16
|
|
440
471
|
diff &= 0xffff
|
|
441
472
|
diff += 0xffff
|
|
442
473
|
sign <<= 9
|
|
443
474
|
sign &= ~diff & safety
|
|
444
|
-
|
|
475
|
+
expanded[18] = sign
|
|
445
476
|
wipe(key)
|
|
446
477
|
}
|
|
447
478
|
|
|
448
|
-
/**
|
|
449
|
-
const
|
|
450
|
-
|
|
479
|
+
/** `BF_set_key`: `bcryptKeySchedule` into `expanded`, and P XORed with it. */
|
|
480
|
+
const bcryptSetKey = (st: u32[], expanded: u32[], password: u8[], safety: u32): void => {
|
|
481
|
+
bcryptKeySchedule(expanded, password, safety)
|
|
482
|
+
if (st.length < 1042) {
|
|
483
|
+
return
|
|
484
|
+
}
|
|
485
|
+
if (expanded.length < 19) {
|
|
486
|
+
return
|
|
487
|
+
}
|
|
488
|
+
for (let i: i32 = 0; i < 18; i++) {
|
|
489
|
+
st[i] ^= expanded[i]
|
|
490
|
+
}
|
|
491
|
+
st[0] ^= expanded[18]
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* The 1042 words of `st` from `at` set to the state Blowfish starts from: P
|
|
496
|
+
* then S0–S3, the digits of π (`BF_init_state`). The caller allocates `st`,
|
|
497
|
+
* so that the wipe of it is a wipe of memory the hashing function made, which
|
|
498
|
+
* a `std/threads` task may do.
|
|
499
|
+
*/
|
|
500
|
+
const bcryptInitState = (st: u32[], at: i32): void => {
|
|
501
|
+
const init: u32[] = [
|
|
451
502
|
// P
|
|
452
503
|
0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
|
|
453
504
|
0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
|
|
@@ -585,7 +636,12 @@ const bcryptInitState = (): u32[] => {
|
|
|
585
636
|
0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
|
|
586
637
|
0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6,
|
|
587
638
|
]
|
|
588
|
-
|
|
639
|
+
if (at < 0) {
|
|
640
|
+
return
|
|
641
|
+
}
|
|
642
|
+
for (let i: i32 = 0; i < toI32(init.length) && at + i < toI32(st.length); i++) {
|
|
643
|
+
st[at + i] = init[i]
|
|
644
|
+
}
|
|
589
645
|
}
|
|
590
646
|
|
|
591
647
|
/** The block `lr` into `st` at `at` and `at + 1`. */
|
|
@@ -612,37 +668,39 @@ const bcryptXorKey = (st: u32[], expanded: u32[]): void => {
|
|
|
612
668
|
}
|
|
613
669
|
}
|
|
614
670
|
|
|
615
|
-
/** P XORed with the salt words, repeated: the second half of a cost round. */
|
|
616
|
-
const bcryptXorSalt = (st: u32[], s0: u32, s1: u32, s2: u32, s3: u32): void => {
|
|
617
|
-
if (st.length
|
|
671
|
+
/** The 18 P words at `at` XORed with the salt words, repeated: the second half of a cost round. */
|
|
672
|
+
const bcryptXorSalt = (st: u32[], at: i32, s0: u32, s1: u32, s2: u32, s3: u32): void => {
|
|
673
|
+
if (at < 0 || at + 17 >= toI32(st.length)) {
|
|
618
674
|
return
|
|
619
675
|
}
|
|
620
676
|
for (let i: i32 = 0; i + 3 < 16; i += 4) {
|
|
621
|
-
st[i] ^= s0
|
|
622
|
-
st[i + 1] ^= s1
|
|
623
|
-
st[i + 2] ^= s2
|
|
624
|
-
st[i + 3] ^= s3
|
|
677
|
+
st[at + i] ^= s0
|
|
678
|
+
st[at + i + 1] ^= s1
|
|
679
|
+
st[at + i + 2] ^= s2
|
|
680
|
+
st[at + i + 3] ^= s3
|
|
625
681
|
}
|
|
626
|
-
st[16] ^= s0
|
|
627
|
-
st[17] ^= s1
|
|
682
|
+
st[at + 16] ^= s0
|
|
683
|
+
st[at + 17] ^= s1
|
|
628
684
|
}
|
|
629
685
|
|
|
630
686
|
/**
|
|
631
|
-
* The hash itself,
|
|
632
|
-
*
|
|
633
|
-
* expanded key and the
|
|
687
|
+
* The hash itself, on the plain password: the 31 digest characters (`BF_crypt`
|
|
688
|
+
* from the key schedule on) into `out` at 29, which the caller made and wipes.
|
|
689
|
+
* The state, the expanded key, the block and the raw digest are wiped before
|
|
690
|
+
* it returns.
|
|
634
691
|
*/
|
|
635
|
-
const bcryptExposed = (password: u8[], setting: BcryptSetting
|
|
692
|
+
const bcryptExposed = (password: u8[], setting: BcryptSetting, out: u8[]): void => {
|
|
636
693
|
const salt: u32[] = setting.salt
|
|
637
694
|
if (salt.length < 4) {
|
|
638
|
-
return
|
|
695
|
+
return
|
|
639
696
|
}
|
|
640
697
|
const s0: u32 = salt[0]
|
|
641
698
|
const s1: u32 = salt[1]
|
|
642
699
|
const s2: u32 = salt[2]
|
|
643
700
|
const s3: u32 = salt[3]
|
|
644
|
-
const st: u32[] =
|
|
645
|
-
|
|
701
|
+
const st: u32[] = new Array<u32>(BCRYPT_STATE_WORDS)
|
|
702
|
+
bcryptInitState(st, 0)
|
|
703
|
+
const expanded: u32[] = new Array<u32>(19)
|
|
646
704
|
const lr: u32[] = new Array<u32>(2)
|
|
647
705
|
bcryptSetKey(st, expanded, password, setting.safety)
|
|
648
706
|
|
|
@@ -661,7 +719,7 @@ const bcryptExposed = (password: u8[], setting: BcryptSetting): u8[] => {
|
|
|
661
719
|
while (rounds !== 0) {
|
|
662
720
|
bcryptXorKey(st, expanded)
|
|
663
721
|
bcryptBody(st)
|
|
664
|
-
bcryptXorSalt(st, s0, s1, s2, s3)
|
|
722
|
+
bcryptXorSalt(st, 0, s0, s1, s2, s3)
|
|
665
723
|
bcryptBody(st)
|
|
666
724
|
rounds -= 1
|
|
667
725
|
}
|
|
@@ -672,12 +730,11 @@ const bcryptExposed = (password: u8[], setting: BcryptSetting): u8[] => {
|
|
|
672
730
|
bcryptMagic(st, lr, digest, 8, 0x65686f6c, 0x64657253)
|
|
673
731
|
bcryptMagic(st, lr, digest, 16, 0x63727944, 0x6f756274)
|
|
674
732
|
// 23 of the 24 bytes, as the reference encodes them.
|
|
675
|
-
|
|
733
|
+
bcryptEncodeInto(out, BCRYPT_SETTING_LENGTH, digest, 23)
|
|
676
734
|
wipe(st)
|
|
677
735
|
wipe(expanded)
|
|
678
736
|
wipe(lr)
|
|
679
737
|
wipe(digest)
|
|
680
|
-
return out
|
|
681
738
|
}
|
|
682
739
|
|
|
683
740
|
/** One pair of the magic string, encrypted 64 times under `st`, into `dst` at `at` as eight big-endian bytes. */
|
|
@@ -687,30 +744,67 @@ const bcryptMagic = (st: u32[], lr: u32[], dst: u8[], at: i32, l: u32, r: u32):
|
|
|
687
744
|
for (let k: i32 = 0; k < 64; k++) {
|
|
688
745
|
bcryptEncrypt(st, lr)
|
|
689
746
|
}
|
|
747
|
+
bcryptPutBlock(dst, at, lr[0], lr[1])
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
/** The block `l`, `r` into `dst` at `at` as eight big-endian bytes; nothing when it does not fit. */
|
|
751
|
+
const bcryptPutBlock = (dst: u8[], at: i32, l: u32, r: u32): void => {
|
|
690
752
|
if (at < 0 || at + 7 >= toI32(dst.length)) {
|
|
691
753
|
return
|
|
692
754
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
dst[at] = toU8(
|
|
696
|
-
dst[at +
|
|
697
|
-
dst[at +
|
|
698
|
-
dst[at +
|
|
699
|
-
dst[at +
|
|
700
|
-
dst[at +
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
755
|
+
dst[at] = toU8(l >>> 24)
|
|
756
|
+
dst[at + 1] = toU8((l >>> 16) & 0xff)
|
|
757
|
+
dst[at + 2] = toU8((l >>> 8) & 0xff)
|
|
758
|
+
dst[at + 3] = toU8(l & 0xff)
|
|
759
|
+
dst[at + 4] = toU8(r >>> 24)
|
|
760
|
+
dst[at + 5] = toU8((r >>> 16) & 0xff)
|
|
761
|
+
dst[at + 6] = toU8((r >>> 8) & 0xff)
|
|
762
|
+
dst[at + 7] = toU8(r & 0xff)
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* The 60-byte hash of the plain password under `job`'s setting into `out`,
|
|
767
|
+
* which the caller made and wipes: the 29-byte setting, then the digest.
|
|
768
|
+
*/
|
|
769
|
+
const bcryptHashInto = (password: u8[], job: BcryptJob, out: u8[]): void => {
|
|
770
|
+
bcryptPut(out, 0, job.prefix)
|
|
771
|
+
bcryptExposed(password, job.setting, out)
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
/**
|
|
775
|
+
* The 60 bytes of the hash, for `exposeWith` on the password. They are the
|
|
776
|
+
* hash `bcryptHash` returns, so they are not secret and are not wiped.
|
|
777
|
+
*/
|
|
778
|
+
const bcryptHashExposed = (password: u8[], job: BcryptJob): u8[] => {
|
|
708
779
|
const out: u8[] = new Array<u8>(BCRYPT_HASH_LENGTH)
|
|
709
|
-
|
|
710
|
-
bcryptPut(out, BCRYPT_SETTING_LENGTH, digest)
|
|
780
|
+
bcryptHashInto(password, job, out)
|
|
711
781
|
return out
|
|
712
782
|
}
|
|
713
783
|
|
|
784
|
+
/**
|
|
785
|
+
* Whether the hash rebuilt from the plain password is `job.hash`, for
|
|
786
|
+
* `exposeWith` on the password.
|
|
787
|
+
*/
|
|
788
|
+
const bcryptVerifyExposed = (password: u8[], job: BcryptJob): boolean => {
|
|
789
|
+
const expected: u8[] = new Array<u8>(BCRYPT_HASH_LENGTH)
|
|
790
|
+
bcryptHashInto(password, job, expected)
|
|
791
|
+
return bcryptSameWiped(expected, job.hash)
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Whether the 60 bytes of `rebuilt` are `hash`'s: a constant-time compare.
|
|
796
|
+
* `rebuilt` and the copy of `hash` made here are both wiped, so the answer is
|
|
797
|
+
* all that leaves.
|
|
798
|
+
*/
|
|
799
|
+
const bcryptSameWiped = (rebuilt: u8[], hash: string): boolean => {
|
|
800
|
+
const given: u8[] = new Array<u8>(BCRYPT_HASH_LENGTH)
|
|
801
|
+
bcryptBytesOf(given, hash)
|
|
802
|
+
const same: boolean = timingSafeEqual(rebuilt, given)
|
|
803
|
+
wipe(rebuilt)
|
|
804
|
+
wipe(given)
|
|
805
|
+
return same
|
|
806
|
+
}
|
|
807
|
+
|
|
714
808
|
/**
|
|
715
809
|
* The `$2b$` hash of `password` with a 16-byte `salt` and 2^`cost` rounds:
|
|
716
810
|
* 60 characters, `$2b$`, the two-digit cost, `$`, the salt and the digest in
|
|
@@ -725,7 +819,8 @@ export const bcryptHash = (password: Secret<u8[]>, salt: u8[], cost: i32): Resul
|
|
|
725
819
|
return Err("bcrypt: the cost is outside 4 to 31")
|
|
726
820
|
}
|
|
727
821
|
const setting: BcryptSetting = { salt: bcryptSaltWords(salt), cost: cost, safety: toU32(0) }
|
|
728
|
-
const
|
|
822
|
+
const job: BcryptJob = { setting: setting, prefix: bcryptSettingOf(salt, cost), hash: "" }
|
|
823
|
+
const out: u8[] = exposeWith(password, job, bcryptHashExposed)
|
|
729
824
|
return Ok(String.fromBytes(out, 0, BCRYPT_HASH_LENGTH))
|
|
730
825
|
}
|
|
731
826
|
|
|
@@ -736,6 +831,24 @@ export const bcryptHash = (password: Secret<u8[]>, salt: u8[], cost: i32): Resul
|
|
|
736
831
|
* are read; an `Err` names what makes `hash` not a bcrypt hash.
|
|
737
832
|
*/
|
|
738
833
|
export const bcryptVerify = (password: Secret<u8[]>, hash: string): Result<boolean, string> => {
|
|
834
|
+
const parsed: Result<BcryptParsed, string> = bcryptParseHash(hash)
|
|
835
|
+
if (parsed.isErr()) {
|
|
836
|
+
return Err(parsed.error)
|
|
837
|
+
}
|
|
838
|
+
return Ok(bcryptVerifyOne(password, parsed.value, hash))
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
/** Whether `hash`, parsed as `parsed`, is `password`'s. */
|
|
842
|
+
const bcryptVerifyOne = (password: Secret<u8[]>, parsed: BcryptParsed, hash: string): boolean => {
|
|
843
|
+
const job: BcryptJob = { setting: parsed.setting, prefix: parsed.prefix, hash: hash }
|
|
844
|
+
return exposeWith(password, job, bcryptVerifyExposed)
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
/**
|
|
848
|
+
* `hash` parsed for verifying: 60 characters, its setting well-formed and its
|
|
849
|
+
* digest in bcrypt's base64, or an `Err` naming what is not.
|
|
850
|
+
*/
|
|
851
|
+
const bcryptParseHash = (hash: string): Result<BcryptParsed, string> => {
|
|
739
852
|
if (hash.length !== 60) {
|
|
740
853
|
return Err("bcrypt: a hash is 60 characters")
|
|
741
854
|
}
|
|
@@ -743,18 +856,702 @@ export const bcryptVerify = (password: Secret<u8[]>, hash: string): Result<boole
|
|
|
743
856
|
if (parsed.isErr()) {
|
|
744
857
|
return Err(parsed.error)
|
|
745
858
|
}
|
|
746
|
-
const given: u8[] = new Array<u8>(BCRYPT_HASH_LENGTH)
|
|
747
859
|
let alphabet: i32 = 0
|
|
748
|
-
for (let i: i32 = 0; i < toI32(
|
|
749
|
-
const c: i32 = toI32(hash.charCodeAt(i))
|
|
860
|
+
for (let i: i32 = 0; i < toI32(hash.length); i++) {
|
|
750
861
|
if (i >= BCRYPT_SETTING_LENGTH) {
|
|
751
|
-
alphabet |= bcryptSextetOf(
|
|
862
|
+
alphabet |= bcryptSextetOf(toI32(hash.charCodeAt(i)))
|
|
752
863
|
}
|
|
753
|
-
given[i] = toU8(c)
|
|
754
864
|
}
|
|
755
865
|
if (alphabet < 0) {
|
|
756
866
|
return Err("bcrypt: the digest is not bcrypt base64")
|
|
757
867
|
}
|
|
758
|
-
|
|
759
|
-
|
|
868
|
+
return parsed
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/** The 60 characters of a hash `bcryptParseHash` accepted, as bytes into `given`, which the caller made and wipes. */
|
|
872
|
+
const bcryptBytesOf = (given: u8[], hash: string): void => {
|
|
873
|
+
for (let i: i32 = 0; i < toI32(given.length) && i < toI32(hash.length); i++) {
|
|
874
|
+
given[i] = toU8(hash.charCodeAt(i))
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// ---------------------------------------------------------------------------
|
|
879
|
+
// The four-lane batch. Four independent hashes share one `Secret<u32[]>`:
|
|
880
|
+
// lane k's Blowfish state at 1042k (0–4167), its expanded key at 4168 + 18k
|
|
881
|
+
// and its `$2a$` safety word at 4240 + k. One exposeWith per password XORs that
|
|
882
|
+
// lane's key in, and one more runs the expansion, the 2^cost rounds and the
|
|
883
|
+
// magic string on all four with their encryption chains interleaved, so the
|
|
884
|
+
// S-box loads of one lane overlap the others' and the core is not left
|
|
885
|
+
// waiting on one chain's latency.
|
|
886
|
+
// ---------------------------------------------------------------------------
|
|
887
|
+
|
|
888
|
+
/** Where the four expanded keys of 18 start in the batch state, after the four Blowfish states. */
|
|
889
|
+
const BCRYPT_BATCH_KEYS: i32 = 4168
|
|
890
|
+
/** Where the four safety words start, after the keys. */
|
|
891
|
+
const BCRYPT_BATCH_SAFETY: i32 = 4240
|
|
892
|
+
/** Words in the batch state: four Blowfish states, four expanded keys of 18 and four safety words. */
|
|
893
|
+
const BCRYPT_BATCH_WORDS: i32 = 4244
|
|
894
|
+
|
|
895
|
+
/**
|
|
896
|
+
* What the batch kernel reads besides the state: each lane's four salt words
|
|
897
|
+
* (lane k at 4k), each lane's `$2a$` safety word, the shared cost, the lanes
|
|
898
|
+
* that run (bit k for lane k), each lane's 29-character setting and, for
|
|
899
|
+
* verifying, each lane's hash to compare with.
|
|
900
|
+
*/
|
|
901
|
+
interface BcryptBatch {
|
|
902
|
+
salts: u32[]
|
|
903
|
+
safety: u32[]
|
|
904
|
+
cost: i32
|
|
905
|
+
mask: i32
|
|
906
|
+
prefixes: u8[][]
|
|
907
|
+
hashes: string[]
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/** The four states set to π and each lane's safety word stored, for `exposeWith` on the fresh batch state. */
|
|
911
|
+
const bcryptInit4 = (st: u32[], batch: BcryptBatch): i32 => {
|
|
912
|
+
if (st.length < 4244) {
|
|
913
|
+
return 0
|
|
914
|
+
}
|
|
915
|
+
const safety: u32[] = batch.safety
|
|
916
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
917
|
+
bcryptInitState(st, k * BCRYPT_STATE_WORDS)
|
|
918
|
+
st[BCRYPT_BATCH_SAFETY + k] = k < toI32(safety.length) ? safety[k] : 0
|
|
919
|
+
}
|
|
920
|
+
return 0
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
/**
|
|
924
|
+
* Word `arg & 31` of `bcryptKeySchedule` for `password`, with the `$2a$`
|
|
925
|
+
* safety when bit 5 of `arg` is set. The schedule is built in a fresh array
|
|
926
|
+
* and wiped here, so the one word is all that leaves the `exposeWith`.
|
|
927
|
+
*/
|
|
928
|
+
const bcryptKeyWord = (password: u8[], arg: i32): u32 => {
|
|
929
|
+
const expanded: u32[] = new Array<u32>(19)
|
|
930
|
+
const safety: u32 = (arg & 32) !== 0 ? 0x10000 : 0
|
|
931
|
+
bcryptKeySchedule(expanded, password, safety)
|
|
932
|
+
const k: i32 = arg & 31
|
|
933
|
+
const word: u32 = k < 19 ? expanded[k] : 0
|
|
934
|
+
wipe(expanded)
|
|
935
|
+
return word
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Lane `lane`'s key into the batch state: P XORed with it as `bcryptSetKey`
|
|
940
|
+
* XORs it, and the 18 words kept for the cost rounds. Each word is one
|
|
941
|
+
* `exposeWith` on the password, since the state is what this function is
|
|
942
|
+
* exposing and a `Secret` cannot be handed into it otherwise.
|
|
943
|
+
*/
|
|
944
|
+
const bcryptLoadLane = (st: u32[], password: Secret<u8[]>, lane: i32): i32 => {
|
|
945
|
+
if (st.length < 4244) {
|
|
946
|
+
return 0
|
|
947
|
+
}
|
|
948
|
+
if (lane < 0 || lane > 3) {
|
|
949
|
+
return 0
|
|
950
|
+
}
|
|
951
|
+
const at: i32 = lane * BCRYPT_STATE_WORDS
|
|
952
|
+
const kept: i32 = lane * 18 + BCRYPT_BATCH_KEYS
|
|
953
|
+
const flag: i32 = st[BCRYPT_BATCH_SAFETY + lane] !== 0 ? 32 : 0
|
|
954
|
+
for (let k: i32 = 0; k < 18; k++) {
|
|
955
|
+
const word: u32 = exposeWith(password, k | flag, bcryptKeyWord)
|
|
956
|
+
st[at + k] ^= word
|
|
957
|
+
st[kept + k] = word
|
|
958
|
+
}
|
|
959
|
+
st[at] ^= exposeWith(password, 18 | flag, bcryptKeyWord)
|
|
960
|
+
return 0
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** `bcryptLoadLane` for lane 0, in the shape `exposeWith` calls. */
|
|
964
|
+
const bcryptLoadLane0 = (st: u32[], password: Secret<u8[]>): i32 => bcryptLoadLane(st, password, 0)
|
|
965
|
+
|
|
966
|
+
/** `bcryptLoadLane` for lane 1. */
|
|
967
|
+
const bcryptLoadLane1 = (st: u32[], password: Secret<u8[]>): i32 => bcryptLoadLane(st, password, 1)
|
|
968
|
+
|
|
969
|
+
/** `bcryptLoadLane` for lane 2. */
|
|
970
|
+
const bcryptLoadLane2 = (st: u32[], password: Secret<u8[]>): i32 => bcryptLoadLane(st, password, 2)
|
|
971
|
+
|
|
972
|
+
/** `bcryptLoadLane` for lane 3. */
|
|
973
|
+
const bcryptLoadLane3 = (st: u32[], password: Secret<u8[]>): i32 => bcryptLoadLane(st, password, 3)
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* `bcryptBody` on all four lanes at once: each lane's state re-encrypted in
|
|
977
|
+
* place, the four encryption chains interleaved round by round so that one
|
|
978
|
+
* lane's S-box loads overlap the others' instead of waiting on their own. The
|
|
979
|
+
* hot loop of the batch: 2^(cost+1) calls of 521 blocks per lane.
|
|
980
|
+
*/
|
|
981
|
+
const bcryptBody4 = (st: u32[]): void => {
|
|
982
|
+
if (st.length < 4168) {
|
|
983
|
+
return
|
|
984
|
+
}
|
|
985
|
+
let l0: u32 = 0
|
|
986
|
+
let r0: u32 = 0
|
|
987
|
+
let l1: u32 = 0
|
|
988
|
+
let r1: u32 = 0
|
|
989
|
+
let l2: u32 = 0
|
|
990
|
+
let r2: u32 = 0
|
|
991
|
+
let l3: u32 = 0
|
|
992
|
+
let r3: u32 = 0
|
|
993
|
+
for (let i: i32 = 0; i + 1 < 1042; i += 2) {
|
|
994
|
+
l0 ^= st[0]
|
|
995
|
+
l1 ^= st[1042]
|
|
996
|
+
l2 ^= st[2084]
|
|
997
|
+
l3 ^= st[3126]
|
|
998
|
+
for (let n: i32 = 1; n < 17; n += 2) {
|
|
999
|
+
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1000
|
+
const l0b: i32 = toI32(l0 >>> 16) & 0xff
|
|
1001
|
+
const l0c: i32 = toI32(l0 >>> 8) & 0xff
|
|
1002
|
+
const l0d: i32 = toI32(l0) & 0xff
|
|
1003
|
+
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1004
|
+
const l1b: i32 = toI32(l1 >>> 16) & 0xff
|
|
1005
|
+
const l1c: i32 = toI32(l1 >>> 8) & 0xff
|
|
1006
|
+
const l1d: i32 = toI32(l1) & 0xff
|
|
1007
|
+
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1008
|
+
const l2b: i32 = toI32(l2 >>> 16) & 0xff
|
|
1009
|
+
const l2c: i32 = toI32(l2 >>> 8) & 0xff
|
|
1010
|
+
const l2d: i32 = toI32(l2) & 0xff
|
|
1011
|
+
const l3a: i32 = toI32(l3 >>> 24) & 0xff
|
|
1012
|
+
const l3b: i32 = toI32(l3 >>> 16) & 0xff
|
|
1013
|
+
const l3c: i32 = toI32(l3 >>> 8) & 0xff
|
|
1014
|
+
const l3d: i32 = toI32(l3) & 0xff
|
|
1015
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1016
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1017
|
+
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1018
|
+
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1019
|
+
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1020
|
+
const r0b: i32 = toI32(r0 >>> 16) & 0xff
|
|
1021
|
+
const r0c: i32 = toI32(r0 >>> 8) & 0xff
|
|
1022
|
+
const r0d: i32 = toI32(r0) & 0xff
|
|
1023
|
+
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1024
|
+
const r1b: i32 = toI32(r1 >>> 16) & 0xff
|
|
1025
|
+
const r1c: i32 = toI32(r1 >>> 8) & 0xff
|
|
1026
|
+
const r1d: i32 = toI32(r1) & 0xff
|
|
1027
|
+
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1028
|
+
const r2b: i32 = toI32(r2 >>> 16) & 0xff
|
|
1029
|
+
const r2c: i32 = toI32(r2 >>> 8) & 0xff
|
|
1030
|
+
const r2d: i32 = toI32(r2) & 0xff
|
|
1031
|
+
const r3a: i32 = toI32(r3 >>> 24) & 0xff
|
|
1032
|
+
const r3b: i32 = toI32(r3 >>> 16) & 0xff
|
|
1033
|
+
const r3c: i32 = toI32(r3 >>> 8) & 0xff
|
|
1034
|
+
const r3d: i32 = toI32(r3) & 0xff
|
|
1035
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1036
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1037
|
+
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1038
|
+
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1039
|
+
}
|
|
1040
|
+
const t0: u32 = r0
|
|
1041
|
+
r0 = l0
|
|
1042
|
+
l0 = t0 ^ st[17]
|
|
1043
|
+
st[i] = l0
|
|
1044
|
+
st[i + 1] = r0
|
|
1045
|
+
const t1: u32 = r1
|
|
1046
|
+
r1 = l1
|
|
1047
|
+
l1 = t1 ^ st[1059]
|
|
1048
|
+
st[1042 + i] = l1
|
|
1049
|
+
st[1042 + i + 1] = r1
|
|
1050
|
+
const t2: u32 = r2
|
|
1051
|
+
r2 = l2
|
|
1052
|
+
l2 = t2 ^ st[2101]
|
|
1053
|
+
st[2084 + i] = l2
|
|
1054
|
+
st[2084 + i + 1] = r2
|
|
1055
|
+
const t3: u32 = r3
|
|
1056
|
+
r3 = l3
|
|
1057
|
+
l3 = t3 ^ st[3143]
|
|
1058
|
+
st[3126 + i] = l3
|
|
1059
|
+
st[3126 + i + 1] = r3
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/** `bcryptEncrypt` on all four lanes: the block of lane `k` is `lr[2k]` and `lr[2k + 1]`. */
|
|
1064
|
+
const bcryptEncrypt4 = (st: u32[], lr: u32[]): void => {
|
|
1065
|
+
if (st.length < 4168) {
|
|
1066
|
+
return
|
|
1067
|
+
}
|
|
1068
|
+
if (lr.length < 8) {
|
|
1069
|
+
return
|
|
1070
|
+
}
|
|
1071
|
+
let l0: u32 = lr[0] ^ st[0]
|
|
1072
|
+
let r0: u32 = lr[1]
|
|
1073
|
+
let l1: u32 = lr[2] ^ st[1042]
|
|
1074
|
+
let r1: u32 = lr[3]
|
|
1075
|
+
let l2: u32 = lr[4] ^ st[2084]
|
|
1076
|
+
let r2: u32 = lr[5]
|
|
1077
|
+
let l3: u32 = lr[6] ^ st[3126]
|
|
1078
|
+
let r3: u32 = lr[7]
|
|
1079
|
+
for (let n: i32 = 1; n < 17; n += 2) {
|
|
1080
|
+
const l0a: i32 = toI32(l0 >>> 24) & 0xff
|
|
1081
|
+
const l0b: i32 = toI32(l0 >>> 16) & 0xff
|
|
1082
|
+
const l0c: i32 = toI32(l0 >>> 8) & 0xff
|
|
1083
|
+
const l0d: i32 = toI32(l0) & 0xff
|
|
1084
|
+
const l1a: i32 = toI32(l1 >>> 24) & 0xff
|
|
1085
|
+
const l1b: i32 = toI32(l1 >>> 16) & 0xff
|
|
1086
|
+
const l1c: i32 = toI32(l1 >>> 8) & 0xff
|
|
1087
|
+
const l1d: i32 = toI32(l1) & 0xff
|
|
1088
|
+
const l2a: i32 = toI32(l2 >>> 24) & 0xff
|
|
1089
|
+
const l2b: i32 = toI32(l2 >>> 16) & 0xff
|
|
1090
|
+
const l2c: i32 = toI32(l2 >>> 8) & 0xff
|
|
1091
|
+
const l2d: i32 = toI32(l2) & 0xff
|
|
1092
|
+
const l3a: i32 = toI32(l3 >>> 24) & 0xff
|
|
1093
|
+
const l3b: i32 = toI32(l3 >>> 16) & 0xff
|
|
1094
|
+
const l3c: i32 = toI32(l3 >>> 8) & 0xff
|
|
1095
|
+
const l3d: i32 = toI32(l3) & 0xff
|
|
1096
|
+
r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
|
|
1097
|
+
r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
|
|
1098
|
+
r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
|
|
1099
|
+
r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
|
|
1100
|
+
const r0a: i32 = toI32(r0 >>> 24) & 0xff
|
|
1101
|
+
const r0b: i32 = toI32(r0 >>> 16) & 0xff
|
|
1102
|
+
const r0c: i32 = toI32(r0 >>> 8) & 0xff
|
|
1103
|
+
const r0d: i32 = toI32(r0) & 0xff
|
|
1104
|
+
const r1a: i32 = toI32(r1 >>> 24) & 0xff
|
|
1105
|
+
const r1b: i32 = toI32(r1 >>> 16) & 0xff
|
|
1106
|
+
const r1c: i32 = toI32(r1 >>> 8) & 0xff
|
|
1107
|
+
const r1d: i32 = toI32(r1) & 0xff
|
|
1108
|
+
const r2a: i32 = toI32(r2 >>> 24) & 0xff
|
|
1109
|
+
const r2b: i32 = toI32(r2 >>> 16) & 0xff
|
|
1110
|
+
const r2c: i32 = toI32(r2 >>> 8) & 0xff
|
|
1111
|
+
const r2d: i32 = toI32(r2) & 0xff
|
|
1112
|
+
const r3a: i32 = toI32(r3 >>> 24) & 0xff
|
|
1113
|
+
const r3b: i32 = toI32(r3 >>> 16) & 0xff
|
|
1114
|
+
const r3c: i32 = toI32(r3 >>> 8) & 0xff
|
|
1115
|
+
const r3d: i32 = toI32(r3) & 0xff
|
|
1116
|
+
l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
|
|
1117
|
+
l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
|
|
1118
|
+
l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
|
|
1119
|
+
l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
|
|
1120
|
+
}
|
|
1121
|
+
lr[0] = r0 ^ st[17]
|
|
1122
|
+
lr[1] = l0
|
|
1123
|
+
lr[2] = r1 ^ st[1059]
|
|
1124
|
+
lr[3] = l1
|
|
1125
|
+
lr[4] = r2 ^ st[2101]
|
|
1126
|
+
lr[5] = l2
|
|
1127
|
+
lr[6] = r3 ^ st[3143]
|
|
1128
|
+
lr[7] = l3
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/** Each lane's P XORed with its kept expanded key, as `bcryptXorKey`. */
|
|
1132
|
+
const bcryptXorKey4 = (st: u32[]): void => {
|
|
1133
|
+
if (st.length < 4244) {
|
|
1134
|
+
return
|
|
1135
|
+
}
|
|
1136
|
+
for (let i: i32 = 0; i < 18; i++) {
|
|
1137
|
+
st[i] ^= st[4168 + i]
|
|
1138
|
+
st[1042 + i] ^= st[4186 + i]
|
|
1139
|
+
st[2084 + i] ^= st[4204 + i]
|
|
1140
|
+
st[3126 + i] ^= st[4222 + i]
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
/** Each lane's P XORed with its salt words, as `bcryptXorSalt`. */
|
|
1145
|
+
const bcryptXorSalt4 = (st: u32[], salts: u32[]): void => {
|
|
1146
|
+
if (salts.length < 16) {
|
|
1147
|
+
return
|
|
1148
|
+
}
|
|
1149
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1150
|
+
bcryptXorSalt(
|
|
1151
|
+
st,
|
|
1152
|
+
k * BCRYPT_STATE_WORDS,
|
|
1153
|
+
salts[k * 4],
|
|
1154
|
+
salts[k * 4 + 1],
|
|
1155
|
+
salts[k * 4 + 2],
|
|
1156
|
+
salts[k * 4 + 3]
|
|
1157
|
+
)
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
/**
|
|
1162
|
+
* The batch's hash, on the batch state with its keys loaded: the salted
|
|
1163
|
+
* expansion, 2^cost rounds and the magic string on all four lanes, and the 31
|
|
1164
|
+
* digest characters of lane k into `digests` at 31k, which the caller made and
|
|
1165
|
+
* wipes. The block and digest copies are wiped here.
|
|
1166
|
+
*/
|
|
1167
|
+
const bcryptEks4 = (st: u32[], salts: u32[], cost: i32, digests: u8[]): void => {
|
|
1168
|
+
if (st.length < 4244 || salts.length < 16) {
|
|
1169
|
+
return
|
|
1170
|
+
}
|
|
1171
|
+
const lr: u32[] = new Array<u32>(8)
|
|
1172
|
+
for (let i: i32 = 0; i < 1042; i += 2) {
|
|
1173
|
+
const half: i32 = (i & 2) !== 0 ? 2 : 0
|
|
1174
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1175
|
+
lr[2 * k] ^= salts[4 * k + half]
|
|
1176
|
+
lr[2 * k + 1] ^= salts[4 * k + half + 1]
|
|
1177
|
+
}
|
|
1178
|
+
bcryptEncrypt4(st, lr)
|
|
1179
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1180
|
+
const at: i32 = k * 1042 + i
|
|
1181
|
+
if (at + 1 < toI32(st.length)) {
|
|
1182
|
+
st[at] = lr[2 * k]
|
|
1183
|
+
st[at + 1] = lr[2 * k + 1]
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
let rounds: u32 = toU32(1) << toU32(cost)
|
|
1189
|
+
while (rounds !== 0) {
|
|
1190
|
+
bcryptXorKey4(st)
|
|
1191
|
+
bcryptBody4(st)
|
|
1192
|
+
bcryptXorSalt4(st, salts)
|
|
1193
|
+
bcryptBody4(st)
|
|
1194
|
+
rounds -= 1
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const digest: u8[] = new Array<u8>(96)
|
|
1198
|
+
bcryptMagic4(st, lr, digest, 0, 0x4f727068, 0x65616e42)
|
|
1199
|
+
bcryptMagic4(st, lr, digest, 8, 0x65686f6c, 0x64657253)
|
|
1200
|
+
bcryptMagic4(st, lr, digest, 16, 0x63727944, 0x6f756274)
|
|
1201
|
+
const one: u8[] = new Array<u8>(24)
|
|
1202
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1203
|
+
for (let j: i32 = 0; j < 24; j++) {
|
|
1204
|
+
one[j] = digest[24 * k + j]
|
|
1205
|
+
}
|
|
1206
|
+
bcryptEncodeInto(digests, 31 * k, one, 23)
|
|
1207
|
+
}
|
|
1208
|
+
wipe(lr)
|
|
1209
|
+
wipe(digest)
|
|
1210
|
+
wipe(one)
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* The four lanes' 60-byte hashes, lane k at 60k, for `exposeWith` on the
|
|
1215
|
+
* loaded batch state; a lane outside `batch.mask` is left zero. They are the
|
|
1216
|
+
* hashes `bcryptHash4` returns, so they are not secret and are not wiped; the
|
|
1217
|
+
* digest characters they are joined from are wiped here.
|
|
1218
|
+
*/
|
|
1219
|
+
const bcryptHash4Exposed = (st: u32[], batch: BcryptBatch): u8[] => {
|
|
1220
|
+
const out: u8[] = new Array<u8>(4 * BCRYPT_HASH_LENGTH)
|
|
1221
|
+
const digests: u8[] = new Array<u8>(124)
|
|
1222
|
+
bcryptEks4(st, batch.salts, batch.cost, digests)
|
|
1223
|
+
for (let k: i32 = 0; k < 4 && k < toI32(batch.prefixes.length); k++) {
|
|
1224
|
+
if ((batch.mask & (1 << k)) !== 0) {
|
|
1225
|
+
bcryptJoin(out, BCRYPT_HASH_LENGTH * k, batch.prefixes[k], digests, k)
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
wipe(digests)
|
|
1229
|
+
return out
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* Bit k set for each running lane whose rebuilt hash is `batch.hashes[k]`, for
|
|
1234
|
+
* `exposeWith` on the loaded batch state. The digest characters are built and
|
|
1235
|
+
* wiped here, so the bits are all that leaves.
|
|
1236
|
+
*/
|
|
1237
|
+
const bcryptVerify4Exposed = (st: u32[], batch: BcryptBatch): i32 => {
|
|
1238
|
+
const digests: u8[] = new Array<u8>(124)
|
|
1239
|
+
bcryptEks4(st, batch.salts, batch.cost, digests)
|
|
1240
|
+
let same: i32 = 0
|
|
1241
|
+
for (let k: i32 = 0; k < 4 && k < toI32(batch.prefixes.length) && k < toI32(batch.hashes.length); k++) {
|
|
1242
|
+
if ((batch.mask & (1 << k)) !== 0 && bcryptJoinedOk(batch.prefixes[k], digests, k, batch.hashes[k])) {
|
|
1243
|
+
same |= 1 << k
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
wipe(digests)
|
|
1247
|
+
return same
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/** `bcryptMagic` on all four lanes: lane k's eight bytes into `dst` at `24k + at`. */
|
|
1251
|
+
const bcryptMagic4 = (st: u32[], lr: u32[], dst: u8[], at: i32, l: u32, r: u32): void => {
|
|
1252
|
+
if (lr.length < 8) {
|
|
1253
|
+
return
|
|
1254
|
+
}
|
|
1255
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1256
|
+
lr[2 * k] = l
|
|
1257
|
+
lr[2 * k + 1] = r
|
|
1258
|
+
}
|
|
1259
|
+
for (let n: i32 = 0; n < 64; n++) {
|
|
1260
|
+
bcryptEncrypt4(st, lr)
|
|
1261
|
+
}
|
|
1262
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1263
|
+
bcryptPutBlock(dst, 24 * k + at, lr[2 * k], lr[2 * k + 1])
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
/**
|
|
1268
|
+
* The batch `state` set up for `batch.mask`: the four states set to π, the
|
|
1269
|
+
* safety words stored and each running lane's key loaded. A lane outside the
|
|
1270
|
+
* mask runs on no key and its characters are not read; lane 0 is always
|
|
1271
|
+
* given, and a null lane must be outside the mask.
|
|
1272
|
+
*/
|
|
1273
|
+
const bcryptLoad4 = (
|
|
1274
|
+
state: Secret<u32[]>,
|
|
1275
|
+
p0: Secret<u8[]>,
|
|
1276
|
+
p1: Secret<u8[]> | null,
|
|
1277
|
+
p2: Secret<u8[]> | null,
|
|
1278
|
+
p3: Secret<u8[]> | null,
|
|
1279
|
+
batch: BcryptBatch
|
|
1280
|
+
): void => {
|
|
1281
|
+
const mask: i32 = batch.mask
|
|
1282
|
+
exposeWith(state, batch, bcryptInit4)
|
|
1283
|
+
if ((mask & 1) !== 0) {
|
|
1284
|
+
exposeWith(state, p0, bcryptLoadLane0)
|
|
1285
|
+
}
|
|
1286
|
+
if (p1 !== null && (mask & 2) !== 0) {
|
|
1287
|
+
exposeWith(state, p1, bcryptLoadLane1)
|
|
1288
|
+
}
|
|
1289
|
+
if (p2 !== null && (mask & 4) !== 0) {
|
|
1290
|
+
exposeWith(state, p2, bcryptLoadLane2)
|
|
1291
|
+
}
|
|
1292
|
+
if (p3 !== null && (mask & 8) !== 0) {
|
|
1293
|
+
exposeWith(state, p3, bcryptLoadLane3)
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
/** Lane k's 60-byte hash out of the batch's `all`, as a string. */
|
|
1298
|
+
const bcryptLaneString = (all: u8[], k: i32): string =>
|
|
1299
|
+
String.fromBytes(all, BCRYPT_HASH_LENGTH * k, BCRYPT_HASH_LENGTH * (k + 1))
|
|
1300
|
+
|
|
1301
|
+
/** Bit k set for each lane given: lane 0 always, lanes 1–3 when not null. */
|
|
1302
|
+
const bcryptMaskOf = (p1: Secret<u8[]> | null, p2: Secret<u8[]> | null, p3: Secret<u8[]> | null): i32 => {
|
|
1303
|
+
let mask: i32 = 1
|
|
1304
|
+
if (p1 !== null) {
|
|
1305
|
+
mask |= 2
|
|
1306
|
+
}
|
|
1307
|
+
if (p2 !== null) {
|
|
1308
|
+
mask |= 4
|
|
1309
|
+
}
|
|
1310
|
+
if (p3 !== null) {
|
|
1311
|
+
mask |= 8
|
|
1312
|
+
}
|
|
1313
|
+
return mask
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* The `$2b$` hashes of up to four passwords at once, each as `bcryptHash`
|
|
1318
|
+
* makes it: lane k with the 16 bytes of `salts` at 16k and 2^`cost` rounds.
|
|
1319
|
+
* Lane 0 is always hashed; a null lane gives "" and its salt bytes are not
|
|
1320
|
+
* read. Two to four lanes run as one interleaved computation, which on one
|
|
1321
|
+
* core takes about the time of 1.2 single hashes (bench/crypto/bcrypt); one
|
|
1322
|
+
* lane runs as `bcryptHash`. An `Err` for `salts` of another length than 64
|
|
1323
|
+
* or a cost outside 4–31.
|
|
1324
|
+
*/
|
|
1325
|
+
export const bcryptHash4 = (
|
|
1326
|
+
p0: Secret<u8[]>,
|
|
1327
|
+
p1: Secret<u8[]> | null,
|
|
1328
|
+
p2: Secret<u8[]> | null,
|
|
1329
|
+
p3: Secret<u8[]> | null,
|
|
1330
|
+
salts: u8[],
|
|
1331
|
+
cost: i32
|
|
1332
|
+
): Result<string[], string> => {
|
|
1333
|
+
if (salts.length !== 64) {
|
|
1334
|
+
return Err("bcrypt: the salts are not 64 bytes")
|
|
1335
|
+
}
|
|
1336
|
+
if (cost < BCRYPT_MIN_COST || cost > BCRYPT_MAX_COST) {
|
|
1337
|
+
return Err("bcrypt: the cost is outside 4 to 31")
|
|
1338
|
+
}
|
|
1339
|
+
const mask: i32 = bcryptMaskOf(p1, p2, p3)
|
|
1340
|
+
const hashes: string[] = ["", "", "", ""]
|
|
1341
|
+
if (mask === 1) {
|
|
1342
|
+
const one: Result<string, string> = bcryptHash(p0, bcryptSaltOf(salts, 0), cost)
|
|
1343
|
+
if (one.isErr()) {
|
|
1344
|
+
return Err(one.error)
|
|
1345
|
+
}
|
|
1346
|
+
hashes[0] = one.value
|
|
1347
|
+
return Ok(hashes)
|
|
1348
|
+
}
|
|
1349
|
+
const words: u32[] = new Array<u32>(16)
|
|
1350
|
+
const prefixes: u8[][] = []
|
|
1351
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1352
|
+
if ((mask & (1 << k)) === 0) {
|
|
1353
|
+
prefixes.push([])
|
|
1354
|
+
continue
|
|
1355
|
+
}
|
|
1356
|
+
prefixes.push(bcryptBatchSalt(words, salts, k, cost))
|
|
1357
|
+
}
|
|
1358
|
+
const none: string[] = []
|
|
1359
|
+
const batch: BcryptBatch = {
|
|
1360
|
+
salts: words,
|
|
1361
|
+
safety: new Array<u32>(4),
|
|
1362
|
+
cost: cost,
|
|
1363
|
+
mask: mask,
|
|
1364
|
+
prefixes: prefixes,
|
|
1365
|
+
hashes: none,
|
|
1366
|
+
}
|
|
1367
|
+
const state: Secret<u32[]> = secret(new Array<u32>(BCRYPT_BATCH_WORDS))
|
|
1368
|
+
bcryptLoad4(state, p0, p1, p2, p3, batch)
|
|
1369
|
+
const all: u8[] = exposeWith(state, batch, bcryptHash4Exposed)
|
|
1370
|
+
wipe(state)
|
|
1371
|
+
const out: string[] = []
|
|
1372
|
+
for (let k: i32 = 0; k < 4; k++) {
|
|
1373
|
+
out.push((mask & (1 << k)) !== 0 ? bcryptLaneString(all, k) : "")
|
|
1374
|
+
}
|
|
1375
|
+
return Ok(out)
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
/** Lane `k`'s salt from the 64 bytes of `salts` into `words` at 4k as four words, and its 29-byte setting. */
|
|
1379
|
+
const bcryptBatchSalt = (words: u32[], salts: u8[], k: i32, cost: i32): u8[] => {
|
|
1380
|
+
const salt: u8[] = bcryptSaltOf(salts, k)
|
|
1381
|
+
const four: u32[] = bcryptSaltWords(salt)
|
|
1382
|
+
for (let j: i32 = 0; j < 4 && j < toI32(four.length) && 4 * k + j < toI32(words.length); j++) {
|
|
1383
|
+
words[4 * k + j] = four[j]
|
|
1384
|
+
}
|
|
1385
|
+
return bcryptSettingOf(salt, cost)
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/** The 16 salt bytes of lane `k` from the 64 of a batch. */
|
|
1389
|
+
const bcryptSaltOf = (salts: u8[], k: i32): u8[] => {
|
|
1390
|
+
const salt: u8[] = new Array<u8>(16)
|
|
1391
|
+
for (let j: i32 = 0; j < 16 && 16 * k + j < toI32(salts.length); j++) {
|
|
1392
|
+
salt[j] = salts[16 * k + j]
|
|
1393
|
+
}
|
|
1394
|
+
return salt
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
/** The 60 bytes of lane `k`'s hash into `out` at `at`: its 29-byte setting, then its 31 digest characters from the batch's 124. */
|
|
1398
|
+
const bcryptJoin = (out: u8[], at: i32, prefix: u8[], digests: u8[], k: i32): void => {
|
|
1399
|
+
bcryptPut(out, at, prefix)
|
|
1400
|
+
const o: i32 = at + BCRYPT_SETTING_LENGTH
|
|
1401
|
+
for (
|
|
1402
|
+
let j: i32 = 0;
|
|
1403
|
+
j < 31 && 31 * k + j < toI32(digests.length) && o + j >= 0 && o + j < toI32(out.length);
|
|
1404
|
+
j++
|
|
1405
|
+
) {
|
|
1406
|
+
out[o + j] = digests[31 * k + j]
|
|
1407
|
+
}
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Whether each of `hashes` (four strings) is its lane's password's, as
|
|
1412
|
+
* `bcryptVerify` decides it: lane 0 always, a null lane `false` and its
|
|
1413
|
+
* string not read. Lanes are grouped by the cost their hash names and each
|
|
1414
|
+
* group of two or more runs as one interleaved computation; a group of one
|
|
1415
|
+
* runs as `bcryptVerify`. A malformed hash in any given lane makes the whole
|
|
1416
|
+
* call an `Err` naming the lane ("bcrypt: lane 2: …") before any hashing, so
|
|
1417
|
+
* one bad row in a batch cannot be read as a wrong password.
|
|
1418
|
+
*/
|
|
1419
|
+
export const bcryptVerify4 = (
|
|
1420
|
+
p0: Secret<u8[]>,
|
|
1421
|
+
p1: Secret<u8[]> | null,
|
|
1422
|
+
p2: Secret<u8[]> | null,
|
|
1423
|
+
p3: Secret<u8[]> | null,
|
|
1424
|
+
hashes: string[]
|
|
1425
|
+
): Result<boolean[], string> => {
|
|
1426
|
+
if (hashes.length !== 4) {
|
|
1427
|
+
return Err("bcrypt: a batch is four hashes")
|
|
1428
|
+
}
|
|
1429
|
+
const mask: i32 = bcryptMaskOf(p1, p2, p3)
|
|
1430
|
+
const lanes: BcryptParsed[] = []
|
|
1431
|
+
for (let k: i32 = 0; k < 4 && k < toI32(hashes.length); k++) {
|
|
1432
|
+
if ((mask & (1 << k)) !== 0) {
|
|
1433
|
+
const parsed: Result<BcryptParsed, string> = bcryptParseHash(hashes[k])
|
|
1434
|
+
if (parsed.isErr()) {
|
|
1435
|
+
const why: string = parsed.error.startsWith("bcrypt: ") ? parsed.error.substring(8) : parsed.error
|
|
1436
|
+
return Err(`bcrypt: lane ${k}: ${why}`)
|
|
1437
|
+
}
|
|
1438
|
+
lanes.push(parsed.value)
|
|
1439
|
+
} else {
|
|
1440
|
+
lanes.push({
|
|
1441
|
+
setting: { salt: new Array<u32>(4), cost: toI32(0), safety: toU32(0) },
|
|
1442
|
+
prefix: new Array<u8>(0),
|
|
1443
|
+
})
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
const ok: boolean[] = [false, false, false, false]
|
|
1447
|
+
let done: i32 = ~mask & 15
|
|
1448
|
+
for (
|
|
1449
|
+
let k: i32 = 0;
|
|
1450
|
+
k < 4 && k < toI32(lanes.length) && k < toI32(hashes.length) && k < toI32(ok.length);
|
|
1451
|
+
k++
|
|
1452
|
+
) {
|
|
1453
|
+
if ((done & (1 << k)) === 0) {
|
|
1454
|
+
const cost: i32 = lanes[k].setting.cost
|
|
1455
|
+
let group: i32 = 0
|
|
1456
|
+
for (let j: i32 = k; j < 4 && j < toI32(lanes.length); j++) {
|
|
1457
|
+
if ((done & (1 << j)) === 0 && lanes[j].setting.cost === cost) {
|
|
1458
|
+
group |= 1 << j
|
|
1459
|
+
}
|
|
1460
|
+
}
|
|
1461
|
+
done |= group
|
|
1462
|
+
if (group === 1 << k) {
|
|
1463
|
+
const one: boolean = bcryptLaneOk(p0, p1, p2, p3, k, lanes[k], hashes[k])
|
|
1464
|
+
if (k < toI32(ok.length)) {
|
|
1465
|
+
ok[k] = one
|
|
1466
|
+
}
|
|
1467
|
+
} else {
|
|
1468
|
+
bcryptVerifyGroup(p0, p1, p2, p3, group, lanes, hashes, ok)
|
|
1469
|
+
}
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return Ok(ok)
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
/** Whether lane `k`'s password, hashed alone with `lane`'s setting, gives `hash`. */
|
|
1476
|
+
const bcryptLaneOk = (
|
|
1477
|
+
p0: Secret<u8[]>,
|
|
1478
|
+
p1: Secret<u8[]> | null,
|
|
1479
|
+
p2: Secret<u8[]> | null,
|
|
1480
|
+
p3: Secret<u8[]> | null,
|
|
1481
|
+
k: i32,
|
|
1482
|
+
lane: BcryptParsed,
|
|
1483
|
+
hash: string
|
|
1484
|
+
): boolean => {
|
|
1485
|
+
if (k === 0) {
|
|
1486
|
+
return bcryptVerifyOne(p0, lane, hash)
|
|
1487
|
+
}
|
|
1488
|
+
if (k === 1 && p1 !== null) {
|
|
1489
|
+
return bcryptVerifyOne(p1, lane, hash)
|
|
1490
|
+
}
|
|
1491
|
+
if (k === 2 && p2 !== null) {
|
|
1492
|
+
return bcryptVerifyOne(p2, lane, hash)
|
|
1493
|
+
}
|
|
1494
|
+
if (k === 3 && p3 !== null) {
|
|
1495
|
+
return bcryptVerifyOne(p3, lane, hash)
|
|
1496
|
+
}
|
|
1497
|
+
return false
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/** Whether lane `k`'s hash joined from `prefix` and the batch's digests is `hash`. */
|
|
1501
|
+
const bcryptJoinedOk = (prefix: u8[], digests: u8[], k: i32, hash: string): boolean => {
|
|
1502
|
+
const joined: u8[] = new Array<u8>(BCRYPT_HASH_LENGTH)
|
|
1503
|
+
bcryptJoin(joined, 0, prefix, digests, k)
|
|
1504
|
+
return bcryptSameWiped(joined, hash)
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
/** One interleaved run for the lanes in `group`, all of one cost, each lane's answer into `ok`. */
|
|
1508
|
+
const bcryptVerifyGroup = (
|
|
1509
|
+
p0: Secret<u8[]>,
|
|
1510
|
+
p1: Secret<u8[]> | null,
|
|
1511
|
+
p2: Secret<u8[]> | null,
|
|
1512
|
+
p3: Secret<u8[]> | null,
|
|
1513
|
+
group: i32,
|
|
1514
|
+
lanes: BcryptParsed[],
|
|
1515
|
+
hashes: string[],
|
|
1516
|
+
ok: boolean[]
|
|
1517
|
+
): void => {
|
|
1518
|
+
const words: u32[] = new Array<u32>(16)
|
|
1519
|
+
const safety: u32[] = new Array<u32>(4)
|
|
1520
|
+
const prefixes: u8[][] = []
|
|
1521
|
+
const given: string[] = []
|
|
1522
|
+
let cost: i32 = 0
|
|
1523
|
+
// A copy, so no callee is handed the caller's `hashes` (the thread rule).
|
|
1524
|
+
for (let k: i32 = 0; k < toI32(hashes.length); k++) {
|
|
1525
|
+
given.push(hashes[k])
|
|
1526
|
+
}
|
|
1527
|
+
for (let k: i32 = 0; k < toI32(lanes.length); k++) {
|
|
1528
|
+
prefixes.push(lanes[k].prefix)
|
|
1529
|
+
}
|
|
1530
|
+
for (let k: i32 = 0; k < 4 && k < toI32(lanes.length) && k < toI32(safety.length); k++) {
|
|
1531
|
+
if ((group & (1 << k)) !== 0) {
|
|
1532
|
+
const setting: BcryptSetting = lanes[k].setting
|
|
1533
|
+
cost = setting.cost
|
|
1534
|
+
safety[k] = setting.safety
|
|
1535
|
+
for (let j: i32 = 0; j < 4 && j < toI32(setting.salt.length); j++) {
|
|
1536
|
+
words[4 * k + j] = setting.salt[j]
|
|
1537
|
+
}
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
const batch: BcryptBatch = {
|
|
1541
|
+
salts: words,
|
|
1542
|
+
safety: safety,
|
|
1543
|
+
cost: cost,
|
|
1544
|
+
mask: group,
|
|
1545
|
+
prefixes: prefixes,
|
|
1546
|
+
hashes: given,
|
|
1547
|
+
}
|
|
1548
|
+
const state: Secret<u32[]> = secret(new Array<u32>(BCRYPT_BATCH_WORDS))
|
|
1549
|
+
bcryptLoad4(state, p0, p1, p2, p3, batch)
|
|
1550
|
+
const same: i32 = exposeWith(state, batch, bcryptVerify4Exposed)
|
|
1551
|
+
wipe(state)
|
|
1552
|
+
for (let k: i32 = 0; k < 4 && k < toI32(ok.length); k++) {
|
|
1553
|
+
if ((group & (1 << k)) !== 0) {
|
|
1554
|
+
ok[k] = (same & (1 << k)) !== 0
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
760
1557
|
}
|