xcrypt 0.1.2 → 0.2.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.
@@ -0,0 +1,99 @@
1
+ # lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*-
2
+ #
3
+ # Copyright (C) 2004-2005, 2007, 2009, 2011-2019, 2021-2024 Free
4
+ # Software Foundation, Inc.
5
+ # Written by Scott James Remnant, 2004.
6
+ #
7
+ # This file is free software; the Free Software Foundation gives
8
+ # unlimited permission to copy and/or distribute it, with or without
9
+ # modifications, as long as this notice is preserved.
10
+
11
+ # serial 5 lt~obsolete.m4
12
+
13
+ # These exist entirely to fool aclocal when bootstrapping libtool.
14
+ #
15
+ # In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN),
16
+ # which have later been changed to m4_define as they aren't part of the
17
+ # exported API, or moved to Autoconf or Automake where they belong.
18
+ #
19
+ # The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN
20
+ # in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us
21
+ # using a macro with the same name in our local m4/libtool.m4 it'll
22
+ # pull the old libtool.m4 in (it doesn't see our shiny new m4_define
23
+ # and doesn't know about Autoconf macros at all.)
24
+ #
25
+ # So we provide this file, which has a silly filename so it's always
26
+ # included after everything else. This provides aclocal with the
27
+ # AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything
28
+ # because those macros already exist, or will be overwritten later.
29
+ # We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6.
30
+ #
31
+ # Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here.
32
+ # Yes, that means every name once taken will need to remain here until
33
+ # we give up compatibility with versions before 1.7, at which point
34
+ # we need to keep only those names which we still refer to.
35
+
36
+ # This is to help aclocal find these macros, as it can't see m4_define.
37
+ AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])])
38
+
39
+ m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])])
40
+ m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])])
41
+ m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])])
42
+ m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])])
43
+ m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])])
44
+ m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])])
45
+ m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])])
46
+ m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])])
47
+ m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])])
48
+ m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])])
49
+ m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])])
50
+ m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])])
51
+ m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])])
52
+ m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])])
53
+ m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])])
54
+ m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])])
55
+ m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])])
56
+ m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])])
57
+ m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])])
58
+ m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])])
59
+ m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])])
60
+ m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])])
61
+ m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])])
62
+ m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])])
63
+ m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])])
64
+ m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])])
65
+ m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])])
66
+ m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])])
67
+ m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])])
68
+ m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])])
69
+ m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])])
70
+ m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])])
71
+ m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])])
72
+ m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])])
73
+ m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])])
74
+ m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])])
75
+ m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])])
76
+ m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])])
77
+ m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])])
78
+ m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])])
79
+ m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])])
80
+ m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])])
81
+ m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])])
82
+ m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])])
83
+ m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])])
84
+ m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])])
85
+ m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])])
86
+ m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])])
87
+ m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])])
88
+ m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])])
89
+ m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])])
90
+ m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])])
91
+ m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])])
92
+ m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])])
93
+ m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])])
94
+ m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])])
95
+ m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])])
96
+ m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])])
97
+ m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])])
98
+ m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])])
99
+ m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])])
data/lib/xcrypt/ffi.rb CHANGED
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "ffi"
4
- require "ffi-compiler/loader"
5
4
 
6
5
  module XCrypt
7
6
  # Low-level FFI bindings for libxcrypt.
@@ -9,9 +8,9 @@ module XCrypt
9
8
  # Consumers should use the high-level {XCrypt} module methods instead of
10
9
  # calling into this module directly.
11
10
  #
12
- # The shared library loaded here is compiled from the libxcrypt submodule
13
- # (ext/libxcrypt) via ffi-compiler. Run <tt>bundle exec rake compile</tt>
14
- # to build it before loading this gem.
11
+ # The shared library loaded here is built from the libxcrypt submodule
12
+ # (ext/libxcrypt). Run <tt>bundle exec rake compile</tt> to build it
13
+ # before loading this gem.
15
14
  module FFI
16
15
  extend ::FFI::Library
17
16
 
@@ -32,11 +31,15 @@ module XCrypt
32
31
  CRYPT_SALT_METHOD_LEGACY = 3
33
32
  CRYPT_SALT_TOO_CHEAP = 4
34
33
 
35
- # Load the native extension compiled from the libxcrypt submodule.
36
- # FFI::Compiler::Loader searches for lib<arch>-<os>/libxcrypt.{bundle,so}
37
- # relative to this file's location.
34
+ # Load the shared library built from the libxcrypt submodule.
35
+ # It lives in a platform-specific subdirectory next to this file.
38
36
  begin
