diff options
Diffstat (limited to 'lib/securerandom.rb')
| -rw-r--r-- | lib/securerandom.rb | 202 |
1 files changed, 79 insertions, 123 deletions
diff --git a/lib/securerandom.rb b/lib/securerandom.rb index 2676c3b536..6079fdb5c4 100644 --- a/lib/securerandom.rb +++ b/lib/securerandom.rb @@ -1,146 +1,102 @@ -# = Secure random number generator interface. +# -*- coding: us-ascii -*- +# frozen_string_literal: true + +require 'random/formatter' + +# == Secure random number generator interface. +# +# This library is an interface to secure random number generators which are +# suitable for generating session keys in HTTP cookies, etc. +# +# You can use this library in your application by requiring it: # -# This library is an interface for secure random number generator which is -# suitable for generating session key in HTTP cookies, etc. +# require 'securerandom' # -# It supports following secure random number generators. +# It supports the following secure random number generators: # # * openssl # * /dev/urandom +# * Win32 # -# == Example +# SecureRandom is extended by the Random::Formatter module which +# defines the following methods: # -# # random hexadecimal string. -# p SecureRandom.hex(10) #=> "52750b30ffbc7de3b362" -# p SecureRandom.hex(10) #=> "92b15d6c8dc4beb5f559" -# p SecureRandom.hex(11) #=> "6aca1b5c58e4863e6b81b8" -# p SecureRandom.hex(12) #=> "94b2fff3e7fd9b9c391a2306" -# p SecureRandom.hex(13) #=> "39b290146bea6ce975c37cfc23" -# ... +# * alphanumeric +# * base64 +# * choose +# * gen_random +# * hex +# * rand +# * random_bytes +# * random_number +# * urlsafe_base64 +# * uuid # -# # random base64 string. -# p SecureRandom.base64(10) #=> "EcmTPZwWRAozdA==" -# p SecureRandom.base64(10) #=> "9b0nsevdwNuM/w==" -# p SecureRandom.base64(10) #=> "KO1nIU+p9DKxGg==" -# p SecureRandom.base64(11) #=> "l7XEiFja+8EKEtY=" -# p SecureRandom.base64(12) #=> "7kJSM/MzBJI+75j8" -# p SecureRandom.base64(13) #=> "vKLJ0tXBHqQOuIcSIg==" -# ... +# These methods are usable as class methods of SecureRandom such as +# +SecureRandom.hex+. # -# # random binary string. -# p SecureRandom.random_bytes(10) #=> "\016\t{\370g\310pbr\301" -# p SecureRandom.random_bytes(10) #=> "\323U\030TO\234\357\020\a\337" -# ... - -begin - require 'openssl' -rescue LoadError -end +# If a secure random number generator is not available, +# +NotImplementedError+ is raised. module SecureRandom - # SecureRandom.random_bytes generates a random binary string. - # - # The argument n specifies the length of the result string. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - def self.random_bytes(n=nil) - n ||= 16 - if defined? OpenSSL::Random + + # The version + VERSION = "0.4.1" + + class << self + # Returns a random binary string containing +size+ bytes. + # + # See Random.bytes + def bytes(n) + return gen_random(n) + end + + # Compatibility methods for Ruby 3.2, we can remove this after dropping to support Ruby 3.2 + def alphanumeric(n = nil, chars: ALPHANUMERIC) + n = 16 if n.nil? + choose(chars, n) + end if RUBY_VERSION < '3.3' + + private + + # :stopdoc: + + # Implementation using OpenSSL + def gen_random_openssl(n) return OpenSSL::Random.random_bytes(n) end - if !defined?(@has_urandom) || @has_urandom - @has_urandom = false - flags = File::RDONLY - flags |= File::NONBLOCK if defined? File::NONBLOCK - flags |= File::NOCTTY if defined? File::NOCTTY - begin - File.open("/dev/urandom", flags) {|f| - unless f.stat.chardev? - raise Errno::ENOENT - end - @has_urandom = true - ret = f.readpartial(n) - if ret.length != n - raise NotImplementedError, "Unexpected partial read from random device" - end - return ret - } - rescue Errno::ENOENT + + # Implementation using system random device + def gen_random_urandom(n) + ret = Random.urandom(n) + unless ret raise NotImplementedError, "No random device" end + unless ret.length == n + raise NotImplementedError, "Unexpected partial read from random device: only #{ret.length} for #{n} bytes" + end + ret end - raise NotImplementedError, "No random device" - end - - # SecureRandom.hex generates a random hex string. - # - # The argument n specifies the length of the random length. - # The length of the result string is twice of n. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - def self.hex(n=nil) - random_bytes(n).unpack("H*")[0] - end - # SecureRandom.base64 generates a random base64 string. - # - # The argument n specifies the length of the random length. - # The length of the result string is about 4/3 of n. - # - # If n is not specified, 16 is assumed. - # It may be larger in future. - # - # If secure random number generator is not available, - # NotImplementedError is raised. - def self.base64(n=nil) - [random_bytes(n)].pack("m*").delete("\n") - end - - # SecureRandom.random_number generates a random number. - # - # If an positive integer is given as n, - # SecureRandom.random_number returns an integer: - # 0 <= SecureRandom.random_number(n) < n. - # - # If 0 is given or an argument is not given, - # SecureRandom.random_number returns an float: - # 0.0 <= SecureRandom.random_number() < 1.0. - def self.random_number(n=0) - if 0 < n - hex = n.to_s(16) - hex = '0' + hex if (hex.length & 1) == 1 - bin = [hex].pack("H*") - mask = bin[0] - mask |= mask >> 1 - mask |= mask >> 2 - mask |= mask >> 4 + begin + # Check if Random.urandom is available + Random.urandom(1) + alias gen_random gen_random_urandom + rescue RuntimeError begin - rnd = SecureRandom.random_bytes(bin.length) - rnd[0] = (rnd[0] & mask).chr - end until rnd < bin - rnd.unpack("H*")[0].hex - else - # assumption: Float::MANT_DIG <= 64 - i64 = SecureRandom.random_bytes(8).unpack("Q")[0] - Math.ldexp(i64 >> (64-Float::MANT_DIG), -Float::MANT_DIG) + require 'openssl' + rescue NoMethodError + raise NotImplementedError, "No random device" + else + alias gen_random gen_random_openssl + end end - end - # SecureRandom.uuid generates a v4 random UUID. - def self.uuid - str = self.random_bytes(16) - str[6] = (str[6] & 0x0f) | 0x40 - str[8] = (str[8] & 0x3f) | 0x80 + # :startdoc: - ary = str.unpack("NnnnnN") - "%08x-%04x-%04x-%04x-%04x%08x" % ary + # Generate random data bytes for Random::Formatter + public :gen_random end end + +SecureRandom.extend(Random::Formatter) |
