yescrypt 0.1.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.
- checksums.yaml +7 -0
- data/LICENSE +42 -0
- data/README.md +325 -0
- data/ext/yescrypt/extconf.rb +7 -0
- data/ext/yescrypt/insecure_memzero.h +34 -0
- data/ext/yescrypt/sha256.c +256 -0
- data/ext/yescrypt/sha256.h +43 -0
- data/ext/yescrypt/yescrypt-common.c +381 -0
- data/ext/yescrypt/yescrypt-opt.c +358 -0
- data/ext/yescrypt/yescrypt.h +124 -0
- data/ext/yescrypt/yescrypt_ext.c +407 -0
- data/lib/yescrypt/version.rb +5 -0
- data/lib/yescrypt.rb +79 -0
- metadata +118 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Ruby C extension wrapper for yescrypt.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
#include <ruby.h>
|
|
6
|
+
#include <ruby/thread.h>
|
|
7
|
+
#include <ruby/encoding.h>
|
|
8
|
+
#include <string.h>
|
|
9
|
+
#include <stdlib.h>
|
|
10
|
+
|
|
11
|
+
#include "yescrypt.h"
|
|
12
|
+
#include "insecure_memzero.h"
|
|
13
|
+
|
|
14
|
+
static VALUE rb_mYescrypt;
|
|
15
|
+
static VALUE rb_eYescryptError;
|
|
16
|
+
|
|
17
|
+
/* Cached symbol IDs */
|
|
18
|
+
static ID id_n, id_r, id_p, id_t_param, id_flags, id_outlen;
|
|
19
|
+
static ID id_n_log2, id_salt;
|
|
20
|
+
|
|
21
|
+
#define MAX_KDF_OUTLEN 1024
|
|
22
|
+
#define MAX_N_LOG2 24
|
|
23
|
+
#define MAX_R 256
|
|
24
|
+
#define MAX_P 256
|
|
25
|
+
#define MAX_T 100
|
|
26
|
+
|
|
27
|
+
/* Default parameter values (single source of truth for C side) */
|
|
28
|
+
#define DEFAULT_N_LOG2_VAL 12
|
|
29
|
+
#define DEFAULT_R_VAL 32
|
|
30
|
+
#define DEFAULT_P_VAL 1
|
|
31
|
+
#define DEFAULT_T_VAL 0
|
|
32
|
+
#define DEFAULT_FLAGS_VAL YESCRYPT_DEFAULTS
|
|
33
|
+
|
|
34
|
+
static void validate_flags(int flags)
|
|
35
|
+
{
|
|
36
|
+
int base = flags & 0x3; /* lowest 2 bits: WORM/RW/DEFAULTS */
|
|
37
|
+
if (base > YESCRYPT_DEFAULTS)
|
|
38
|
+
rb_raise(rb_eArgError, "invalid base flag value: %d", base);
|
|
39
|
+
|
|
40
|
+
int extra = flags & ~0x3;
|
|
41
|
+
int valid_extra = YESCRYPT_ROUNDS_6 | YESCRYPT_GATHER_8 | YESCRYPT_SIMPLE_8;
|
|
42
|
+
if (extra & ~valid_extra)
|
|
43
|
+
rb_raise(rb_eArgError, "invalid flags: 0x%x", flags);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/* --- GVL-free structs for kdf and hash_password --- */
|
|
47
|
+
|
|
48
|
+
struct kdf_nogvl_args {
|
|
49
|
+
const yescrypt_shared_t *shared;
|
|
50
|
+
yescrypt_local_t *local;
|
|
51
|
+
const uint8_t *password;
|
|
52
|
+
size_t password_len;
|
|
53
|
+
const uint8_t *salt;
|
|
54
|
+
size_t salt_len;
|
|
55
|
+
const yescrypt_params_t *params;
|
|
56
|
+
uint8_t *buf;
|
|
57
|
+
size_t buflen;
|
|
58
|
+
int ret;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
static void *kdf_nogvl(void *data)
|
|
62
|
+
{
|
|
63
|
+
struct kdf_nogvl_args *args = data;
|
|
64
|
+
args->ret = yescrypt_kdf(
|
|
65
|
+
args->shared, args->local,
|
|
66
|
+
args->password, args->password_len,
|
|
67
|
+
args->salt, args->salt_len,
|
|
68
|
+
args->params, args->buf, args->buflen);
|
|
69
|
+
return NULL;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
struct hash_nogvl_args {
|
|
73
|
+
const yescrypt_shared_t *shared;
|
|
74
|
+
yescrypt_local_t *local;
|
|
75
|
+
const uint8_t *password;
|
|
76
|
+
size_t password_len;
|
|
77
|
+
const uint8_t *setting;
|
|
78
|
+
uint8_t *buf;
|
|
79
|
+
size_t buflen;
|
|
80
|
+
uint8_t *result;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
static void *hash_nogvl(void *data)
|
|
84
|
+
{
|
|
85
|
+
struct hash_nogvl_args *args = data;
|
|
86
|
+
args->result = yescrypt_r(
|
|
87
|
+
args->shared, args->local,
|
|
88
|
+
args->password, args->password_len,
|
|
89
|
+
args->setting,
|
|
90
|
+
args->buf, args->buflen);
|
|
91
|
+
return NULL;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* --- Cleanup helpers for rb_ensure --- */
|
|
95
|
+
|
|
96
|
+
struct kdf_cleanup_ctx {
|
|
97
|
+
yescrypt_shared_t shared;
|
|
98
|
+
yescrypt_local_t local;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
static VALUE kdf_cleanup(VALUE arg)
|
|
102
|
+
{
|
|
103
|
+
struct kdf_cleanup_ctx *ctx = (struct kdf_cleanup_ctx *)arg;
|
|
104
|
+
yescrypt_free_local(&ctx->local);
|
|
105
|
+
yescrypt_free_shared(&ctx->shared);
|
|
106
|
+
return Qnil;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/*
|
|
110
|
+
* Yescrypt.kdf(password, salt, n:, r:, p:, flags:, t:, outlen:)
|
|
111
|
+
*
|
|
112
|
+
* Low-level KDF function. n, r, p, and flags are required.
|
|
113
|
+
* Returns raw hash bytes.
|
|
114
|
+
*/
|
|
115
|
+
static VALUE kdf_body(VALUE arg);
|
|
116
|
+
|
|
117
|
+
struct kdf_call_ctx {
|
|
118
|
+
VALUE password;
|
|
119
|
+
VALUE salt;
|
|
120
|
+
VALUE opts;
|
|
121
|
+
struct kdf_cleanup_ctx *cleanup;
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
static VALUE rb_yescrypt_kdf(int argc, VALUE *argv, VALUE self)
|
|
125
|
+
{
|
|
126
|
+
(void)self;
|
|
127
|
+
VALUE password, salt, opts;
|
|
128
|
+
rb_scan_args(argc, argv, "2:", &password, &salt, &opts);
|
|
129
|
+
|
|
130
|
+
Check_Type(password, T_STRING);
|
|
131
|
+
Check_Type(salt, T_STRING);
|
|
132
|
+
|
|
133
|
+
if (NIL_P(opts))
|
|
134
|
+
opts = rb_hash_new();
|
|
135
|
+
|
|
136
|
+
struct kdf_cleanup_ctx cleanup;
|
|
137
|
+
yescrypt_init_shared(&cleanup.shared);
|
|
138
|
+
yescrypt_init_local(&cleanup.local);
|
|
139
|
+
|
|
140
|
+
struct kdf_call_ctx ctx = { password, salt, opts, &cleanup };
|
|
141
|
+
return rb_ensure(kdf_body, (VALUE)&ctx, kdf_cleanup, (VALUE)&cleanup);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
static VALUE kdf_body(VALUE arg)
|
|
145
|
+
{
|
|
146
|
+
struct kdf_call_ctx *ctx = (struct kdf_call_ctx *)arg;
|
|
147
|
+
|
|
148
|
+
VALUE v_N = rb_hash_aref(ctx->opts, ID2SYM(id_n));
|
|
149
|
+
VALUE v_r = rb_hash_aref(ctx->opts, ID2SYM(id_r));
|
|
150
|
+
VALUE v_p = rb_hash_aref(ctx->opts, ID2SYM(id_p));
|
|
151
|
+
VALUE v_t = rb_hash_aref(ctx->opts, ID2SYM(id_t_param));
|
|
152
|
+
VALUE v_flags = rb_hash_aref(ctx->opts, ID2SYM(id_flags));
|
|
153
|
+
VALUE v_outlen = rb_hash_aref(ctx->opts, ID2SYM(id_outlen));
|
|
154
|
+
|
|
155
|
+
if (NIL_P(v_N) || NIL_P(v_r) || NIL_P(v_p) || NIL_P(v_flags))
|
|
156
|
+
rb_raise(rb_eArgError, "n, r, p, and flags are required for kdf");
|
|
157
|
+
|
|
158
|
+
uint64_t N = NUM2ULL(v_N);
|
|
159
|
+
uint32_t r = NUM2UINT(v_r);
|
|
160
|
+
uint32_t p = NUM2UINT(v_p);
|
|
161
|
+
uint32_t t = NIL_P(v_t) ? 0 : NUM2UINT(v_t);
|
|
162
|
+
int flags = NUM2INT(v_flags);
|
|
163
|
+
size_t outlen = NIL_P(v_outlen) ? 32 : NUM2SIZET(v_outlen);
|
|
164
|
+
|
|
165
|
+
/* Input validation */
|
|
166
|
+
if (N == 0 || (N & (N - 1)) != 0)
|
|
167
|
+
rb_raise(rb_eArgError, "n must be a power of 2");
|
|
168
|
+
if (N > ((uint64_t)1 << MAX_N_LOG2))
|
|
169
|
+
rb_raise(rb_eArgError, "n must not exceed 2^%d", MAX_N_LOG2);
|
|
170
|
+
if (r == 0 || r > MAX_R)
|
|
171
|
+
rb_raise(rb_eArgError, "r must be between 1 and %d", MAX_R);
|
|
172
|
+
if (p == 0 || p > MAX_P)
|
|
173
|
+
rb_raise(rb_eArgError, "p must be between 1 and %d", MAX_P);
|
|
174
|
+
if (t > MAX_T)
|
|
175
|
+
rb_raise(rb_eArgError, "t must be between 0 and %d", MAX_T);
|
|
176
|
+
if (outlen == 0 || outlen > MAX_KDF_OUTLEN)
|
|
177
|
+
rb_raise(rb_eArgError, "outlen must be between 1 and %d", MAX_KDF_OUTLEN);
|
|
178
|
+
validate_flags(flags);
|
|
179
|
+
|
|
180
|
+
yescrypt_params_t params;
|
|
181
|
+
params.flags = (yescrypt_flags_t)flags;
|
|
182
|
+
params.N = N;
|
|
183
|
+
params.r = r;
|
|
184
|
+
params.p = p;
|
|
185
|
+
params.t = t;
|
|
186
|
+
params.g = 0;
|
|
187
|
+
|
|
188
|
+
uint8_t buf[MAX_KDF_OUTLEN];
|
|
189
|
+
|
|
190
|
+
/* Pin strings so GC compaction won't move them while GVL is released */
|
|
191
|
+
VALUE pw_pinned = rb_str_new_frozen(ctx->password);
|
|
192
|
+
VALUE salt_pinned = rb_str_new_frozen(ctx->salt);
|
|
193
|
+
|
|
194
|
+
struct kdf_nogvl_args args;
|
|
195
|
+
args.shared = &ctx->cleanup->shared;
|
|
196
|
+
args.local = &ctx->cleanup->local;
|
|
197
|
+
args.password = (const uint8_t *)RSTRING_PTR(pw_pinned);
|
|
198
|
+
args.password_len = RSTRING_LEN(pw_pinned);
|
|
199
|
+
args.salt = (const uint8_t *)RSTRING_PTR(salt_pinned);
|
|
200
|
+
args.salt_len = RSTRING_LEN(salt_pinned);
|
|
201
|
+
args.params = ¶ms;
|
|
202
|
+
args.buf = buf;
|
|
203
|
+
args.buflen = outlen;
|
|
204
|
+
|
|
205
|
+
rb_thread_call_without_gvl(kdf_nogvl, &args, RUBY_UBF_IO, NULL);
|
|
206
|
+
|
|
207
|
+
if (args.ret != 0) {
|
|
208
|
+
insecure_memzero(buf, sizeof(buf));
|
|
209
|
+
rb_raise(rb_eYescryptError, "yescrypt_kdf failed");
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
VALUE result = rb_str_new((const char *)buf, outlen);
|
|
213
|
+
insecure_memzero(buf, sizeof(buf));
|
|
214
|
+
RB_GC_GUARD(pw_pinned);
|
|
215
|
+
RB_GC_GUARD(salt_pinned);
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/*
|
|
220
|
+
* Yescrypt.gensalt(n_log2:, r:, p:, t:, flags:, salt:)
|
|
221
|
+
*
|
|
222
|
+
* Generate a yescrypt setting/salt string.
|
|
223
|
+
* The salt: parameter is required.
|
|
224
|
+
*/
|
|
225
|
+
static VALUE rb_yescrypt_gensalt(int argc, VALUE *argv, VALUE self)
|
|
226
|
+
{
|
|
227
|
+
(void)self;
|
|
228
|
+
VALUE opts;
|
|
229
|
+
rb_scan_args(argc, argv, "0:", &opts);
|
|
230
|
+
|
|
231
|
+
if (NIL_P(opts))
|
|
232
|
+
opts = rb_hash_new();
|
|
233
|
+
|
|
234
|
+
VALUE v_N = rb_hash_aref(opts, ID2SYM(id_n_log2));
|
|
235
|
+
VALUE v_r = rb_hash_aref(opts, ID2SYM(id_r));
|
|
236
|
+
VALUE v_p = rb_hash_aref(opts, ID2SYM(id_p));
|
|
237
|
+
VALUE v_t = rb_hash_aref(opts, ID2SYM(id_t_param));
|
|
238
|
+
VALUE v_flags = rb_hash_aref(opts, ID2SYM(id_flags));
|
|
239
|
+
VALUE v_salt = rb_hash_aref(opts, ID2SYM(id_salt));
|
|
240
|
+
|
|
241
|
+
if (NIL_P(v_salt))
|
|
242
|
+
rb_raise(rb_eArgError, "salt is required");
|
|
243
|
+
|
|
244
|
+
Check_Type(v_salt, T_STRING);
|
|
245
|
+
|
|
246
|
+
uint32_t N_log2 = NIL_P(v_N) ? DEFAULT_N_LOG2_VAL : NUM2UINT(v_N);
|
|
247
|
+
uint32_t r = NIL_P(v_r) ? DEFAULT_R_VAL : NUM2UINT(v_r);
|
|
248
|
+
uint32_t p = NIL_P(v_p) ? DEFAULT_P_VAL : NUM2UINT(v_p);
|
|
249
|
+
uint32_t t = NIL_P(v_t) ? DEFAULT_T_VAL : NUM2UINT(v_t);
|
|
250
|
+
int flags = NIL_P(v_flags) ? DEFAULT_FLAGS_VAL : NUM2INT(v_flags);
|
|
251
|
+
|
|
252
|
+
/* Input validation */
|
|
253
|
+
if (N_log2 == 0 || N_log2 > MAX_N_LOG2)
|
|
254
|
+
rb_raise(rb_eArgError, "n_log2 must be between 1 and %d", MAX_N_LOG2);
|
|
255
|
+
if (r == 0 || r > MAX_R)
|
|
256
|
+
rb_raise(rb_eArgError, "r must be between 1 and %d", MAX_R);
|
|
257
|
+
if (p == 0 || p > MAX_P)
|
|
258
|
+
rb_raise(rb_eArgError, "p must be between 1 and %d", MAX_P);
|
|
259
|
+
if (t > MAX_T)
|
|
260
|
+
rb_raise(rb_eArgError, "t must be between 0 and %d", MAX_T);
|
|
261
|
+
validate_flags(flags);
|
|
262
|
+
|
|
263
|
+
const uint8_t *salt_ptr = (const uint8_t *)RSTRING_PTR(v_salt);
|
|
264
|
+
size_t salt_len = RSTRING_LEN(v_salt);
|
|
265
|
+
|
|
266
|
+
uint8_t buf[YESCRYPT_MAX_ENCODED];
|
|
267
|
+
uint8_t *result = yescrypt_gensalt(N_log2, r, p, t,
|
|
268
|
+
(yescrypt_flags_t)flags, salt_ptr, salt_len,
|
|
269
|
+
buf, sizeof(buf));
|
|
270
|
+
|
|
271
|
+
if (!result)
|
|
272
|
+
rb_raise(rb_eYescryptError, "failed to generate salt");
|
|
273
|
+
|
|
274
|
+
VALUE str = rb_str_new_cstr((const char *)result);
|
|
275
|
+
rb_enc_associate(str, rb_usascii_encoding());
|
|
276
|
+
OBJ_FREEZE(str);
|
|
277
|
+
return str;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/*
|
|
281
|
+
* Yescrypt._hash_password(password, setting)
|
|
282
|
+
*
|
|
283
|
+
* Internal: Hash a password using a setting string (from gensalt).
|
|
284
|
+
* Returns the full encoded hash string (frozen).
|
|
285
|
+
*/
|
|
286
|
+
static VALUE hash_body(VALUE arg);
|
|
287
|
+
|
|
288
|
+
struct hash_call_ctx {
|
|
289
|
+
VALUE password;
|
|
290
|
+
VALUE setting;
|
|
291
|
+
struct kdf_cleanup_ctx *cleanup;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
static VALUE rb_yescrypt_hash(VALUE self, VALUE password, VALUE setting)
|
|
295
|
+
{
|
|
296
|
+
(void)self;
|
|
297
|
+
Check_Type(password, T_STRING);
|
|
298
|
+
Check_Type(setting, T_STRING);
|
|
299
|
+
|
|
300
|
+
struct kdf_cleanup_ctx cleanup;
|
|
301
|
+
yescrypt_init_shared(&cleanup.shared);
|
|
302
|
+
yescrypt_init_local(&cleanup.local);
|
|
303
|
+
|
|
304
|
+
struct hash_call_ctx ctx = { password, setting, &cleanup };
|
|
305
|
+
return rb_ensure(hash_body, (VALUE)&ctx, kdf_cleanup, (VALUE)&cleanup);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
static VALUE hash_body(VALUE arg)
|
|
309
|
+
{
|
|
310
|
+
struct hash_call_ctx *ctx = (struct hash_call_ctx *)arg;
|
|
311
|
+
|
|
312
|
+
uint8_t buf[YESCRYPT_MAX_ENCODED];
|
|
313
|
+
|
|
314
|
+
/* Pin strings so GC compaction won't move them while GVL is released */
|
|
315
|
+
VALUE pw_pinned = rb_str_new_frozen(ctx->password);
|
|
316
|
+
VALUE setting_pinned = rb_str_new_frozen(ctx->setting);
|
|
317
|
+
|
|
318
|
+
struct hash_nogvl_args args;
|
|
319
|
+
args.shared = &ctx->cleanup->shared;
|
|
320
|
+
args.local = &ctx->cleanup->local;
|
|
321
|
+
args.password = (const uint8_t *)RSTRING_PTR(pw_pinned);
|
|
322
|
+
args.password_len = RSTRING_LEN(pw_pinned);
|
|
323
|
+
args.setting = (const uint8_t *)RSTRING_PTR(setting_pinned);
|
|
324
|
+
args.buf = buf;
|
|
325
|
+
args.buflen = sizeof(buf);
|
|
326
|
+
|
|
327
|
+
rb_thread_call_without_gvl(hash_nogvl, &args, RUBY_UBF_IO, NULL);
|
|
328
|
+
|
|
329
|
+
RB_GC_GUARD(pw_pinned);
|
|
330
|
+
RB_GC_GUARD(setting_pinned);
|
|
331
|
+
|
|
332
|
+
if (!args.result) {
|
|
333
|
+
insecure_memzero(buf, sizeof(buf));
|
|
334
|
+
rb_raise(rb_eYescryptError, "yescrypt hash failed");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
VALUE str = rb_str_new_cstr((const char *)args.result);
|
|
338
|
+
insecure_memzero(buf, sizeof(buf));
|
|
339
|
+
rb_enc_associate(str, rb_usascii_encoding());
|
|
340
|
+
OBJ_FREEZE(str);
|
|
341
|
+
return str;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/*
|
|
345
|
+
* Yescrypt.decode_params(hash_string)
|
|
346
|
+
*
|
|
347
|
+
* Parse the parameter segment from an encoded yescrypt string.
|
|
348
|
+
* Returns a frozen Hash with :n_log2, :r, :p, :t, :flags or nil if invalid.
|
|
349
|
+
*/
|
|
350
|
+
static VALUE rb_yescrypt_decode_params(VALUE self, VALUE str)
|
|
351
|
+
{
|
|
352
|
+
(void)self;
|
|
353
|
+
if (NIL_P(str) || TYPE(str) != T_STRING)
|
|
354
|
+
return Qnil;
|
|
355
|
+
|
|
356
|
+
const uint8_t *setting = (const uint8_t *)RSTRING_PTR(str);
|
|
357
|
+
uint32_t N_log2, r, p, t;
|
|
358
|
+
yescrypt_flags_t flags;
|
|
359
|
+
|
|
360
|
+
const uint8_t *result = yescrypt_decode_params_ext(
|
|
361
|
+
setting, &N_log2, &r, &p, &t, &flags);
|
|
362
|
+
|
|
363
|
+
if (!result)
|
|
364
|
+
return Qnil;
|
|
365
|
+
|
|
366
|
+
VALUE hash = rb_hash_new();
|
|
367
|
+
rb_hash_aset(hash, ID2SYM(id_n_log2), UINT2NUM(N_log2));
|
|
368
|
+
rb_hash_aset(hash, ID2SYM(id_r), UINT2NUM(r));
|
|
369
|
+
rb_hash_aset(hash, ID2SYM(id_p), UINT2NUM(p));
|
|
370
|
+
rb_hash_aset(hash, ID2SYM(id_t_param), UINT2NUM(t));
|
|
371
|
+
rb_hash_aset(hash, ID2SYM(id_flags), INT2NUM((int)flags));
|
|
372
|
+
OBJ_FREEZE(hash);
|
|
373
|
+
return hash;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
void Init_yescrypt_ext(void)
|
|
377
|
+
{
|
|
378
|
+
/* Cache intern IDs */
|
|
379
|
+
id_n = rb_intern("n");
|
|
380
|
+
id_r = rb_intern("r");
|
|
381
|
+
id_p = rb_intern("p");
|
|
382
|
+
id_t_param = rb_intern("t");
|
|
383
|
+
id_flags = rb_intern("flags");
|
|
384
|
+
id_outlen = rb_intern("outlen");
|
|
385
|
+
id_n_log2 = rb_intern("n_log2");
|
|
386
|
+
id_salt = rb_intern("salt");
|
|
387
|
+
|
|
388
|
+
rb_mYescrypt = rb_define_module("Yescrypt");
|
|
389
|
+
rb_eYescryptError = rb_define_class_under(rb_mYescrypt, "Error", rb_eStandardError);
|
|
390
|
+
|
|
391
|
+
rb_define_module_function(rb_mYescrypt, "kdf", rb_yescrypt_kdf, -1);
|
|
392
|
+
rb_define_module_function(rb_mYescrypt, "gensalt", rb_yescrypt_gensalt, -1);
|
|
393
|
+
rb_define_module_function(rb_mYescrypt, "decode_params", rb_yescrypt_decode_params, 1);
|
|
394
|
+
rb_define_private_method(rb_singleton_class(rb_mYescrypt), "_hash_password", rb_yescrypt_hash, 2);
|
|
395
|
+
|
|
396
|
+
/* Flavor flag constants */
|
|
397
|
+
rb_define_const(rb_mYescrypt, "WORM", INT2FIX(YESCRYPT_WORM));
|
|
398
|
+
rb_define_const(rb_mYescrypt, "RW", INT2FIX(YESCRYPT_RW));
|
|
399
|
+
rb_define_const(rb_mYescrypt, "DEFAULTS", INT2FIX(YESCRYPT_DEFAULTS));
|
|
400
|
+
|
|
401
|
+
/* Default parameter constants (single source of truth) */
|
|
402
|
+
rb_define_const(rb_mYescrypt, "DEFAULT_N_LOG2", INT2FIX(DEFAULT_N_LOG2_VAL));
|
|
403
|
+
rb_define_const(rb_mYescrypt, "DEFAULT_R", INT2FIX(DEFAULT_R_VAL));
|
|
404
|
+
rb_define_const(rb_mYescrypt, "DEFAULT_P", INT2FIX(DEFAULT_P_VAL));
|
|
405
|
+
rb_define_const(rb_mYescrypt, "DEFAULT_T", INT2FIX(DEFAULT_T_VAL));
|
|
406
|
+
rb_define_const(rb_mYescrypt, "DEFAULT_FLAGS", INT2FIX(DEFAULT_FLAGS_VAL));
|
|
407
|
+
}
|
data/lib/yescrypt.rb
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "yescrypt/version"
|
|
4
|
+
require "yescrypt/yescrypt_ext"
|
|
5
|
+
require "securerandom"
|
|
6
|
+
require "openssl"
|
|
7
|
+
|
|
8
|
+
module Yescrypt
|
|
9
|
+
class << self
|
|
10
|
+
# High-level: hash a password, returning an encoded string.
|
|
11
|
+
#
|
|
12
|
+
# @param password [String] the plaintext password
|
|
13
|
+
# @param options [Hash] override defaults (n_log2:, r:, p:, t:, flags:)
|
|
14
|
+
# @return [String] encoded yescrypt hash (starts with "$y$")
|
|
15
|
+
def create(password, **options)
|
|
16
|
+
raise TypeError, "no implicit conversion of #{password.class} into String" unless password.is_a?(String)
|
|
17
|
+
opts = default_params.merge(options)
|
|
18
|
+
salt = SecureRandom.random_bytes(16)
|
|
19
|
+
setting = gensalt(
|
|
20
|
+
n_log2: opts[:n_log2],
|
|
21
|
+
r: opts[:r],
|
|
22
|
+
p: opts[:p],
|
|
23
|
+
t: opts[:t],
|
|
24
|
+
flags: opts[:flags],
|
|
25
|
+
salt: salt
|
|
26
|
+
)
|
|
27
|
+
_hash_password(password, setting)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# High-level: verify a password against an encoded hash.
|
|
31
|
+
#
|
|
32
|
+
# @param password [String] the plaintext password
|
|
33
|
+
# @param hash [String] the encoded hash from .create
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def verify(password, hash)
|
|
36
|
+
return false unless password.is_a?(String) && hash.is_a?(String)
|
|
37
|
+
return false unless hash.start_with?("$y$")
|
|
38
|
+
|
|
39
|
+
rehash = _hash_password(password, hash)
|
|
40
|
+
begin
|
|
41
|
+
OpenSSL.fixed_length_secure_compare(rehash, hash)
|
|
42
|
+
rescue ArgumentError
|
|
43
|
+
false
|
|
44
|
+
end
|
|
45
|
+
rescue Error
|
|
46
|
+
false
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Check if hash parameters match the given configuration.
|
|
50
|
+
#
|
|
51
|
+
# @param hash [String] encoded hash string
|
|
52
|
+
# @param options [Hash] expected parameters (n_log2:, r:, p:, t:, flags:)
|
|
53
|
+
# @return [Boolean]
|
|
54
|
+
def cost_matches?(hash, **options)
|
|
55
|
+
params = decode_params(hash)
|
|
56
|
+
return false unless params
|
|
57
|
+
|
|
58
|
+
opts = default_params.merge(options)
|
|
59
|
+
params[:n_log2] == opts[:n_log2] &&
|
|
60
|
+
params[:r] == opts[:r] &&
|
|
61
|
+
params[:p] == opts[:p] &&
|
|
62
|
+
params[:t] == opts[:t] &&
|
|
63
|
+
params[:flags] == opts[:flags]
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns the default parameter hash, derived from constants defined in C.
|
|
67
|
+
def default_params
|
|
68
|
+
DEFAULT_PARAMS
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
DEFAULT_PARAMS = {
|
|
73
|
+
n_log2: DEFAULT_N_LOG2,
|
|
74
|
+
r: DEFAULT_R,
|
|
75
|
+
p: DEFAULT_P,
|
|
76
|
+
t: DEFAULT_T,
|
|
77
|
+
flags: DEFAULT_FLAGS
|
|
78
|
+
}.freeze
|
|
79
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: yescrypt
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Suleyman Musayev
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-03-07 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '2.0'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '2.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: minitest
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '5.0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '5.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: rake
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '13.0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '13.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake-compiler
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '1.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '1.0'
|
|
69
|
+
description: Ruby C extension wrapping the yescrypt password hashing algorithm. yescrypt
|
|
70
|
+
is the default password hash in modern Linux distributions (glibc 2.36+). It extends
|
|
71
|
+
scrypt with pwxform for stronger time-memory tradeoff resistance. Supports YESCRYPT_WORM
|
|
72
|
+
(scrypt-compatible), YESCRYPT_RW, and YESCRYPT_DEFAULTS flavors.
|
|
73
|
+
email:
|
|
74
|
+
- slmusayev@gmail.com
|
|
75
|
+
executables: []
|
|
76
|
+
extensions:
|
|
77
|
+
- ext/yescrypt/extconf.rb
|
|
78
|
+
extra_rdoc_files: []
|
|
79
|
+
files:
|
|
80
|
+
- LICENSE
|
|
81
|
+
- README.md
|
|
82
|
+
- ext/yescrypt/extconf.rb
|
|
83
|
+
- ext/yescrypt/insecure_memzero.h
|
|
84
|
+
- ext/yescrypt/sha256.c
|
|
85
|
+
- ext/yescrypt/sha256.h
|
|
86
|
+
- ext/yescrypt/yescrypt-common.c
|
|
87
|
+
- ext/yescrypt/yescrypt-opt.c
|
|
88
|
+
- ext/yescrypt/yescrypt.h
|
|
89
|
+
- ext/yescrypt/yescrypt_ext.c
|
|
90
|
+
- lib/yescrypt.rb
|
|
91
|
+
- lib/yescrypt/version.rb
|
|
92
|
+
homepage: https://github.com/msuliq/yescrypt
|
|
93
|
+
licenses:
|
|
94
|
+
- MIT
|
|
95
|
+
metadata:
|
|
96
|
+
rubygems_mfa_required: 'true'
|
|
97
|
+
homepage_uri: https://github.com/msuliq/yescrypt
|
|
98
|
+
source_code_uri: https://github.com/msuliq/yescrypt
|
|
99
|
+
post_install_message:
|
|
100
|
+
rdoc_options: []
|
|
101
|
+
require_paths:
|
|
102
|
+
- lib
|
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: 2.7.2
|
|
108
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
|
+
requirements:
|
|
110
|
+
- - ">="
|
|
111
|
+
- !ruby/object:Gem::Version
|
|
112
|
+
version: '0'
|
|
113
|
+
requirements: []
|
|
114
|
+
rubygems_version: 3.4.19
|
|
115
|
+
signing_key:
|
|
116
|
+
specification_version: 4
|
|
117
|
+
summary: 'yescrypt: a memory-hard password hashing function used in modern Linux.'
|
|
118
|
+
test_files: []
|