39
- ffi_lib ::FFI::Compiler::Loader.find("xcrypt")
37
+ _ext = ::FFI::Platform.mac? ? "bundle" : "so"
38
+ _lib = File.expand_path(
39
+ "../#{::FFI::Platform::ARCH}-#{::FFI::Platform::OS}/libxcrypt.#{_ext}",
40
+ __FILE__
41
+ )
42
+ ffi_lib _lib
40
43
  rescue LoadError
41
44
  raise LoadError,
42
45
  "XCrypt native extension not found. " \
@@ -70,6 +73,7 @@ module XCrypt
70
73
  # Returns the prefix string of the library's preferred (strongest)
71
74
  # hashing method.
72
75
  attach_function :crypt_preferred_method, [], :string
76
+
73
77
  end
74
78
 
75
79
  private_constant :FFI
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module XCrypt
4
- VERSION = "0.1.2"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "securerandom"
4
+
5
+ module XCrypt
6
+ # Setting-string generation for the yescrypt and scrypt algorithms.
7
+ #
8
+ # Both algorithms share a base-64 alphabet and encoding scheme taken from
9
+ # libxcrypt's +alg-yescrypt-common.c+. The public methods produce setting
10
+ # strings that can be passed directly to {XCrypt.crypt}.
11
+ #
12
+ # @example Generate a $y$ yescrypt setting
13
+ # setting = XCrypt::Yescrypt.generate_setting(n: 16384, r: 8, p: 1)
14
+ # hash = XCrypt.crypt("hunter2", setting)
15
+ #
16
+ # @example Generate a $7$ scrypt setting
17
+ # setting = XCrypt::Yescrypt.generate_scrypt_setting(n: 16384, r: 32, p: 1)
18
+ # hash = XCrypt.crypt("hunter2", setting)
19
+ module Yescrypt
20
+ extend self
21
+
22
+ # -------------------------------------------------------------------------
23
+ # Flag constants — mirrors alg-yescrypt.h
24
+ #
25
+ # These may be OR'd together to form the +flags:+ argument of
26
+ # {generate_setting}, except that {WORM} stands alone (do not combine
27
+ # with {RW}).
28
+ # -------------------------------------------------------------------------
29
+
30
+ # Classic scrypt with minimal extensions (t parameter support only).
31
+ WORM = 0x001
32
+
33
+ # Full yescrypt mode — time-memory tradeoff resistant.
34
+ RW = 0x002
35
+
36
+ # Number of inner rounds.
37
+ ROUNDS_3 = 0x000
38
+ ROUNDS_6 = 0x004
39
+
40
+ # Memory-access gather width.
41
+ GATHER_1 = 0x000
42
+ GATHER_2 = 0x008
43
+ GATHER_4 = 0x010
44
+ GATHER_8 = 0x018
45
+
46
+ # Simple mix factor.
47
+ SIMPLE_1 = 0x000
48
+ SIMPLE_2 = 0x020
49
+ SIMPLE_4 = 0x040
50
+ SIMPLE_8 = 0x060
51
+
52
+ # S-box size.
53
+ SBOX_6K = 0x000
54
+ SBOX_12K = 0x080
55
+ SBOX_24K = 0x100
56
+ SBOX_48K = 0x180
57
+ SBOX_96K = 0x200
58
+ SBOX_192K = 0x280
59
+ SBOX_384K = 0x300
60
+ SBOX_768K = 0x380
61
+
62
+ # Recommended defaults: RW mode with 6 rounds, 4-wide gather, 2x simple
63
+ # mix, and a 12 KiB S-box.
64
+ DEFAULTS = RW | ROUNDS_6 | GATHER_4 | SIMPLE_2 | SBOX_12K
65
+
66
+ # -------------------------------------------------------------------------
67
+ # Public interface
68
+ # -------------------------------------------------------------------------
69
+
70
+ # Generates a +$y$+ yescrypt setting string from explicit parameters.
71
+ #
72
+ # Implements the encoding of libxcrypt's +yescrypt_encode_params_r+
73
+ # (alg-yescrypt-common.c) in pure Ruby, because that function is not
74
+ # exported on Linux.
75
+ #
76
+ # @param n [Integer] memory/CPU cost; must be a power of 2 greater than 1
77
+ # @param r [Integer] block size (default: 8)
78
+ # @param p [Integer] parallelism (default: 1)
79
+ # @param t [Integer] additional time cost (default: 0)
80
+ # @param flags [Integer] yescrypt mode flags; see +WORM+/+RW+/+ROUNDS_*+
81
+ # etc.; defaults to {DEFAULTS}
82
+ # @return [String] a +$y$+ setting string
83
+ # @raise [ArgumentError] if +n+ is not a valid power of 2, or if +flags+
84
+ # is an unsupported combination
85
+ def generate_setting(n:, r: 8, p: 1, t: 0, flags: DEFAULTS)
86
+ n_log2 = log2_of_power_of_2(n)
87
+
88
+ # Compute the "flavor" field exactly as yescrypt_encode_params_r does:
89
+ # flags < RW → flavor = flags (WORM / pure-scrypt modes)
90
+ # flags is valid RW → flavor = RW + (flags >> 2)
91
+ flavor =
92
+ if flags < RW
93
+ flags
94
+ elsif (flags & 0x3) == RW && flags <= (RW | 0x3fc)
95
+ RW + (flags >> 2)
96
+ else
97
+ raise ArgumentError, "invalid yescrypt flags: 0x#{flags.to_s(16)}"
98
+ end
99
+
100
+ # "have" bitmask indicates which optional fields follow r.
101
+ have = 0
102
+ have |= 1 if p != 1
103
+ have |= 2 if t != 0
104
+
105
+ setting = +"$y$"
106
+ setting << encode_varint(flavor, 0)
107
+ setting << encode_varint(n_log2, 1)
108
+ setting << encode_varint(r, 1)
109
+ if have != 0
110
+ setting << encode_varint(have, 1)
111
+ setting << encode_varint(p, 2) if p != 1
112
+ setting << encode_varint(t, 1) if t != 0
113
+ end
114
+ setting << "$"
115
+ setting << encode_bytes(SecureRandom.random_bytes(32))
116
+ setting
117
+ end
118
+
119
+ # Generates a +$7$+ scrypt setting string from explicit parameters.
120
+ #
121
+ # Encodes the setting using the same base-64 alphabet and field layout as
122
+ # libxcrypt's +gensalt_scrypt_rn+ (crypt-scrypt.c).
123
+ #
124
+ # @param n [Integer] memory/CPU cost; must be a power of 2 greater than 1
125
+ # @param r [Integer] block size (default: 32)
126
+ # @param p [Integer] parallelism (default: 1)
127
+ # @return [String] a +$7$+ setting string
128
+ # @raise [ArgumentError] if +n+ is not a valid power of 2
129
+ def generate_scrypt_setting(n:, r: 32, p: 1)
130
+ n_log2 = log2_of_power_of_2(n)
131
+
132
+ setting = +"$7$"
133
+ setting << B64[n_log2]
134
+ setting << encode_uint32(r, 30)
135
+ setting << encode_uint32(p, 30)
136
+ setting << encode_bytes(SecureRandom.random_bytes(32))
137
+ setting
138
+ end
139
+
140
+ private
141
+
142
+ # Base-64 alphabet shared by yescrypt and scrypt (crypt-style, not RFC 4648).
143
+ B64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
144
+
145
+ # Returns log2(n) after validating that n is a power of 2 greater than 1.
146
+ def log2_of_power_of_2(n)
147
+ n_log2 = Integer(Math.log2(n).round)
148
+ raise ArgumentError, "n must be a power of 2 greater than 1 (got #{n})" unless (1 << n_log2) == n && n > 1
149
+ n_log2
150
+ end
151
+
152
+ # Variable-length base-64 encoding for yescrypt parameter fields
153
+ # (encode64_uint32 in alg-yescrypt-common.c, last argument = min).
154
+ #
155
+ # The first character of the output encodes both the number of subsequent
156
+ # characters and the most-significant bits. The character ranges used are:
157
+ # 1 char : indices 0..47 (48 distinct values)
158
+ # 2 chars: indices 48..56 (9 × 64 = 576 additional values)
159
+ # 3 chars: indices 57..60 (4 × 64² = 16 384 additional values), …
160
+ def encode_varint(src, min)
161
+ raise ArgumentError, "value #{src} is below minimum #{min}" if src < min
162
+
163
+ src -= min
164
+ start = 0
165
+ endv = 47
166
+ chars = 1
167
+ bits = 0
168
+
169
+ loop do
170
+ count = (endv + 1 - start) << bits
171
+ break if src < count
172
+ raise ArgumentError, "value too large for yescrypt varint encoding" if start >= 63
173
+
174
+ start = endv + 1
175
+ endv = start + (62 - endv) / 2
176
+ src -= count
177
+ chars += 1
178
+ bits += 6
179
+ end
180
+
181
+ result = +B64[start + (src >> bits)]
182
+ (chars - 1).times { bits -= 6; result << B64[(src >> bits) & 0x3f] }
183
+ result
184
+ end
185
+
186
+ # Fixed-width base-64 encoding of a 32-bit value using +srcbits+ bits,
187
+ # LSB first (ceil(srcbits/6) output characters).
188
+ # Used for scrypt's r and p fields (encode64_uint32 in crypt-scrypt.c).
189
+ def encode_uint32(value, srcbits)
190
+ out = +""
191
+ bits = 0
192
+ while bits < srcbits
193
+ out << B64[value & 0x3f]
194
+ value >>= 6
195
+ bits += 6
196
+ end
197
+ out
198
+ end
199
+
200
+ # Encodes a binary string using the fixed-width base-64 scheme, processing
201
+ # 3 bytes (24 bits) into 4 characters at a time (encode64 in both
202
+ # alg-yescrypt-common.c and crypt-scrypt.c).
203
+ def encode_bytes(bytes)
204
+ out = +""
205
+ i = 0
206
+ while i < bytes.bytesize
207
+ value = 0
208
+ bits = 0
209
+ while bits < 24 && i < bytes.bytesize
210
+ value |= bytes.getbyte(i) << bits
211
+ bits += 8
212
+ i += 1
213
+ end
214
+ out << encode_uint32(value, bits)
215
+ end
216
+ out
217
+ end
218
+ end
219
+ end
data/lib/xcrypt.rb CHANGED
@@ -16,9 +16,19 @@
16
16
  #
