Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tags/rel_2-0-2/railties/lib/rails_generator/secret_key_generator.rb

Revision 8365, 5.5 kB (checked in by bitsweat, 2 years ago)

Ruby 1.9 compat: File.exists\? -> File.exist\? en masse. References #1689 [Pratik Naik]

Line 
1 # A class for creating random secret keys. This class will do its best to create a
2 # random secret key that's as secure as possible, using whatever methods are
3 # available on the current platform. For example:
4 #
5 #   generator = Rails::SecretKeyGenerator("some unique identifier, such as the application name")
6 #   generator.generate_secret     # => "f3f1be90053fa851... (some long string)"
7
8 module Rails
9   class SecretKeyGenerator
10     GENERATORS = [ :secure_random, :win32_api, :urandom, :openssl, :prng ].freeze
11
12     def initialize(identifier)
13       @identifier = identifier
14     end
15
16     # Generate a random secret key with the best possible method available on
17     # the current platform.
18     def generate_secret
19       generator = GENERATORS.find do |g|
20         self.class.send("supports_#{g}?")
21       end
22       send("generate_secret_with_#{generator}")
23     end
24
25     # Generate a random secret key by using the Win32 API. Raises LoadError
26     # if the current platform cannot make use of the Win32 API. Raises
27     # SystemCallError if some other error occured.
28     def generate_secret_with_win32_api
29       # Following code is based on David Garamond's GUID library for Ruby.
30       require 'Win32API'
31
32       crypt_acquire_context = Win32API.new("advapi32", "CryptAcquireContext",
33                                            'PPPII', 'L')
34       crypt_gen_random = Win32API.new("advapi32", "CryptGenRandom",
35                                       'LIP', 'L')
36       crypt_release_context = Win32API.new("advapi32", "CryptReleaseContext",
37                                          'LI', 'L')
38       prov_rsa_full       = 1
39       crypt_verifycontext = 0xF0000000
40
41       hProvStr = " " * 4
42       if crypt_acquire_context.call(hProvStr, nil, nil, prov_rsa_full,
43                                     crypt_verifycontext) == 0
44         raise SystemCallError, "CryptAcquireContext failed: #{lastWin32ErrorMessage}"
45       end
46       hProv, = hProvStr.unpack('L')
47       bytes = " " * 64
48       if crypt_gen_random.call(hProv, bytes.size, bytes) == 0
49         raise SystemCallError, "CryptGenRandom failed: #{lastWin32ErrorMessage}"
50       end
51       if crypt_release_context.call(hProv, 0) == 0
52         raise SystemCallError, "CryptReleaseContext failed: #{lastWin32ErrorMessage}"
53       end
54       bytes.unpack("H*")[0]
55     end
56
57     # Generate a random secret key with Ruby 1.9's SecureRandom module.
58     # Raises LoadError if the current Ruby version does not support
59     # SecureRandom.
60     def generate_secret_with_secure_random
61       require 'securerandom'
62       return SecureRandom.hex(64)
63     end
64
65     # Generate a random secret key with OpenSSL. If OpenSSL is not
66     # already loaded, then this method will attempt to load it.
67     # LoadError will be raised if that fails.
68     def generate_secret_with_openssl
69       require 'openssl'
70       if !File.exist?("/dev/urandom")
71         # OpenSSL transparently seeds the random number generator with
72         # data from /dev/urandom. On platforms where that is not
73         # available, such as Windows, we have to provide OpenSSL with
74         # our own seed. Unfortunately there's no way to provide a
75         # secure seed without OS support, so we'll have to do with
76         # rand() and Time.now.usec().
77         OpenSSL::Random.seed(rand(0).to_s + Time.now.usec.to_s)
78       end
79       data = OpenSSL::BN.rand(2048, -1, false).to_s
80       return OpenSSL::Digest::SHA512.new(data).hexdigest
81     end
82
83     # Generate a random secret key with /dev/urandom.
84     # Raises SystemCallError on failure.
85     def generate_secret_with_urandom
86       return File.read("/dev/urandom", 64).unpack("H*")[0]
87     end
88
89     # Generate a random secret key with Ruby's pseudo random number generator,
90     # as well as some environment information.
91     #
92     # This is the least cryptographically secure way to generate a secret key,
93     # and should be avoided whenever possible.
94     def generate_secret_with_prng
95       require 'digest/sha2'
96       sha = Digest::SHA2.new(512)
97       now = Time.now
98       sha << now.to_s
99       sha << String(now.usec)
100       sha << String(rand(0))
101       sha << String($$)
102       sha << @identifier
103       return sha.hexdigest
104     end
105
106     private
107       def lastWin32ErrorMessage
108         # Following code is based on David Garamond's GUID library for Ruby.
109         get_last_error = Win32API.new("kernel32", "GetLastError", '', 'L')
110         format_message = Win32API.new("kernel32", "FormatMessageA",
111                                       'LPLLPLPPPPPPPP', 'L')
112         format_message_ignore_inserts  = 0x00000200
113         format_message_from_system     = 0x00001000
114
115         code = get_last_error.call
116         msg = "\0" * 1024
117         len = format_message.call(format_message_ignore_inserts +
118                                   format_message_from_system, 0,
119                                   code, 0, msg, 1024, nil, nil,
120                                   nil, nil, nil, nil, nil, nil)
121         msg[0, len].tr("\r", '').chomp
122       end
123
124       def self.supports_secure_random?
125         begin
126           require 'securerandom'
127           true
128         rescue LoadError
129           false
130         end
131       end
132
133       def self.supports_win32_api?
134         return false unless RUBY_PLATFORM =~ /(:?mswin|mingw)/
135         begin
136           require 'Win32API'
137           true
138         rescue LoadError
139           false
140         end
141       end
142
143       def self.supports_urandom?
144         File.exist?('/dev/urandom')
145       end
146
147       def self.supports_openssl?
148         begin
149           require 'openssl'
150           true
151         rescue LoadError
152           false
153         end
154       end
155
156       def self.supports_prng?
157         true
158       end
159   end
160 end
Note: See TracBrowser for help on using the browser.