@vlasky/shacrypt 0.2.0 → 0.3.0
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/CLAUDE.md +56 -0
- package/Makefile +1 -1
- package/package.json +6 -5
- package/src/sha256crypt.c +1 -1
- package/src/sha512crypt.c +1 -1
- package/src/shacrypt.cc +77 -4
- package/test/tests.js +49 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
shacrypt is a Node.js native addon that provides cross-platform support for SHA-256 crypt and SHA-512 crypt password hashing. It wraps around Ulrich Drepper's C implementation for high performance cryptographic operations.
|
|
8
|
+
|
|
9
|
+
## Architecture
|
|
10
|
+
|
|
11
|
+
- **Native C++ addon**: Built using node-gyp and NAN (Native Abstractions for Node.js)
|
|
12
|
+
- **Core C implementations**: `sha256crypt.c` and `sha512crypt.c` contain the actual cryptographic algorithms
|
|
13
|
+
- **Node.js wrapper**: `shacrypt.cc` provides the V8/Node.js bindings with both sync and async interfaces
|
|
14
|
+
- **JavaScript API**: `shacrypt.js` provides the public API with input validation and parameter handling
|
|
15
|
+
- **Dual execution modes**: Synchronous calls block the event loop, asynchronous calls use libuv thread pool
|
|
16
|
+
|
|
17
|
+
## Key Files
|
|
18
|
+
|
|
19
|
+
- `src/shacrypt.cc` - Main C++ addon code with NAN bindings
|
|
20
|
+
- `src/sha256crypt.c` and `src/sha512crypt.c` - Core cryptographic implementations
|
|
21
|
+
- `shacrypt.js` - JavaScript API wrapper with validation logic
|
|
22
|
+
- `binding.gyp` - Build configuration for the native addon
|
|
23
|
+
- `test/tests.js` - Comprehensive test vectors for both SHA-256 and SHA-512
|
|
24
|
+
|
|
25
|
+
## Development Commands
|
|
26
|
+
|
|
27
|
+
### Build the native addon
|
|
28
|
+
```bash
|
|
29
|
+
npm install
|
|
30
|
+
# or
|
|
31
|
+
make
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Run tests
|
|
35
|
+
```bash
|
|
36
|
+
make test
|
|
37
|
+
# or
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The tests use Mocha with test vectors that validate against known good SHA crypt implementations.
|
|
42
|
+
|
|
43
|
+
### Clean build artifacts
|
|
44
|
+
```bash
|
|
45
|
+
make clean
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## API Design
|
|
49
|
+
|
|
50
|
+
Both `sha256crypt()` and `sha512crypt()` support:
|
|
51
|
+
- Synchronous and asynchronous execution (callback as last parameter)
|
|
52
|
+
- Automatic salt generation if not provided
|
|
53
|
+
- Custom round counts (defaults to 5000, minimum 1000)
|
|
54
|
+
- Salt string validation and formatting
|
|
55
|
+
|
|
56
|
+
The JavaScript wrapper handles parameter validation and salt formatting before calling the native C++ functions.
|
package/Makefile
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vlasky/shacrypt",
|
|
3
3
|
"description": "Wrapper over sha256-crypt and sha512-crypt functions originally created by Ulrich Drepper https://www.akkadia.org/drepper/SHA-crypt.txt",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"homepage": "https://github.com/vlasky/shacrypt",
|
|
6
6
|
"author": "Vlad Lasky <github@vladlasky.com> (https://github.com/vlasky/)",
|
|
7
7
|
"contributors": [
|
|
@@ -9,17 +9,18 @@
|
|
|
9
9
|
],
|
|
10
10
|
"main": "shacrypt",
|
|
11
11
|
"scripts": {
|
|
12
|
+
"install": "node-gyp rebuild",
|
|
12
13
|
"test": "make test"
|
|
13
14
|
},
|
|
14
15
|
"dependencies": {
|
|
15
|
-
"nan": "^2.
|
|
16
|
+
"nan": "^2.22.2"
|
|
16
17
|
},
|
|
17
18
|
"devDependencies": {
|
|
18
|
-
"mocha": "
|
|
19
|
-
"chai": "
|
|
19
|
+
"mocha": "^10.8.2",
|
|
20
|
+
"chai": "^4.5.0"
|
|
20
21
|
},
|
|
21
22
|
"engines": {
|
|
22
|
-
"node": ">= 0.
|
|
23
|
+
"node": ">= 16.0.0"
|
|
23
24
|
},
|
|
24
25
|
"bugs": {
|
|
25
26
|
"url": "https://github.com/vlasky/shacrypt/issues"
|
package/src/sha256crypt.c
CHANGED
|
@@ -449,7 +449,7 @@ sha256_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
|
|
|
449
449
|
sha256_init_ctx (&alt_ctx);
|
|
450
450
|
|
|
451
451
|
/* For every character in the password add the entire password. */
|
|
452
|
-
for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
|
|
452
|
+
for (cnt = 0; cnt < (size_t)(16 + alt_result[0]); ++cnt)
|
|
453
453
|
sha256_process_bytes (salt, salt_len, &alt_ctx);
|
|
454
454
|
|
|
455
455
|
/* Finish the digest. */
|
package/src/sha512crypt.c
CHANGED
|
@@ -479,7 +479,7 @@ sha512_crypt_r (const char *key, const char *salt, char *buffer, int buflen)
|
|
|
479
479
|
sha512_init_ctx (&alt_ctx);
|
|
480
480
|
|
|
481
481
|
/* For every character in the password add the entire password. */
|
|
482
|
-
for (cnt = 0; cnt < 16 + alt_result[0]; ++cnt)
|
|
482
|
+
for (cnt = 0; cnt < (size_t)(16 + alt_result[0]); ++cnt)
|
|
483
483
|
sha512_process_bytes (salt, salt_len, &alt_ctx);
|
|
484
484
|
|
|
485
485
|
/* Finish the digest. */
|
package/src/shacrypt.cc
CHANGED
|
@@ -19,17 +19,49 @@ using Nan::Null;
|
|
|
19
19
|
using Nan::To;
|
|
20
20
|
|
|
21
21
|
NAN_METHOD(sha256crypt) {
|
|
22
|
+
if (info.Length() < 2) {
|
|
23
|
+
Nan::ThrowError("Wrong number of arguments");
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (!info[0]->IsString() || !info[1]->IsString()) {
|
|
28
|
+
Nan::ThrowTypeError("Arguments must be strings");
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
Nan::Utf8String key(info[0]);
|
|
23
33
|
Nan::Utf8String salt(info[1]);
|
|
24
34
|
|
|
25
|
-
|
|
35
|
+
const char* result = sha256_crypt(*key, *salt);
|
|
36
|
+
if (!result) {
|
|
37
|
+
Nan::ThrowError("sha256_crypt failed");
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
info.GetReturnValue().Set(Nan::New(result).ToLocalChecked());
|
|
26
42
|
}
|
|
27
43
|
|
|
28
44
|
NAN_METHOD(sha512crypt) {
|
|
45
|
+
if (info.Length() < 2) {
|
|
46
|
+
Nan::ThrowError("Wrong number of arguments");
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!info[0]->IsString() || !info[1]->IsString()) {
|
|
51
|
+
Nan::ThrowTypeError("Arguments must be strings");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
29
55
|
Nan::Utf8String key(info[0]);
|
|
30
56
|
Nan::Utf8String salt(info[1]);
|
|
31
57
|
|
|
32
|
-
|
|
58
|
+
const char* result = sha512_crypt(*key, *salt);
|
|
59
|
+
if (!result) {
|
|
60
|
+
Nan::ThrowError("sha512_crypt failed");
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
info.GetReturnValue().Set(Nan::New(result).ToLocalChecked());
|
|
33
65
|
}
|
|
34
66
|
|
|
35
67
|
|
|
@@ -40,7 +72,12 @@ class SHA256CryptWorker : public AsyncWorker {
|
|
|
40
72
|
}
|
|
41
73
|
|
|
42
74
|
void Execute() {
|
|
43
|
-
|
|
75
|
+
const char* crypt_result = sha256_crypt(key.c_str(), salt.c_str());
|
|
76
|
+
if (!crypt_result) {
|
|
77
|
+
SetErrorMessage("sha256_crypt failed");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
result = crypt_result;
|
|
44
81
|
}
|
|
45
82
|
|
|
46
83
|
void HandleOKCallback() {
|
|
@@ -61,7 +98,12 @@ class SHA512CryptWorker : public AsyncWorker {
|
|
|
61
98
|
}
|
|
62
99
|
|
|
63
100
|
void Execute() {
|
|
64
|
-
|
|
101
|
+
const char* crypt_result = sha512_crypt(key.c_str(), salt.c_str());
|
|
102
|
+
if (!crypt_result) {
|
|
103
|
+
SetErrorMessage("sha512_crypt failed");
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
result = crypt_result;
|
|
65
107
|
}
|
|
66
108
|
|
|
67
109
|
void HandleOKCallback() {
|
|
@@ -76,6 +118,21 @@ class SHA512CryptWorker : public AsyncWorker {
|
|
|
76
118
|
};
|
|
77
119
|
|
|
78
120
|
NAN_METHOD(sha256cryptasync) {
|
|
121
|
+
if (info.Length() < 3) {
|
|
122
|
+
Nan::ThrowError("Wrong number of arguments");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!info[0]->IsString() || !info[1]->IsString()) {
|
|
127
|
+
Nan::ThrowTypeError("First two arguments must be strings");
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (!info[2]->IsFunction()) {
|
|
132
|
+
Nan::ThrowTypeError("Third argument must be a function");
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
79
136
|
Nan::Utf8String key(info[0]);
|
|
80
137
|
Nan::Utf8String salt(info[1]);
|
|
81
138
|
|
|
@@ -84,6 +141,21 @@ NAN_METHOD(sha256cryptasync) {
|
|
|
84
141
|
}
|
|
85
142
|
|
|
86
143
|
NAN_METHOD(sha512cryptasync) {
|
|
144
|
+
if (info.Length() < 3) {
|
|
145
|
+
Nan::ThrowError("Wrong number of arguments");
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!info[0]->IsString() || !info[1]->IsString()) {
|
|
150
|
+
Nan::ThrowTypeError("First two arguments must be strings");
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!info[2]->IsFunction()) {
|
|
155
|
+
Nan::ThrowTypeError("Third argument must be a function");
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
87
159
|
Nan::Utf8String key(info[0]);
|
|
88
160
|
Nan::Utf8String salt(info[1]);
|
|
89
161
|
|
|
@@ -104,5 +176,6 @@ NAN_MODULE_INIT(init) {
|
|
|
104
176
|
Nan::GetFunction(Nan::New<FunctionTemplate>(sha512cryptasync)).ToLocalChecked());
|
|
105
177
|
}
|
|
106
178
|
|
|
179
|
+
|
|
107
180
|
NODE_MODULE(shacrypt, init);
|
|
108
181
|
|
package/test/tests.js
CHANGED
|
@@ -97,3 +97,52 @@ tests512.forEach(function(test) {
|
|
|
97
97
|
});
|
|
98
98
|
});
|
|
99
99
|
});
|
|
100
|
+
|
|
101
|
+
describe('Input validation', function() {
|
|
102
|
+
it('should throw error for non-string password in sha256crypt', function() {
|
|
103
|
+
expect(function() {
|
|
104
|
+
shacrypt.sha256crypt(123, 'salt');
|
|
105
|
+
}).to.throw('password must be a String');
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should throw error for non-string password in sha512crypt', function() {
|
|
109
|
+
expect(function() {
|
|
110
|
+
shacrypt.sha512crypt(null, 'salt');
|
|
111
|
+
}).to.throw('password must be a String');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should handle missing password gracefully', function() {
|
|
115
|
+
expect(function() {
|
|
116
|
+
shacrypt.sha256crypt();
|
|
117
|
+
}).to.throw('password must be a String');
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('should generate salt when not provided', function() {
|
|
121
|
+
var result = shacrypt.sha256crypt('password');
|
|
122
|
+
result.should.match(/^\$5\$/);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should handle async callback errors for invalid password', function(done) {
|
|
126
|
+
try {
|
|
127
|
+
shacrypt.sha256crypt(123, 'salt', function(err, result) {
|
|
128
|
+
// This callback should not be called
|
|
129
|
+
done(new Error('Expected synchronous error'));
|
|
130
|
+
});
|
|
131
|
+
} catch (e) {
|
|
132
|
+
e.message.should.equal('password must be a String');
|
|
133
|
+
done();
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should handle async callback errors for invalid password in sha512', function(done) {
|
|
138
|
+
try {
|
|
139
|
+
shacrypt.sha512crypt(undefined, 'salt', function(err, result) {
|
|
140
|
+
// This callback should not be called
|
|
141
|
+
done(new Error('Expected synchronous error'));
|
|
142
|
+
});
|
|
143
|
+
} catch (e) {
|
|
144
|
+
e.message.should.equal('password must be a String');
|
|
145
|
+
done();
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|