Skip to content

Commit 4943272

Browse files
authored
Merge pull request #25 from Brooooooklyn/bcrypt
feat(bcrypt): fastest bcrypt
2 parents 7121c80 + f12f7fd commit 4943272

File tree

21 files changed

+1242
-8
lines changed

21 files changed

+1242
-8
lines changed

.github/workflows/ci.yaml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,17 @@ jobs:
8585
run: yarn lint
8686
if: matrix.os == 'ubuntu-latest'
8787

88-
- name: typecheck
88+
- name: TypeCheck
8989
run: yarn typecheck
9090
if: matrix.os == 'ubuntu-latest'
9191

92+
- name: Cargo test
93+
uses: actions-rs/cargo@v1
94+
timeout-minutes: 5
95+
with:
96+
command: test
97+
args: -p nodr-rs-bcrypt --lib -- --nocapture
98+
9299
- name: Run build
93100
run: |
94101
cargo build --release

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"./packages/bcrypt",
34
"./packages/crc32",
45
"./packages/jieba"
56
]

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ Make rust crates binding to NodeJS use [napi-rs](https://github.com/Brooooooklyn
1616

1717
# Packages
1818

19-
| Package | Status | Description |
20-
| ---------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
21-
| [`@node-rs/crc32`](./packages/crc32/README.md) | ![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg) | Fastest `CRC32` implementation using `SIMD` |
22-
| [`@node-rs/jieba`](./packages/jieba/README.md) | ![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg) | [`jieba-rs`](https://github.com/messense/jieba-rs) binding |
19+
| Package | Status | Description |
20+
| -------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------- |
21+
| [`@node-rs/crc32`](./packages/crc32) | ![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg) | Fastest `CRC32` implementation using `SIMD` |
22+
| [`@node-rs/jieba`](./packages/jieba) | ![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg) | [`jieba-rs`](https://github.com/messense/jieba-rs) binding |
23+
| [`@node-rs/bcrypt`](./packages/bcrypt) | ![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg) | Fastest bcrypt implementation |

packages/bcrypt/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "nodr-rs-bcrypt"
3+
version = "0.1.0"
4+
authors = ["LongYinan <[email protected]>"]
5+
edition = "2018"
6+
7+
[lib]
8+
crate-type = ["cdylib"]
9+
10+
[dependencies]
11+
radix64 = "0.6"
12+
blowfish = { version = "0.4", features = ["bcrypt"] }
13+
byteorder = "1"
14+
napi-rs = { version = "0.3" }
15+
napi-rs-derive = { version = "0.2" }
16+
rand = "0.7"
17+
phf = { version = "0.8", features = ["macros"] }
18+
19+
[target.'cfg(unix)'.dependencies]
20+
jemallocator = { version = "0.3", features = ["disable_initial_exec_tls"] }
21+
22+
[dev-dependencies]
23+
quickcheck = "0.9"
24+
25+
[build-dependencies]
26+
napi-build = { version = "0.1" }

packages/bcrypt/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# `@node-rs/bcrypt`
2+
3+
![](https://github.com/Brooooooklyn/node-rs/workflows/CI/badge.svg)
4+
5+
🚀 Fastest bcrypt in NodeJS
6+
7+
## Support matrix
8+
9+
| | node 10 | node12 | node13 | node14 |
10+
| ----------------- | ------- | ------ | ------ | ------ |
11+
| Windows 64 latest |||||
12+
| macOS latest |||||
13+
| Linux |||||
14+
15+
## Usage
16+
17+
```typescript
18+
export const DEFAULT_ROUND = 12
19+
20+
function hashSync(password: string | Buffer, round?: number): string
21+
function hash(password: string | Buffer, round?: number): Promise<string>
22+
function verifySync(password: string | Buffer, hash: string | Buffer): boolean
23+
function verify(password: string | Buffer, hash: string | Buffer): Promise<boolean>
24+
```
25+
26+
## Bench
27+
28+
```
29+
Model Name: MacBook Pro
30+
Model Identifier: MacBookPro15,1
31+
Processor Name: Intel Core i7
32+
Processor Speed: 2.6 GHz
33+
Number of Processors: 1
34+
Total Number of Cores: 6
35+
L2 Cache (per Core): 256 KB
36+
L3 Cache: 12 MB
37+
Hyper-Threading Technology: Enabled
38+
Memory: 16 GB
39+
```
40+
41+
<pre>
42+
@node-rs/bcrypt x <span style="color: hotpink;">72.11</span> ops/sec ±1.43% (33 runs sampled)
43+
node bcrypt x <span style="color: hotpink;">62.75</span> ops/sec ±2.95% (30 runs sampled)
44+
Async hash round 10 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
45+
@node-rs/bcrypt x <span style="color: hotpink;">18.49</span> ops/sec ±1.04% (12 runs sampled)
46+
node bcrypt x <span style="color: hotpink;">16.67</span> ops/sec ±2.05% (11 runs sampled)
47+
Async hash round 12 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
48+
@node-rs/bcrypt x <span style="color: hotpink;">3.99</span> ops/sec ±3.17% (6 runs sampled)
49+
node bcrypt x <span style="color: hotpink;">3.13</span> ops/sec ±1.92% (6 runs sampled)
50+
Async hash round 14 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
51+
@node-rs/bcrypt x <span style="color: hotpink;">14.32</span> ops/sec ±0.55% (10 runs sampled)
52+
node bcrypt x <span style="color: hotpink;">13.55</span> ops/sec ±2.83% (10 runs sampled)
53+
Async verify bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
54+
@node-rs/bcrypt x <span style="color: hotpink;">15.98</span> ops/sec ±1.12% (44 runs sampled)
55+
node bcrypt x <span style="color: hotpink;">14.55</span> ops/sec ±1.30% (40 runs sampled)
56+
Hash round 10 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
57+
@node-rs/bcrypt x <span style="color: hotpink;">4.65</span> ops/sec ±3.60% (16 runs sampled)
58+
node bcrypt x <span style="color: hotpink;">4.26</span> ops/sec ±1.90% (15 runs sampled)
59+
Hash round 12 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
60+
@node-rs/bcrypt x <span style="color: hotpink;">1.16</span> ops/sec ±2.65% (7 runs sampled)
61+
node bcrypt x <span style="color: hotpink;">1.04</span> ops/sec ±2.95% (7 runs sampled)
62+
Hash round 14 bench suite: Fastest is <span style="color: rgb(80, 250, 123);">@node-rs/bcrypt</span>
63+
</pre>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import test from 'ava'
2+
import { verifySync, hash } from '../index'
3+
4+
const { hashSync } = require('bcrypt')
5+
6+
const fx = Buffer.from('bcrypt-test-password')
7+
8+
const hashedPassword = hashSync(fx)
9+
10+
test('verifySync hashed password from bcrypt should be true', (t) => {
11+
t.true(verifySync(fx, hashedPassword))
12+
})
13+
14+
test('verifySync hashed password from @node-rs/bcrypt should be true', async (t) => {
15+
const hashed = await hash(fx)
16+
t.true(verifySync(fx, hashed))
17+
})
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const { Suite } = require('benchmark')
2+
const { hashSync, hash, compare } = require('bcrypt')
3+
const { cpus } = require('os')
4+
const chalk = require('chalk')
5+
const { range } = require('lodash')
6+
7+
const { hash: napiHash, hashSync: napiHashSync, verify } = require('../index')
8+
9+
const hashRounds = [10, 12, 14]
10+
const parallel = cpus().length
11+
12+
const password = 'node-rust-password'
13+
14+
function runAsync(round) {
15+
const asyncHashSuite = new Suite(`Async hash round ${round}`)
16+
return new Promise((resolve) => {
17+
asyncHashSuite
18+
.add('@node-rs/bcrypt', {
19+
defer: true,
20+
fn: (deferred) => {
21+
Promise.all(range(parallel).map(() => napiHash(password, round))).then(() => {
22+
deferred.resolve()
23+
})
24+
},
25+
})
26+
.add('node bcrypt', {
27+
defer: true,
28+
fn: (deferred) => {
29+
Promise.all(range(parallel).map(() => hash(password, round))).then(() => {
30+
deferred.resolve()
31+
})
32+
},
33+
})
34+
.on('cycle', function (event) {
35+
event.target.hz = event.target.hz * parallel
36+
console.info(String(event.target))
37+
})
38+
.on('complete', function () {
39+
console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`)
40+
resolve()
41+
})
42+
.run({ async: true })
43+
})
44+
}
45+
46+
hashRounds
47+
.reduce(async (acc, cur) => {
48+
await acc
49+
return runAsync(cur)
50+
}, Promise.resolve())
51+
.then(
52+
() =>
53+
new Promise((resolve) => {
54+
const suite = new Suite('Async verify')
55+
const hash = napiHashSync(password)
56+
suite
57+
.add({
58+
name: '@node-rs/bcrypt',
59+
defer: true,
60+
fn: (deferred) => {
61+
Promise.all(range(parallel).map(() => verify(password, hash))).then(() => {
62+
deferred.resolve()
63+
})
64+
},
65+
})
66+
.add({
67+
name: 'node bcrypt',
68+
defer: true,
69+
fn: (deferred) => {
70+
Promise.all(range(parallel).map(() => compare(password, hash))).then(() => {
71+
deferred.resolve()
72+
})
73+
},
74+
})
75+
.on('cycle', function (event) {
76+
event.target.hz = event.target.hz * parallel
77+
console.info(String(event.target))
78+
})
79+
.on('complete', function () {
80+
resolve()
81+
console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`)
82+
})
83+
.run()
84+
}),
85+
)
86+
.then(() => {
87+
for (const round of hashRounds) {
88+
const syncHashSuite = new Suite(`Hash round ${round}`)
89+
syncHashSuite
90+
.add('@node-rs/bcrypt', () => {
91+
napiHashSync(password, round)
92+
})
93+
.add('node bcrypt', () => {
94+
hashSync(password, round)
95+
})
96+
.on('cycle', function (event) {
97+
console.info(String(event.target))
98+
})
99+
.on('complete', function () {
100+
console.info(`${this.name} bench suite: Fastest is ${chalk.green(this.filter('fastest').map('name'))}`)
101+
})
102+
.run()
103+
}
104+
})

packages/bcrypt/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
extern crate napi_build;
2+
3+
fn main() {
4+
napi_build::setup();
5+
}

packages/bcrypt/index.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const DEFAULT_COST: 12
2+
3+
export function hashSync(password: string | Buffer, round?: number): string
4+
export function hash(password: string | Buffer, round?: number): Promise<string>
5+
export function verifySync(password: string | Buffer, hash: string | Buffer): boolean
6+
export function verify(password: string | Buffer, hash: string | Buffer): Promise<boolean>

packages/bcrypt/index.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const { locateBinding } = require('@node-rs/helper')
2+
3+
const binding = require(locateBinding(__dirname, 'bcrypt'))
4+
5+
const DEFAULT_COST = 12
6+
7+
module.exports = {
8+
DEFAULT_COST: DEFAULT_COST,
9+
10+
genSalt: function genSalt(round = 10, version = '2b') {
11+
return binding.genSalt(round, version)
12+
},
13+
14+
hashSync: function hashSync(password, round = DEFAULT_COST) {
15+
const input = Buffer.isBuffer(password) ? password : Buffer.from(password)
16+
return binding.hashSync(input, round)
17+
},
18+
19+
hash: function hash(password, round = DEFAULT_COST) {
20+
const input = Buffer.isBuffer(password) ? password : Buffer.from(password)
21+
return binding.hash(input, round)
22+
},
23+
24+
verifySync: function verifySync(password, hash) {
25+
password = Buffer.isBuffer(password) ? password : Buffer.from(password)
26+
hash = Buffer.isBuffer(hash) ? hash : Buffer.from(hash)
27+
return binding.verifySync(password, hash)
28+
},
29+
30+
verify: function verify(password, hash) {
31+
password = Buffer.isBuffer(password) ? password : Buffer.from(password)
32+
hash = Buffer.isBuffer(hash) ? hash : Buffer.from(hash)
33+
return binding.verify(password, hash)
34+
},
35+
}

0 commit comments

Comments
 (0)