securerandom 0.2.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 396cd8fff3b7710c09c49c19017c3b32b81810ab7aa4d4d468e88e0231294da4
4
- data.tar.gz: c0a2fa9b2e657eff48a221877b49a3c7d20d18ca955cfcc90594b28a4adcef74
3
+ metadata.gz: 1e5fca1a0e74f7d256340e2b2e12196ed97adc8ebc512697473d56c110fe682c
4
+ data.tar.gz: 19d45327f812eede26a7a1432381e8c43d114ca0268ea1697b727db5c94c5ca9
5
5
  SHA512:
6
- metadata.gz: 7f562481a1134ea11d151e3167c700b9995767aa7f643c8d95b112af872c9a2884e74194550329b618a05081b05bc96e21d1e7c7e117940c4a8edbb5a94897d7
7
- data.tar.gz: 81aea2e28ad6406656700f8b2df92f9a87d1b6eca6ab5b2edd9753dcf444968bdbedda317a1785ad781ddf4ddbdefac117cc91871466f1a8c7af4f36d7ed1f15
6
+ metadata.gz: e36e2adb4f23bf29d52a519e68e89dd5c947803399fd493bdab26c452e38142f11d7af03ef436f70c6f7a3c8881cee75b179167b5376556dfc91e4ab3f8ccc46
7
+ data.tar.gz: 0e86593665d73afc22c91f6964df05f6134dc3b75b443932dde93ee38d296c692478b456a95110e042c4ff7dc54ccefb6cad84847dcd8a3e97d47736a6f25a6c
@@ -1,17 +1,24 @@
1
- name: build
1
+ name: test
2
2
 
3
3
  on: [push, pull_request]
4
4
 
5
5
  jobs:
6
- build:
6
+ ruby-versions:
7
+ uses: ruby/actions/.github/workflows/ruby_versions.yml@master
8
+ with:
9
+ engine: cruby
10
+ min_version: 2.6
11
+
12
+ test:
13
+ needs: ruby-versions
7
14
  name: build (${{ matrix.ruby }} / ${{ matrix.os }})
8
15
  strategy:
9
16
  matrix:
10
- ruby: [ head, '3.0', '2.7', '2.6' ]
17
+ ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
11
18
  os: [ ubuntu-latest, macos-latest, windows-latest ]
12
19
  runs-on: ${{ matrix.os }}
13
20
  steps:
14
- - uses: actions/checkout@v3
21
+ - uses: actions/checkout@v4
15
22
  - name: Set up Ruby
16
23
  uses: ruby/setup-ruby@v1
17
24
  with:
@@ -1,9 +1,14 @@
1
1
  # -*- coding: us-ascii -*-
2
2
  # frozen_string_literal: true
3
3
 
4
- # == Random number formatter.
4
+ # == \Random number formatter.
5
5
  #
6
- # Formats generated random numbers in many manners.
6
+ # Formats generated random numbers in many manners. When <tt>'random/formatter'</tt>
7
+ # is required, several methods are added to empty core module <tt>Random::Formatter</tt>,
8
+ # making them available as Random's instance and module methods.
9
+ #
10
+ # Standard library SecureRandom is also extended with the module, and the methods
11
+ # described below are available as a module methods in it.
7
12
  #
8
13
  # === Examples
9
14
  #
@@ -11,34 +16,45 @@
11
16
  #
12
17
  # require 'random/formatter'
13
18
  #
19
+ # prng = Random.new
14
20
  # prng.hex(10) #=> "52750b30ffbc7de3b362"
15
21
  # prng.hex(10) #=> "92b15d6c8dc4beb5f559"
16
22
  # prng.hex(13) #=> "39b290146bea6ce975c37cfc23"
23
+ # # or just
24
+ # Random.hex #=> "1aed0c631e41be7f77365415541052ee"
17
25
  #
18
26
  # Generate random base64 strings:
19
27
  #
20
28
  # prng.base64(10) #=> "EcmTPZwWRAozdA=="
21
29
  # prng.base64(10) #=> "KO1nIU+p9DKxGg=="
22
30
  # prng.base64(12) #=> "7kJSM/MzBJI+75j8"