17
17
  # @example Use the generic interface
18
18
  # hash = XCrypt.crypt("hunter2", algorithm: :sha512)
19
+ #
20
+ # @example Generate a yescrypt setting with explicit N, r, p, t, and flags
21
+ # setting = XCrypt.generate_setting(:yescrypt, n: 16384, r: 8, p: 1, t: 0,
22
+ # flags: XCrypt::YESCRYPT_DEFAULTS)
23
+ # hash = XCrypt.crypt("hunter2", setting)
24
+ #
25
+ # @example Generate a scrypt ($7$) setting with explicit N, r, p
26
+ # setting = XCrypt.generate_setting(:scrypt, n: 16384, r: 32, p: 1)
27
+ # hash = XCrypt.crypt("hunter2", setting)
19
28
  module XCrypt
20
29
  require "xcrypt/version"
21
30
  require "xcrypt/ffi"
31
+ require "xcrypt/yescrypt"
22
32
 
23
33
  # Raised when hashing or salt generation fails. Common causes include an
24
34
  # unsupported algorithm, a malformed setting string, or a passphrase that
@@ -122,14 +132,14 @@ module XCrypt
122
132
  # @raise (see #yescrypt)
123
133
 
124
134
  ALGORITHMS.each_key do |algorithm|
125
- define_method(algorithm) do |phrase, setting = nil, cost: nil|
135
+ define_method(algorithm) do |phrase, setting = nil, cost: nil, n: nil, r: nil, p: nil, t: nil, flags: nil|
126
136
  if setting
