diff options
author | Nobuyoshi Nakada <[email protected]> | 2021-12-05 21:53:35 +0900 |
---|---|---|
committer | Nobuyoshi Nakada <[email protected]> | 2021-12-09 20:26:44 +0900 |
commit | 12a0a89e22fbc312e4a95a7749bc153532daa855 () | |
tree | c02bab74641503e5a9cdb4ce1d2db02241e9fb43 | |
parent | 4258c8df867b497369a815e92f741aebf1469b0d (diff) |
[ruby/securerandom] Split Random::Formatter from SecureRandom [Feature #18190]
https://.com/ruby/securerandom/commit/1e57277b9e
Notes: Merged: https://.com/ruby/ruby/pull/5237
-rw-r--r-- | lib/random/formatter.rb | 215 | ||||
-rw-r--r-- | lib/securerandom.rb | 233 | ||||
-rw-r--r-- | test/ruby/test_random_formatter.rb | 123 | ||||
-rw-r--r-- | test/test_securerandom.rb | 102 |
4 files changed, 346 insertions, 327 deletions
@@ -0,0 +1,215 @@ @@ -1,6 +1,8 @@ # -*- coding: us-ascii -*- # frozen_string_literal: true # == Secure random number generator interface. # # This library is an interface to secure random number generators which are @@ -33,37 +35,8 @@ # These methods are usable as class methods of SecureRandom such as # +SecureRandom.hex+. # -# === Examples -# -# Generate random hexadecimal strings: -# -# require 'securerandom' -# -# SecureRandom.hex(10) #=> "52750b30ffbc7de3b362" -# SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559" -# SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23" -# -# Generate random base64 strings: -# -# SecureRandom.base64(10) #=> "EcmTPZwWRAozdA==" -# SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg==" -# SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8" -# -# Generate random binary strings: -# -# SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301" -# SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" -# -# Generate alphanumeric strings: -# -# SecureRandom.alphanumeric(10) #=> "S8baxMJnPl" -# SecureRandom.alphanumeric(10) #=> "aOxAg8BAJe" -# -# Generate UUIDs: -# -# SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" -# SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" -# module SecureRandom class << self @@ -116,202 +89,4 @@ module SecureRandom end end -module Random::Formatter - - # SecureRandom.random_bytes generates a random binary string. - # - # The argument _n_ specifies the length of the result string. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in future. - # - # The result may contain any byte: "\x00" - "\xff". - # - # require 'securerandom' - # - # SecureRandom.random_bytes #=> "\xD8\\\xE0\xF4\r\xB2\xFC*WM\xFF\x83\x18\xF45\xB6" - # SecureRandom.random_bytes #=> "m\xDC\xFC/\a\x00Uf\xB2\xB2P\xBD\xFF6S\x97" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - def random_bytes(n=nil) - n = n ? n.to_int : 16 - gen_random(n) - end - - # SecureRandom.hex generates a random hexadecimal string. - # - # The argument _n_ specifies the length, in bytes, of the random number to be generated. - # The length of the resulting hexadecimal string is twice of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain 0-9 and a-f. - # - # require 'securerandom' - # - # SecureRandom.hex #=> "eb693ec8252cd630102fd0d0fb7c3485" - # SecureRandom.hex #=> "91dc3bfb4de5b11d029d376634589b61" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - def hex(n=nil) - random_bytes(n).unpack("H*")[0] - end - - # SecureRandom.base64 generates a random base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z, 0-9, "+", "/" and "=". - # - # require 'securerandom' - # - # SecureRandom.base64 #=> "/2BuBuLf3+WfSKyQbRcc/A==" - # SecureRandom.base64 #=> "6BbW0pxO0YENxn38HMUbcQ==" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - # - # See RFC 3548 for the definition of base64. - def base64(n=nil) - [random_bytes(n)].pack("m0") - end - - # SecureRandom.urlsafe_base64 generates a random URL-safe base64 string. - # - # The argument _n_ specifies the length, in bytes, of the random number - # to be generated. The length of the result string is about 4/3 of _n_. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The boolean argument _padding_ specifies the padding. - # If it is false or nil, padding is not generated. - # Otherwise padding is generated. - # By default, padding is not generated because "=" may be used as a URL delimiter. - # - # The result may contain A-Z, a-z, 0-9, "-" and "_". - # "=" is also used if _padding_ is true. - # - # require 'securerandom' - # - # SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg" - # SecureRandom.urlsafe_base64 #=> "UZLdOkzop70Ddx-IJR0ABg" - # - # SecureRandom.urlsafe_base64(nil, true) #=> "i0XQ-7gglIsHGV2_BNPrdQ==" - # SecureRandom.urlsafe_base64(nil, true) #=> "-M8rLhr7JEpJlqFGUMmOxg==" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - # - # See RFC 3548 for the definition of URL-safe base64. - def urlsafe_base64(n=nil, padding=false) - s = [random_bytes(n)].pack("m0") - s.tr!("+/", "-_") - s.delete!("=") unless padding - s - end - - # SecureRandom.uuid generates a random v4 UUID (Universally Unique IDentifier). - # - # require 'securerandom' - # - # SecureRandom.uuid #=> "2d931510-d99f-494a-8c67-87feb05e1594" - # SecureRandom.uuid #=> "bad85eb9-0713-4da7-8d36-07a8e4b00eab" - # SecureRandom.uuid #=> "62936e70-1815-439b-bf89-8492855a7e6b" - # - # The version 4 UUID is purely random (except the version). - # It doesn't contain meaningful information such as MAC addresses, timestamps, etc. - # - # The result contains 122 random bits (15.25 random bytes). - # - # See RFC 4122 for details of UUID. - # - def uuid - ary = random_bytes(16).unpack("NnnnnN") - ary[2] = (ary[2] & 0x0fff) | 0x4000 - ary[3] = (ary[3] & 0x3fff) | 0x8000 - "%08x-%04x-%04x-%04x-%04x%08x" % ary - end - - private def gen_random(n) - self.bytes(n) - end - - # SecureRandom.choose generates a string that randomly draws from a - # source array of characters. - # - # The argument _source_ specifies the array of characters from which - # to generate the string. - # The argument _n_ specifies the length, in characters, of the string to be - # generated. - # - # The result may contain whatever characters are in the source array. - # - # require 'securerandom' - # - # SecureRandom.choose([*'l'..'r'], 16) #=> "lmrqpoonmmlqlron" - # SecureRandom.choose([*'0'..'9'], 5) #=> "27309" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - private def choose(source, n) - size = source.size - m = 1 - limit = size - while limit * size <= 0x100000000 - limit *= size - m += 1 - end - result = ''.dup - while m <= n - rs = random_number(limit) - is = rs.digits(size) - (m-is.length).times { is << 0 } - result << source.values_at(*is).join('') - n -= m - end - if 0 < n - rs = random_number(limit) - is = rs.digits(size) - if is.length < n - (n-is.length).times { is << 0 } - else - is.pop while n < is.length - end - result.concat source.values_at(*is).join('') - end - result - end - - ALPHANUMERIC = [*'A'..'Z', *'a'..'z', *'0'..'9'] - # SecureRandom.alphanumeric generates a random alphanumeric string. - # - # The argument _n_ specifies the length, in characters, of the alphanumeric - # string to be generated. - # - # If _n_ is not specified or is nil, 16 is assumed. - # It may be larger in the future. - # - # The result may contain A-Z, a-z and 0-9. - # - # require 'securerandom' - # - # SecureRandom.alphanumeric #=> "2BuBuLf3WfSKyQbR" - # SecureRandom.alphanumeric(10) #=> "i6K93NdqiH" - # - # If a secure random number generator is not available, - # +NotImplementedError+ is raised. - def alphanumeric(n=nil) - n = 16 if n.nil? - choose(ALPHANUMERIC, n) - end -end - SecureRandom.extend(Random::Formatter) @@ -0,0 +1,123 @@ @@ -1,21 +1,17 @@ # frozen_string_literal: false require 'test/unit' require 'securerandom' # This testcase does NOT aim to test cryptographically strongness and randomness. class TestSecureRandom < Test::Unit::TestCase def setup @it = SecureRandom end - def test_s_random_bytes - assert_equal(16, @it.random_bytes.size) - assert_equal(Encoding::ASCII_8BIT, @it.random_bytes.encoding) - 65.times do |idx| - assert_equal(idx, @it.random_bytes(idx).size) - end - end - # This test took 2 minutes on my machine. # And 65536 times loop could not be enough for forcing PID recycle. if false @@ -69,96 +65,6 @@ if false end end - def test_s_hex - s = @it.hex - assert_equal(16 * 2, s.size) - assert_match(/\A\h+\z/, s) - 33.times do |idx| - s = @it.hex(idx) - assert_equal(idx * 2, s.size) - assert_match(/\A\h*\z/, s) - end - end - - def test_hex_encoding - assert_equal(Encoding::US_ASCII, @it.hex.encoding) - end - - def test_s_base64 - assert_equal(16, @it.base64.unpack('m*')[0].size) - 17.times do |idx| - assert_equal(idx, @it.base64(idx).unpack('m*')[0].size) - end - end - - def test_s_urlsafe_base64 - safe = /[\n+\/]/ - 65.times do |idx| - assert_not_match(safe, @it.urlsafe_base64(idx)) - end - # base64 can include unsafe byte - assert((0..10000).any? {|idx| safe =~ @it.base64(idx)}, "None of base64(0..10000) is url-safe") - end - - def test_s_random_number_float - 101.times do - v = @it.random_number - assert_in_range(0.0...1.0, v) - end - end - - def test_s_random_number_float_by_zero - 101.times do - v = @it.random_number(0) - assert_in_range(0.0...1.0, v) - end - end - - def test_s_random_number_int - 101.times do |idx| - next if idx.zero? - v = @it.random_number(idx) - assert_in_range(0...idx, v) - end - end - - def test_s_random_number_not_default - msg = "SecureRandom#random_number should not be affected by srand" - seed = srand(0) - x = @it.random_number(1000) - 10.times do|i| - srand(0) - return unless @it.random_number(1000) == x - end - srand(0) - assert_not_equal(x, @it.random_number(1000), msg) - ensure - srand(seed) if seed - end - - def test_uuid - uuid = @it.uuid - assert_equal(36, uuid.size) - - # Check time_hi_and_version and clock_seq_hi_res bits (RFC 4122 4.4) - assert_equal('4', uuid[14]) - assert_include(%w'8 9 a b', uuid[19]) - - assert_match(/\A\h{8}-\h{4}-\h{4}-\h{4}-\h{12}\z/, uuid) - end - - def test_alphanumeric - 65.times do |n| - an = @it.alphanumeric(n) - assert_match(/\A[0-9a-zA-Z]*\z/, an) - assert_equal(n, an.length) - end - end - - def assert_in_range(range, result, mesg = nil) - assert(range.cover?(result), build_message(mesg, "Expected #{result} to be in #{range}")) - end - def test_with_openssl begin require 'openssl' |