31
+ # Random.base64(4) #=> "bsQ3fQ=="
23
32
  #
24
33
  # Generate random binary strings:
25
34
  #
26
35
  # prng.random_bytes(10) #=> "\016\t{\370g\310pbr\301"
27
36
  # prng.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337"
37
+ # Random.random_bytes(6) #=> "\xA1\xE6Lr\xC43"
28
38
  #
29
39
  # Generate alphanumeric strings:
30
40
  #
31
41
  # prng.alphanumeric(10) #=> "S8baxMJnPl"
32
42
  # prng.alphanumeric(10) #=> "aOxAg8BAJe"
43
+ # Random.alphanumeric #=> "TmP9OsJHJLtaZYhP"
33
44
  #
34
45
  # Generate UUIDs:
35
46
  #
36
47
  # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
37
48
  # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
49
+ # Random.uuid #=> "f14e0271-de96-45cc-8911-8910292a42cd"
50
+ #
51
+ # All methods are available in the standard library SecureRandom, too:
52
+ #
53
+ # SecureRandom.hex #=> "05b45376a30c67238eb93b16499e50cf"
38
54
 
39
55
  module Random::Formatter
40
56
 
41
- # Random::Formatter#random_bytes generates a random binary string.
57
+ # Generate a random binary string.
42
58
  #
43
59
  # The argument _n_ specifies the length of the result string.
44
60
  #
@@ -49,14 +65,16 @@ module Random::Formatter
49
65
  #
50
66
  # require 'random/formatter'
51
67
  #
52
- # prng.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
68
+ # Random.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6"
69
+ # # or
70
+ # prng = Random.new
53
71
  # prng.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97"
54
72
  def random_bytes(n=nil)
55
73
  n = n ? n.to_int : 16
56
74
  gen_random(n)
57
75
  end
58
76
 
59
- # Random::Formatter#hex generates a random hexadecimal string.
77
+ # Generate a random hexadecimal string.
60
78
  #
61
79
  # The argument _n_ specifies the length, in bytes, of the random number to be generated.
62
80
  # The length of the resulting hexadecimal string is twice of _n_.
@@ -68,13 +86,15 @@ module Random::Formatter
68
86
  #
69
87
  # require 'random/formatter'
70
88
  #
71
- # prng.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
89
+ # Random.hex #=> "eb693ec8252cd630102fd0d0fb7c3485"
90
+ # # or
91
+ # prng = Random.new
72
92
  # prng.hex #=> "91dc3bfb4de5b11d029d376634589b61"
73
93
  def hex(n=nil)
74
94
  random_bytes(n).unpack1("H*")
75
95
  end
76
96
 
77
- # Random::Formatter#base64 generates a random base64 string.
97
+ # Generate a random base64 string.
78
98
  #
79
99
  # The argument _n_ specifies the length, in bytes, of the random number
80
100
  # to be generated. The length of the result string is about 4/3 of _n_.
@@ -86,7 +106,9 @@ module Random::Formatter
86
106
  #
87
107
  # require 'random/formatter'
88
108
  #
89
- # prng.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
109
+ # Random.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A=="
110
+ # # or
111
+ # prng = Random.new
90
112
  # prng.base64 #=> "6BbW0pxO0YENxn38HMUbcQ=="
91
113
  #
92
114
  # See RFC 3548 for the definition of base64.
@@ -94,7 +116,7 @@ module Random::Formatter
94
116
  [random_bytes(n)].pack("m0")
95
117
  end
96
118
 
97
- # Random::Formatter#urlsafe_base64 generates a random URL-safe base64 string.
119
+ # Generate a random URL-safe base64 string.
98
120
  #
99
121
  # The argument _n_ specifies the length, in bytes, of the random number
100
122
  # to be generated. The length of the result string is about 4/3 of _n_.
@@ -112,7 +134,9 @@ module Random::Formatter
112
134
  #
113
135
  # require 'random/formatter'
114
136
  #
115
- # prng.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
137
+ # Random.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
138
+ # # or
139
+ # prng = Random.new
116
140
  # prng.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg"