127
137
  setting_algorithm = detect_algorithm(setting)
128
138
  if setting_algorithm != algorithm
129
139
  raise ArgumentError, "setting algorithm #{setting_algorithm.inspect} does not match expected #{algorithm.inspect}"
130
140
  end
131
141
  end
132
- crypt(phrase, setting, algorithm:, cost:)
142
+ crypt(phrase, setting, algorithm:, cost:, n:, r:, p:, t:, flags:)
133
143
  end
134
144
  end
135
145
 
@@ -161,12 +171,20 @@ module XCrypt
161
171
  # setting; ignored when +setting+ is already a String
162
172
  # @param cost [Integer, nil] work-factor override passed to
163
173
  # {generate_setting}; uses the library default when +nil+
174
+ # @param n [Integer, nil] explicit N parameter for yescrypt/scrypt; passed
175
+ # to {generate_setting} when no +setting+ is provided
176
+ # @param r [Integer, nil] explicit r parameter; passed to {generate_setting}
177
+ # @param p [Integer, nil] explicit p parameter; passed to {generate_setting}
178
+ # @param t [Integer, nil] explicit t parameter (yescrypt only); passed to
179
+ # {generate_setting}
180
+ # @param flags [Integer, nil] explicit yescrypt flags; passed to
181
+ # {generate_setting}
164
182
  # @return [String] the hashed password
