mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
164 lines
5.6 KiB
Ruby
164 lines
5.6 KiB
Ruby
module Rails
|
|
# A class for creating random secret keys. This class will do its best to create a
|
|
# random secret key that's as secure as possible, using whatever methods are
|
|
# available on the current platform. For example:
|
|
#
|
|
# generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name")
|
|
# generator.generate_secret # => "f3f1be90053fa851... (some long string)"
|
|
class SecretKeyGenerator
|
|
GENERATORS = [ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze
|
|
|
|
def initialize(identifier)
|
|
@identifier = identifier
|
|
end
|
|
|
|
# Generate a random secret key with the best possible method available on
|
|
# the current platform.
|
|
def generate_secret
|
|
generator = GENERATORS.find do |g|
|
|
self.class.send("supports_#{g}?")
|
|
end
|
|
send("generate_secret_with_#{generator}")
|
|
end
|
|
|
|
# Generate a random secret key by using the Win32 API. Raises LoadError
|
|
# if the current platform cannot make use of the Win32 API. Raises
|
|
# SystemCallError if some other error occured.
|
|
def generate_secret_with_win32_api
|
|
# Following code is based on David Garamond's GUID library for Ruby.
|
|
require 'Win32API'
|
|
|
|
crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext",
|
|
'PPPII', 'L')
|
|
crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",
|
|
'LIP', 'L')
|
|
crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext",
|
|
'LI', 'L')
|
|
prov_rsa_full = 1
|
|
crypt_verifycontext = 0xF0000000
|
|
|
|
hProvStr = " " * 4
|
|
if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full,
|
|
crypt_verifycontext) == 0
|
|
raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
|
|
end
|
|
hProv, = hProvStr.unpack('L')
|
|
bytes = " " * 64
|
|
if crypt_gen_random.call(hProv, bytes.size, bytes) == 0
|
|
raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
|
|
end
|
|
if crypt_release_context.call(hProv, 0) == 0
|
|
raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}"
|
|
end
|
|
bytes.unpack("H*")[0]
|
|
end
|
|
|
|
# Generate a random secret key with Ruby 1.9's SecureRandom module.
|
|
# Raises LoadError if the current Ruby version does not support
|
|
# SecureRandom.
|
|
def generate_secret_with_secure_random
|
|
require 'securerandom'
|
|
return SecureRandom.hex(64)
|
|
end
|
|
|
|
# Generate a random secret key with OpenSSL. If OpenSSL is not
|
|
# already loaded, then this method will attempt to load it.
|
|
# LoadError will be raised if that fails.
|
|
def generate_secret_with_openssl
|
|
require 'openssl'
|
|
if !File.exist?("/dev/urandom")
|
|
# OpenSSL transparently seeds the random number generator with
|
|
# data from /dev/urandom. On platforms where that is not
|
|
# available, such as Windows, we have to provide OpenSSL with
|
|
# our own seed. Unfortunately there's no way to provide a
|
|
# secure seed without OS support, so we'll have to do with
|
|
# rand() and Time.now.usec().
|
|
OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
|
|
end
|
|
data = OpenSSL::BN.rand(2048, -1, false).to_s
|
|
|
|
if OpenSSL::OPENSSL_VERSION_NUMBER > 0x00908000
|
|
OpenSSL::Digest::SHA512.new(data).hexdigest
|
|
else
|
|
generate_secret_with_prng
|
|
end
|
|
end
|
|
|
|
# Generate a random secret key with /dev/urandom.
|
|
# Raises SystemCallError on failure.
|
|
def generate_secret_with_urandom
|
|
return File.read("/dev/urandom", 64).unpack("H*")[0]
|
|
end
|
|
|
|
# Generate a random secret key with Ruby's pseudo random number generator,
|
|
# as well as some environment information.
|
|
#
|
|
# This is the least cryptographically secure way to generate a secret key,
|
|
# and should be avoided whenever possible.
|
|
def generate_secret_with_prng
|
|
require 'digest/sha2'
|
|
sha = Digest::SHA2.new(512)
|
|
now = Time.now
|
|
sha << now.to_s
|
|
sha << String(now.usec)
|
|
sha << String(rand(0))
|
|
sha << String($$)
|
|
sha << @identifier
|
|
return sha.hexdigest
|
|
end
|
|
|
|
private
|
|
def lastWin32ErrorMessage
|
|
# Following code is based on David Garamond's GUID library for Ruby.
|
|
get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
|
|
format_message = Win32API.new("kernel32", "FormatMessageA",
|
|
'LPLLPLPPPPPPPP', 'L')
|
|
format_message_ignore_inserts = 0x00000200
|
|
format_message_from_system = 0x00001000
|
|
|
|
code = get_last_error.call
|
|
msg = "\0" * 1024
|
|
len = format_message.call(format_message_ignore_inserts +
|
|
format_message_from_system, 0,
|
|
code, 0, msg, 1024, nil, nil,
|
|
nil, nil, nil, nil, nil, nil)
|
|
msg[0, len].tr("\r", '').chomp
|
|
end
|
|
|
|
def self.supports_secure_random?
|
|
begin
|
|
require 'securerandom'
|
|
true
|
|
rescue LoadError
|
|
false
|
|
end
|
|
end
|
|
|
|
def self.supports_win32_api?
|
|
return false unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
|
|
begin
|
|
require 'Win32API'
|
|
true
|
|
rescue LoadError
|
|
false
|
|
end
|
|
end
|
|
|
|
def self.supports_urandom?
|
|
File.exist?('/dev/urandom')
|
|
end
|
|
|
|
def self.supports_openssl?
|
|
begin
|
|
require 'openssl'
|
|
true
|
|
rescue LoadError
|
|
false
|
|
end
|
|
end
|
|
|
|
def self.supports_prng?
|
|
true
|
|
end
|
|
end
|
|
end
|