117
141
  #
118
142
  # prng.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ=="
@@ -126,12 +150,14 @@ module Random::Formatter
126
150
  s
127
151
  end
128
152
 
129
- # Random::Formatter#uuid generates a random v4 UUID (Universally Unique IDentifier).
153
+ # Generate a random v4 UUID (Universally Unique IDentifier).
130
154
  #
131
155
  # require 'random/formatter'
132
156
  #
133
- # prng.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
134
- # prng.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
157
+ # Random.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594"
158
+ # Random.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab"
159
+ # # or
160
+ # prng = Random.new
135
161
  # prng.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b"
136
162
  #
137
163
  # The version 4 UUID is purely random (except the version).
@@ -139,7 +165,7 @@ module Random::Formatter
139
165
  #
140
166
  # The result contains 122 random bits (15.25 random bytes).
141
167
  #
142
- # See RFC 4122 for details of UUID.
168
+ # See RFC4122[https://datatracker.ietf.org/doc/html/rfc4122] for details of UUID.
143
169
  #
144
170
  def uuid
145
171
  ary = random_bytes(16).unpack("NnnnnN")
@@ -148,11 +174,130 @@ module Random::Formatter
148
174
  "%08x-%04x-%04x-%04x-%04x%08x" % ary
149
175
  end
150
176
 
