Class: Rails::SecretKeyGenerator

Inherits:
Object
  • Object
show all
Defined in:
lib/rails_generator/secret_key_generator.rb

Overview

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)"

Constant Summary collapse

GENERATORS =
[ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze

Instance Method Summary collapse

Constructor Details

#initialize(identifier) ⇒ SecretKeyGenerator

Returns a new instance of SecretKeyGenerator.



11
12
13
# File 'lib/rails_generator/secret_key_generator.rb', line 11

def initialize(identifier)
  @identifier = identifier
end

Instance Method Details

#generate_secretObject

Generate a random secret key with the best possible method available on the current platform.



17
18
19
20
21
22
# File 'lib/rails_generator/secret_key_generator.rb', line 17

def generate_secret
  generator = GENERATORS.find do |g|
    self.class.send("supports_#{g}?")
  end
  send("generate_secret_with_#{generator}")
end

#generate_secret_with_opensslObject

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.



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rails_generator/secret_key_generator.rb', line 67

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_secret_with_prngObject

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.



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/rails_generator/secret_key_generator.rb', line 98

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

#generate_secret_with_secure_randomObject

Generate a random secret key with Ruby 1.9’s SecureRandom module. Raises LoadError if the current Ruby version does not support SecureRandom.



59
60
61
62
# File 'lib/rails_generator/secret_key_generator.rb', line 59

def generate_secret_with_secure_random
  require 'securerandom'
  return SecureRandom.hex(64)
end

#generate_secret_with_urandomObject

Generate a random secret key with /dev/urandom. Raises SystemCallError on failure.



89
90
91
# File 'lib/rails_generator/secret_key_generator.rb', line 89

def generate_secret_with_urandom
  return File.read("/dev/urandom", 64).unpack("H*")[0]
end

#generate_secret_with_win32_apiObject

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.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/rails_generator/secret_key_generator.rb', line 27

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