165
183
  # @raise [Error] if +crypt_rn+ returns +NULL+, indicating an invalid
166
184
  # setting or an unsupported algorithm
167
- def crypt(phrase, setting = nil, algorithm: nil, cost: nil)
185
+ def crypt(phrase, setting = nil, algorithm: nil, cost: nil, n: nil, r: nil, p: nil, t: nil, flags: nil)
168
186
  setting, algorithm = nil, setting if setting.is_a? Symbol
169
- setting ||= generate_setting(algorithm, cost:)
187
+ setting ||= generate_setting(algorithm, cost:, n:, r:, p:, t:, flags:)
170
188
  data = ::FFI::MemoryPointer.new(:uint8, FFI::CRYPT_DATA_SIZE)
171
189
  result_ptr = FFI.crypt_rn(phrase, setting, data, FFI::CRYPT_DATA_SIZE)
172
190
  raise Error, "crypt failed: invalid setting or unsupported algorithm" if result_ptr.null?
@@ -196,19 +214,58 @@ module XCrypt
196
214
 
197
215
  # Generates a fresh setting string suitable for passing to {crypt}.
198
216
  #
199
- # Delegates to libxcrypt's +crypt_gensalt_rn+, which draws entropy from
200
- # the OS to produce the random salt component. When +algorithm+ is +nil+,
201
- # the library selects its preferred (strongest) algorithm.
217
+ # When only +algorithm+ and optionally +cost+ are given, delegates to
218
+ # libxcrypt's +crypt_gensalt_rn+, which draws entropy from the OS.
219
+ #
220
+ # When +n+, +r+, +p+, +t+, or +flags+ are supplied the method generates the
221
+ # setting directly from those parameters instead, delegating to
222
+ # {XCrypt::Yescrypt}:
223
+ #
224
+ # * For +:yescrypt+ (and +:gost_yescrypt+): delegates to
225
+ # {XCrypt::Yescrypt.generate_setting}, producing a +$y$+ setting.
226
+ # +n+ must be a power of 2 greater than 1; +r+, +p+, +t+, and +flags+
227
+ # default to 8, 1, 0, and {XCrypt::Yescrypt::DEFAULTS} respectively.
228
+ #
229
+ # * For +:scrypt+: delegates to {XCrypt::Yescrypt.generate_scrypt_setting},
230
+ # producing a +$7$+ setting. +n+ must be a power of 2 (2..2^63); +r+
231
+ # and +p+ default to 32 and 1. +t+ and +flags+ are not used for scrypt.
232
+ #
233
+ # When +algorithm+ is +nil+, the library selects its preferred algorithm.
202
234
  #
203
235
  # @param algorithm [Symbol, nil] the desired algorithm; uses the library
204
236
  # default when +nil+
205
237
  # @param cost [Integer, nil] work-factor for the generated setting; a value
206
- # of +0+ selects the library's own default cost
238
+ # of +0+ selects the library's own default cost; ignored when +n:+ is set
239
+ # @param n [Integer, nil] explicit N (memory/CPU cost, must be a power of 2
240
+ # greater than 1); yescrypt and scrypt only
241
+ # @param r [Integer, nil] block size parameter; yescrypt and scrypt only
242
+ # @param p [Integer, nil] parallelism parameter; yescrypt and scrypt only
243
+ # @param t [Integer, nil] additional time cost; yescrypt only
244
+ # @param flags [Integer, nil] yescrypt mode flags; see {XCrypt::Yescrypt}
245
+ # constants; yescrypt only; defaults to {XCrypt::Yescrypt::DEFAULTS}
207
246
  # @return [String] a setting string beginning with the algorithm prefix