177
+ alias uuid_v4 uuid
178
+
179
+ # Generate a random v7 UUID (Universally Unique IDentifier).
180
+ #
181
+ # require 'random/formatter'
182
+ #
183
+ # Random.uuid_v7 # => "0188d4c3-1311-7f96-85c7-242a7aa58f1e"
184
+ # Random.uuid_v7 # => "0188d4c3-16fe-744f-86af-38fa04c62bb5"
185
+ # Random.uuid_v7 # => "0188d4c3-1af8-764f-b049-c204ce0afa23"
186
+ # Random.uuid_v7 # => "0188d4c3-1e74-7085-b14f-ef6415dc6f31"
187
+ # # |<--sorted-->| |<----- random ---->|
188
+ #
189
+ # # or
190
+ # prng = Random.new
191
+ # prng.uuid_v7 # => "0188ca51-5e72-7950-a11d-def7ff977c98"
192
+ #
193
+ # The version 7 UUID starts with the least significant 48 bits of a 64 bit
194
+ # Unix timestamp (milliseconds since the epoch) and fills the remaining bits
195
+ # with random data, excluding the version and variant bits.
196
+ #
197
+ # This allows version 7 UUIDs to be sorted by creation time. Time ordered
198
+ # UUIDs can be used for better database index locality of newly inserted
199
+ # records, which may have a significant performance benefit compared to random
200
+ # data inserts.
201
+ #
202
+ # The result contains 74 random bits (9.25 random bytes).
203
+ #
204
+ # Note that this method cannot be made reproducable with Kernel#srand, which
205
+ # can only affect the random bits. The sorted bits will still be based on
206
+ # Process.clock_gettime.
207
+ #
208
+ # See draft-ietf-uuidrev-rfc4122bis[https://datatracker.ietf.org/doc/draft-ietf-uuidrev-rfc4122bis/]
209
+ # for details of UUIDv7.
210
+ #
211
+ # ==== Monotonicity
212
+ #
213
+ # UUIDv7 has millisecond precision by default, so multiple UUIDs created
214
+ # within the same millisecond are not issued in monotonically increasing
215
+ # order. To create UUIDs that are time-ordered with sub-millisecond
216
+ # precision, up to 12 bits of additional timestamp may added with
217
+ # +extra_timestamp_bits+. The extra timestamp precision comes at the expense
218
+ # of random bits. Setting <tt>extra_timestamp_bits: 12</tt> provides ~244ns
219
+ # of precision, but only 62 random bits (7.75 random bytes).
220
+ #
221
+ # prng = Random.new
222
+ # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 12) }
223
+ # # =>
224
+ # ["0188d4c7-13da-74f9-8b53-22a786ffdd5a",
225
+ # "0188d4c7-13da-753b-83a5-7fb9b2afaeea",
226
+ # "0188d4c7-13da-754a-88ea-ac0baeedd8db",
227
+ # "0188d4c7-13da-7557-83e1-7cad9cda0d8d"]
228
+ # # |<--- sorted --->| |<-- random --->|
229
+ #
230
+ # Array.new(4) { prng.uuid_v7(extra_timestamp_bits: 8) }
231
+ # # =>
232
+ # ["0188d4c7-3333-7a95-850a-de6edb858f7e",
233
+ # "0188d4c7-3333-7ae8-842e-bc3a8b7d0cf9", # <- out of order
234
+ # "0188d4c7-3333-7ae2-995a-9f135dc44ead", # <- out of order
235
+ # "0188d4c7-3333-7af9-87c3-8f612edac82e"]
236
+ # # |<--- sorted -->||<---- random --->|
237
+ #
238
+ # Any rollbacks of the system clock will break monotonicity. UUIDv7 is based
239
+ # on UTC, which excludes leap seconds and can rollback the clock. To avoid
240
+ # this, the system clock can synchronize with an NTP server configured to use
241
+ # a "leap smear" approach. NTP or PTP will also be needed to synchronize
242
+ # across distributed nodes.
243
+ #
244
+ # Counters and other mechanisms for stronger guarantees of monotonicity are
245
+ # not implemented. Applications with stricter requirements should follow
246
+ # {Section 6.2}[https://www.ietf.org/archive/id/draft-ietf-uuidrev-rfc4122bis-07.html#monotonicity_counters]
247
+ # of the specification.
248
+ #
249
+ def uuid_v7(extra_timestamp_bits: 0)
250
+ case (extra_timestamp_bits = Integer(extra_timestamp_bits))
251
+ when 0 # min timestamp precision
252
+ ms = Process.clock_gettime(Process::CLOCK_REALTIME, :millisecond)
253
+ rand = random_bytes(10)
254
+ rand.setbyte(0, rand.getbyte(0) & 0x0f | 0x70) # version
255
+ rand.setbyte(2, rand.getbyte(2) & 0x3f | 0x80) # variant
256
+ "%08x-%04x-%s" % [
257
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
258
+ (ms & 0x0000_0000_0000_ffff),
259
+ rand.unpack("H4H4H12").join("-")
260
+ ]
261
+
262
+ when 12 # max timestamp precision
263
+ ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
264
+ .divmod(1_000_000)
265
+ extra_bits = ns * 4096 / 1_000_000
266
+ rand = random_bytes(8)
267
+ rand.setbyte(0, rand.getbyte(0) & 0x3f | 0x80) # variant
268
+ "%08x-%04x-7%03x-%s" % [
269
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
270
+ (ms & 0x0000_0000_0000_ffff),
271
+ extra_bits,
272
+ rand.unpack("H4H12").join("-")
273
+ ]
274
+
275
+ when (0..12) # the generic version is slower than the special cases above
276
+ rand_a, rand_b1, rand_b2, rand_b3 = random_bytes(10).unpack("nnnN")
277
+ rand_mask_bits = 12 - extra_timestamp_bits
278
+ ms, ns = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
279
+ .divmod(1_000_000)
280
+ "%08x-%04x-%04x-%04x-%04x%08x" % [
281
+ (ms & 0x0000_ffff_ffff_0000) >> 16,
282
+ (ms & 0x0000_0000_0000_ffff),
283
+ 0x7000 |
284
+ ((ns * (1 << extra_timestamp_bits) / 1_000_000) << rand_mask_bits) |
285
+ rand_a & ((1 << rand_mask_bits) - 1),
286
+ 0x8000 | (rand_b1 & 0x3fff),
287
+ rand_b2,
288
+ rand_b3
289
+ ]
290
+
291
+ else
292
+ raise ArgumentError, "extra_timestamp_bits must be in 0..12"
293
+ end
294
+ end
295
+
151
296
  private def gen_random(n)
152
297
  self.bytes(n)
153
298
  end
154
299
 
155
- # Random::Formatter#choose generates a string that randomly draws from a
300
+ # Generate a string that randomly draws from a
156
301
  # source array of characters.
