aboutsummaryrefslogtreecommitdiffstats
path: root/railties/lib/rails_generator/secret_key_generator.rb
diff options
context:
space:
mode:
Diffstat (limited to 'railties/lib/rails_generator/secret_key_generator.rb')
-rw-r--r--railties/lib/rails_generator/secret_key_generator.rb160
1 files changed, 160 insertions, 0 deletions
diff --git a/railties/lib/rails_generator/secret_key_generator.rb b/railties/lib/rails_generator/secret_key_generator.rb
new file mode 100644
index 0000000000..f488f96f32
--- /dev/null
+++ b/railties/lib/rails_generator/secret_key_generator.rb
@@ -0,0 +1,160 @@
+# 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)"
+
+module Rails
+ 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
+ return OpenSSL::Digest::SHA512.new(data).hexdigest
+ 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.exists?('/dev/urandom')
+ end
+
+ def self.supports_openssl?
+ begin
+ require 'openssl'
+ true
+ rescue LoadError
+ false
+ end
+ end
+
+ def self.supports_prng?
+ true
+ end
+ end
+end