208
- # @raise [ArgumentError] if +algorithm+ is not a key in {ALGORITHMS}
209
- # @raise [Error] if +crypt_gensalt_rn+ returns +NULL+
210
- def generate_setting(algorithm = nil, cost: nil)
211
- prefix = ALGORITHMS.fetch(algorithm) { raise ArgumentError, "unknown algorithm: #{algorithm.inspect}" } if algorithm
247
+ # @raise [ArgumentError] if +algorithm+ is not a key in {ALGORITHMS}, or if
248
+ # +n+ is not a power of 2 greater than 1
249
+ # @raise [Error] if the underlying C call returns +NULL+
250
+ def generate_setting(algorithm = nil, cost: nil, n: nil, r: nil, p: nil, t: nil, flags: nil)
251
+ if algorithm
252
+ ALGORITHMS.key?(algorithm) or raise ArgumentError, "unknown algorithm: #{algorithm.inspect}"
253
+ end
254
+
255
+ if n || r || p || t || flags
256
+ case algorithm
257
+ when :yescrypt, :gost_yescrypt, nil
258
+ return Yescrypt.generate_setting(n: n || 4096, r: r || 8, p: p || 1,
259
+ t: t || 0, flags: flags || Yescrypt::DEFAULTS)
260
+ when :scrypt
261
+ return Yescrypt.generate_scrypt_setting(n: n || 16384, r: r || 32, p: p || 1)
262
+ else
263
+ raise ArgumentError,
264
+ "n/r/p/t/flags parameters are only supported for :yescrypt and :scrypt, got #{algorithm.inspect}"
265
+ end
266
+ end
267
+
268
+ prefix = ALGORITHMS[algorithm]
212
269
  cost ||= 0
213
270
 
214
271
  output = ::FFI::MemoryPointer.new(:char, FFI::CRYPT_GENSALT_OUTPUT_SIZE)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: xcrypt
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Haase
@@ -24,33 +24,33 @@ dependencies:
24
24
  - !ruby/object:Gem::Version
25
25
  version: '1.0'
26
26
  - !ruby/object:Gem::Dependency
27
- name: ffi-compiler
27
+ name: rake
28
28
  requirement: !ruby/object:Gem::Requirement
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '1.0'
32
+ version: '13.0'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '1.0'
39
+ version: '13.0'
40
40
  - !ruby/object:Gem::Dependency
41
- name: rake
41
+ name: rake-compiler-dock
42
42
  requirement: !ruby/object:Gem::Requirement
43
43
  requirements:
44
- - - "~>"
44
+ - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '13.0'
47
- type: :runtime
46
+ version: '0'
47
+ type: :development
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
- - - "~>"
51
+ - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '13.0'
53
+ version: '0'
54
54
  description: |
55
55
  Ruby FFI bindings for libxcrypt, a modern library for one-way hashing of
56
56
  passwords. Supports yescrypt, bcrypt, SHA-512, SHA-256, and other
@@ -79,6 +79,11 @@ files:
79
79
  - ext/libxcrypt/build-aux/m4/ax_gcc_func_attribute.m4
80
80
  - ext/libxcrypt/build-aux/m4/ax_require_defined.m4
81
81
  - ext/libxcrypt/build-aux/m4/ax_valgrind_check.m4
82
+ - ext/libxcrypt/build-aux/m4/libtool.m4
83
+ - ext/libxcrypt/build-aux/m4/ltoptions.m4
84
+ - ext/libxcrypt/build-aux/m4/ltsugar.m4
85
+ - ext/libxcrypt/build-aux/m4/ltversion.m4
86
+ - ext/libxcrypt/build-aux/m4/lt~obsolete.m4
82
87
  - ext/libxcrypt/build-aux/m4/pkg_compat.m4
83
88
  - ext/libxcrypt/build-aux/m4/zw_alignment.m4
84
89
  - ext/libxcrypt/build-aux/m4/zw_automodern.m4
@@ -216,10 +221,10 @@ files:
216
221
  - ext/libxcrypt/test/symbols-compat.pl
217
222
  - ext/libxcrypt/test/symbols-renames.pl
218
223
  - ext/libxcrypt/test/symbols-static.pl
219
- - ext/xcrypt/xcrypt.c
220
224
  - lib/xcrypt.rb
221
225
  - lib/xcrypt/ffi.rb
222
226
  - lib/xcrypt/version.rb
227
+ - lib/xcrypt/yescrypt.rb
223
228
  homepage: https://github.com/rkh/ruby-xcrypt
224
229
  licenses:
225
230
  - MIT
data/ext/xcrypt/xcrypt.c DELETED
@@ -1,9 +0,0 @@
1
- /*
2
- * xcrypt.c — compilation stub for ffi-compiler.
3
- *
4
- * The actual implementation lives in libxcrypt (ext/libxcrypt), which is
5
- * linked statically into this shared library at build time. Ruby code
6
- * reaches the libxcrypt API through Ruby-FFI (see lib/xcrypt/ffi.rb); no
7
- * wrapper functions are needed here.
8
- */
9
- #include "crypt.h"