157
302
  #
158
303
  # The argument _source_ specifies the array of characters from which
@@ -196,22 +341,31 @@ module Random::Formatter
196
341
  end
197
342
 
198
343
  ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9']
199
- # Random::Formatter#alphanumeric generates a random alphanumeric string.
344
+ # Generate a random alphanumeric string.
200
345
  #
201
346
  # The argument _n_ specifies the length, in characters, of the alphanumeric
202
347
  # string to be generated.
348
+ # The argument _chars_ specifies the character list which the result is
349
+ # consist of.
203
350
  #
204
351
  # If _n_ is not specified or is nil, 16 is assumed.
205
352
  # It may be larger in the future.
206
353
  #
207
- # The result may contain A-Z, a-z and 0-9.
354
+ # The result may contain A-Z, a-z and 0-9, unless _chars_ is specified.
208
355
  #
209
356
  # require 'random/formatter'
210
357
  #
211
- # prng.alphanumeric #=> "2BuBuLf3WfSKyQbR"
358
+ # Random.alphanumeric #=> "2BuBuLf3WfSKyQbR"
359
+ # # or
360
+ # prng = Random.new
212
361
  # prng.alphanumeric(10) #=> "i6K93NdqiH"
213
- def alphanumeric(n=nil)
362
+ #
363
+ # Random.alphanumeric(4, chars: [*"0".."9"]) #=> "2952"
364
+ # # or
365
+ # prng = Random.new
366
+ # prng.alphanumeric(10, chars: [*"!".."/"]) #=> ",.,++%/''."
367
+ def alphanumeric(n = nil, chars: ALPHANUMERIC)
214
368
  n = 16 if n.nil?
215
- choose(ALPHANUMERIC, n)
369
+ choose(chars, n)
216
370
  end
217
371
  end
data/lib/securerandom.rb CHANGED
@@ -39,6 +39,9 @@ require 'random/formatter'
39
39
  # +NotImplementedError+ is raised.
40
40
 
41
41
  module SecureRandom
42
+
43
+ VERSION = "0.3.0"
44
+
42
45
  class << self
43
46
  def bytes(n)
44
47
  return gen_random(n)
@@ -47,17 +50,6 @@ module SecureRandom
47
50
  private
48
51
 
49
52
  def gen_random_openssl(n)
50
- @pid = 0 unless defined?(@pid)
51
- pid = $$
52
- unless @pid == pid
53
- now = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
54
- OpenSSL::Random.random_add([now, @pid, pid].join(""), 0.0)
55
- seed = Random.urandom(16)
56
- if (seed)
57
- OpenSSL::Random.random_add(seed, 16)
58
- end
59
- @pid = pid
60
- end
61
53
  return OpenSSL::Random.random_bytes(n)
62
54
  end
63
55
 
data/securerandom.gemspec CHANGED
@@ -1,6 +1,13 @@
1
+ name = File.basename(__FILE__, ".gemspec")
2
+ version = ["lib", Array.new(name.count("-")+1).join("/")].find do |dir|
3
+ break File.foreach(File.join(__dir__, dir, "#{name.tr('-', '/')}.rb")) do |line|
4
+ /^\s*VERSION\s*=\s*"(.*)"/ =~ line and break $1
5
+ end rescue nil
6
+ end
7
+
1
8
  Gem::Specification.new do |spec|
2
- spec.name = "securerandom"
3
- spec.version = "0.2.1"
9
+ spec.name = name
10
+ spec.version = version
4
11
  spec.authors = ["Tanaka Akira"]
5
12
  spec.email = ["akr@fsij.org"]
6
13
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: securerandom
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tanaka Akira
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-05 00:00:00.000000000 Z
11
+ date: 2023-11-07 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Interface for secure random number generator.
14
14
  email:
@@ -52,7 +52,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
52
  - !ruby/object:Gem::Version
53
53
  version: '0'
54
54
  requirements: []
55
- rubygems_version: 3.4.0.dev
55
+ rubygems_version: 3.5.0.dev
56
56
  signing_key:
57
57
  specification_version: 4
58
58
  summary: Interface for secure random number generator.