opensecret 0.0.962 → 0.0.988

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -10
  3. data/bin/opensecret +3 -4
  4. data/bin/ops +5 -0
  5. data/lib/extension/string.rb +114 -0
  6. data/lib/factbase/facts.opensecret.io.ini +9 -21
  7. data/lib/interprete/begin.rb +232 -0
  8. data/lib/interprete/cmd.rb +621 -0
  9. data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
  10. data/lib/interprete/init.rb +205 -0
  11. data/lib/interprete/key.rb +119 -0
  12. data/lib/interprete/open.rb +148 -0
  13. data/lib/{plugins/usecases → interprete}/put.rb +19 -6
  14. data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
  15. data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
  16. data/lib/interprete/set.rb +46 -0
  17. data/lib/interprete/use.rb +43 -0
  18. data/lib/interpreter.rb +165 -0
  19. data/lib/keytools/binary.map.rb +245 -0
  20. data/lib/keytools/digester.rb +245 -0
  21. data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
  22. data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
  23. data/lib/keytools/doc.star.schema.strategy.txt +77 -0
  24. data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
  25. data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
  26. data/lib/keytools/kdf.bcrypt.rb +180 -0
  27. data/lib/keytools/kdf.pbkdf2.rb +164 -0
  28. data/lib/keytools/key.data.rb +227 -0
  29. data/lib/keytools/key.derivation.rb +341 -0
  30. data/lib/keytools/key.module.rb +140 -0
  31. data/lib/keytools/key.rb +481 -0
  32. data/lib/logging/gem.logging.rb +1 -2
  33. data/lib/modules/cryptology.md +43 -0
  34. data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
  35. data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
  36. data/lib/modules/cryptology/blowfish.rb +130 -0
  37. data/lib/modules/cryptology/cipher.rb +207 -0
  38. data/lib/modules/cryptology/collect.rb +118 -0
  39. data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
  40. data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
  41. data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
  42. data/lib/modules/mappers/collateral.rb +282 -0
  43. data/lib/modules/mappers/dictionary.rb +288 -0
  44. data/lib/modules/mappers/envelope.rb +127 -0
  45. data/lib/modules/mappers/settings.rb +170 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
  48. data/lib/notepad/scratch.pad.rb +17 -0
  49. data/lib/session/fact.finder.rb +13 -0
  50. data/lib/session/require.gem.rb +5 -0
  51. data/lib/store-commands.txt +180 -0
  52. data/lib/version.rb +1 -1
  53. data/opensecret.gemspec +5 -6
  54. metadata +74 -29
  55. data/lib/crypto/blowfish.rb +0 -85
  56. data/lib/crypto/collect.rb +0 -140
  57. data/lib/crypto/verify.rb +0 -33
  58. data/lib/opensecret.rb +0 -236
  59. data/lib/plugins/cipher.rb +0 -203
  60. data/lib/plugins/ciphers/blowfish.rb +0 -126
  61. data/lib/plugins/coldstore.rb +0 -181
  62. data/lib/plugins/envelope.rb +0 -116
  63. data/lib/plugins/secrets.uc.rb +0 -94
  64. data/lib/plugins/usecase.rb +0 -239
  65. data/lib/plugins/usecases/init.rb +0 -145
  66. data/lib/plugins/usecases/open.rb +0 -108
  67. data/lib/session/attributes.rb +0 -279
  68. data/lib/session/dictionary.rb +0 -191
  69. data/lib/session/file.path.rb +0 -53
  70. data/lib/session/session.rb +0 -80
@@ -0,0 +1,266 @@
1
+ # coding: utf-8
2
+
3
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
4
+ ### Creating a Key
5
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
6
+
7
+ This example creates a 2048 bit RSA keypair and writes it to the current directory.
8
+
9
+ key = OpenSSL::PKey::RSA.new 2048
10
+
11
+ open 'private_key.pem', 'w' do |io| io.write key.to_pem end
12
+ open 'public_key.pem', 'w' do |io| io.write key.public_key.to_pem end
13
+
14
+
15
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
16
+ ### Exporting a Key
17
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
18
+
19
+ Keys saved to disk without encryption are not secure as anyone who gets ahold of the key may use it unless it is encrypted. In order to securely export a key you may export it with a pass phrase.
20
+
21
+ cipher = OpenSSL::Cipher.new 'AES-128-CBC'
22
+ pass_phrase = 'my secure pass phrase goes here'
23
+
24
+ key_secure = key.export cipher, pass_phrase
25
+
26
+ open 'private.secure.pem', 'w' do |io|
27
+ io.write key_secure
28
+ end
29
+ OpenSSL::Cipher.ciphers returns a list of available ciphers.
30
+
31
+
32
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
33
+ ### Loading a Key
34
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
35
+
36
+ A key can also be loaded from a file.
37
+
38
+ key2 = OpenSSL::PKey::RSA.new File.read 'private_key.pem'
39
+ key2.public? # => true
40
+ key2.private? # => true
41
+ or
42
+
43
+ key3 = OpenSSL::PKey::RSA.new File.read 'public_key.pem'
44
+ key3.public? # => true
45
+ key3.private? # => false
46
+
47
+
48
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
49
+ ### Loading an Encrypted Key
50
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
51
+
52
+ OpenSSL will prompt you for your pass phrase when loading an encrypted key. If you will not be able to type in the pass phrase you may provide it when loading the key:
53
+
54
+ key4_pem = File.read 'private.secure.pem'
55
+ pass_phrase = 'my secure pass phrase goes here'
56
+ key4 = OpenSSL::PKey::RSA.new key4_pem, pass_phrase
57
+
58
+
59
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
60
+ ### RSA Encryption
61
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
62
+
63
+ RSA provides encryption and decryption using the public and private keys. You can use a variety of padding methods depending upon the intended use of encrypted data.
64
+
65
+
66
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
67
+ ### Encryption & Decryption
68
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
69
+
70
+ Asymmetric public/private key encryption is slow and victim to attack in cases where it is used without padding or directly to encrypt larger chunks of data. Typical use cases for RSA encryption involve wrapping a symmetric key with the public key of the recipient who would unwrap that symmetric key again using their private key. The following illustrates a simplified example of such a key transport scheme. It shouldnt be used in practice, though, standardized protocols should always be preferred.
71
+
72
+ wrapped_key = key.public_encrypt key
73
+ A symmetric key encrypted with the public key can only be decrypted with the corresponding private key of the recipient.
74
+
75
+ original_key = key.private_decrypt wrapped_key
76
+ By default PKCS#1 padding will be used, but it is also possible to use other forms of padding, see PKey::RSA for further details.
77
+
78
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
79
+ ### Signatures
80
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
81
+
82
+
83
+ Using “private_encrypt” to encrypt some data with the private key is equivalent to applying a digital signature to the data. A verifying party may validate the signature by comparing the result of decrypting the signature with “public_decrypt” to the original data. However, OpenSSL::PKey already has methods “sign” and “verify” that handle digital signatures in a standardized way - “private_encrypt” and “public_decrypt” shouldnt be used in practice.
84
+
85
+ To sign a document, a cryptographically secure hash of the document is computed first, which is then signed using the private key.
86
+
87
+ digest = OpenSSL::Digest::SHA256.new
88
+ signature = key.sign digest, document
89
+ To validate the signature, again a hash of the document is computed and the signature is decrypted using the public key. The result is then compared to the hash just computed, if they are equal the signature was valid.
90
+
91
+ digest = OpenSSL::Digest::SHA256.new
92
+ if key.verify digest, signature, document
93
+ puts 'Valid'
94
+ else
95
+ puts 'Invalid'
96
+ end
97
+
98
+
99
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
100
+ ### PBKDF2 Password-based Encryption
101
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
102
+
103
+ If supported by the underlying OpenSSL version used, Password-based Encryption should use the features of PKCS5. If not supported or if required by legacy applications, the older, less secure methods specified in RFC 2898 are also supported (see below).
104
+
105
+ PKCS5 supports PBKDF2 as it was specified in PKCS#5 v2.0. It still uses a password, a salt, and additionally a number of iterations that will slow the key derivation process down. The slower this is, the more work it requires being able to brute-force the resulting key.
106
+
107
+
108
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
109
+ ### Encryption
110
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
111
+
112
+ The strategy is to first instantiate a Cipher for encryption, and then to generate a random IV plus a key derived from the password using PBKDF2. PKCS #5 v2.0 recommends at least 8 bytes for the salt, the number of iterations largely depends on the hardware being used.
113
+
114
+ cipher = OpenSSL::Cipher.new 'AES-128-CBC'
115
+ cipher.encrypt
116
+ iv = cipher.random_iv
117
+
118
+ pwd = 'some hopefully not to easily guessable password'
119
+ salt = OpenSSL::Random.random_bytes 16
120
+ iter = 20000
121
+ key_len = cipher.key_len
122
+ digest = OpenSSL::Digest::SHA256.new
123
+
124
+ key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest)
125
+ cipher.key = key
126
+
127
+ Now encrypt the data:
128
+
129
+ encrypted = cipher.update document
130
+ encrypted << cipher.final
131
+
132
+
133
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
134
+ ### Decryption
135
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
136
+
137
+ Use the same steps as before to derive the symmetric AES key, this time setting the Cipher up for decryption.
138
+
139
+ cipher = OpenSSL::Cipher.new 'AES-128-CBC'
140
+ cipher.decrypt
141
+ cipher.iv = iv # the one generated with #random_iv
142
+
143
+ pwd = 'some hopefully not to easily guessable password'
144
+ salt = ... # the one generated above
145
+ iter = 20000
146
+ key_len = cipher.key_len
147
+ digest = OpenSSL::Digest::SHA256.new
148
+
149
+ key = OpenSSL::PKCS5.pbkdf2_hmac(pwd, salt, iter, key_len, digest)
150
+ cipher.key = key
151
+
152
+ Now decrypt the data:
153
+
154
+ decrypted = cipher.update encrypted
155
+ decrypted << cipher.final
156
+
157
+
158
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
159
+ ### PKCS #5 Password-based Encryption
160
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
161
+
162
+ PKCS #5 is a password-based encryption standard documented at RFC2898. It allows a short password or passphrase to be used to create a secure encryption key. If possible, PBKDF2 as described above should be used if the circumstances allow it.
163
+
164
+ PKCS #5 uses a Cipher, a pass phrase and a salt to generate an encryption key.
165
+
166
+ pass_phrase = 'my secure pass phrase goes here'
167
+ salt = '8 octets'
168
+
169
+
170
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
171
+ ### Encryption
172
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
173
+
174
+ First set up the cipher for encryption
175
+
176
+ encryptor = OpenSSL::Cipher.new 'AES-128-CBC'
177
+ encryptor.encrypt
178
+ encryptor.pkcs5_keyivgen pass_phrase, salt
179
+ Then pass the data you want to encrypt through
180
+
181
+ encrypted = encryptor.update 'top secret document'
182
+ encrypted << encryptor.final
183
+
184
+
185
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
186
+ ### Decryption
187
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
188
+
189
+ Use a new Cipher instance set up for decryption
190
+
191
+ decryptor = OpenSSL::Cipher.new 'AES-128-CBC'
192
+ decryptor.decrypt
193
+ decryptor.pkcs5_keyivgen pass_phrase, salt
194
+ Then pass the data you want to decrypt through
195
+
196
+ plain = decryptor.update encrypted
197
+ plain << decryptor.final
198
+
199
+
200
+
201
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
202
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
203
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
204
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
205
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
206
+
207
+
208
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
209
+ ### OpenSSL::PKCS5
210
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
211
+
212
+ Provides password-based encryption functionality based on PKCS#5. Typically used for securely deriving arbitrary length symmetric keys to be used with an OpenSSL::Cipher from passwords. Another use case is for storing passwords: Due to the ability to tweak the effort of computation by increasing the iteration count, computation can be slowed down artificially in order to render possible attacks infeasible.
213
+
214
+ PKCS5 offers support for PBKDF2 with an OpenSSL::Digest::SHA1-based HMAC, or an arbitrary Digest if the underlying version of OpenSSL already supports it (>= 0.9.4).
215
+
216
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
217
+ ### Parameters
218
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
219
+
220
+
221
+ 1 - Password - Typically an arbitrary String that represents the password to be used for deriving a key.
222
+
223
+ 2 - Salt - Prevents attacks based on dictionaries of common passwords. It is a public value that can be safely stored along with the password (e.g. if PBKDF2 is used for password storage). For maximum security, a fresh, random salt should be generated for each stored password. According to PKCS#5, a salt should be at least 8 bytes long.
224
+
225
+ 3 - Iteration Count - Allows to tweak the length that the actual computation will take. The larger the iteration count, the longer it will take.
226
+
227
+ 4 - Key Length - Specifies the length in bytes of the output that will be generated. Typically, the key length should be larger than or equal to the output length of the underlying digest function, otherwise an attacker could simply try to brute-force the key. According to PKCS#5, security is limited by the output length of the underlying digest function, i.e. security is not improved if a key length strictly larger than the digest output length is chosen. Therefore, when using PKCS5 for password storage, it suffices to store values equal to the digest output length, nothing is gained by storing larger values.
228
+
229
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
230
+ ### Generating a 128 bit key for a Cipher (e.g. AES)
231
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
232
+
233
+ pass = "secret"
234
+ salt = OpenSSL::Random.random_bytes(16)
235
+ iter = 20000
236
+ key_len = 16
237
+ key = OpenSSL::PKCS5.pbkdf2_hmac_sha1(pass, salt, iter, key_len)
238
+
239
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
240
+ ### Storing Passwords
241
+ ### @@@@@@@@@@@@@@@@@@@@@@@@@@
242
+
243
+ pass = "secret"
244
+ salt = OpenSSL::Random.random_bytes(16) #store this with the generated value
245
+ iter = 20000
246
+ digest = OpenSSL::Digest::SHA256.new
247
+ len = digest.digest_length
248
+ #the final value to be stored
249
+ value = OpenSSL::PKCS5.pbkdf2_hmac(pass, salt, iter, len, digest)
250
+ Important Note on Checking Passwords¶ ↑
251
+ When comparing passwords provided by the user with previously stored values, a common mistake made is comparing the two values using "==". Typically, "==" short-circuits on evaluation, and is therefore vulnerable to timing attacks. The proper way is to use a method that always takes the same amount of time when comparing two values, thus not leaking any information to potential attackers. To compare two values, the following could be used:
252
+
253
+ def eql_time_cmp(a, b)
254
+ unless a.length == b.length
255
+ return false
256
+ end
257
+ cmp = b.bytes.to_a
258
+ result = 0
259
+ a.bytes.each_with_index {|c,i|
260
+ result |= c ^ cmp[i]
261
+ }
262
+ result == 0
263
+ end
264
+
265
+
266
+ Please note that the premature return in case of differing lengths typically does not leak valuable information - when using PKCS#5, the length of the values to be compared is of fixed size.
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module OpenKey
5
+
6
+
7
+ # BCrypt is a <b>Blowfish based Key Derivation Function (KDF)</b> that exists to
8
+ # convert <b>low entropy</b> human created passwords into a high entropy key that
9
+ # is computationally infeasible to acquire through brute force.
10
+ #
11
+ # As human generated passwords have a relatively small key space, key derivation
12
+ # functions must be slow to compute with any implementation.
13
+ #
14
+ # BCrypt offers a <b>cost parameter</b> that determines (via the powers of two)
15
+ # the number of iterations performed.
16
+ #
17
+ # If the cost parameter is 12, then 4096 iterations (two to the power of 12) will
18
+ # be enacted.
19
+ #
20
+ # == A Cost of 16 is 65,536 iterations
21
+ #
22
+ # The <b>minimum cost</b> is 4 (16 iterations) and the maximum is 31.
23
+ #
24
+ # <b>A cost of 16 will result in 2^16 = 65,536 iterations</b>.
25
+ #
26
+ # This is a safe default and will slow the derivation time
27
+ # to about a second on a powerful 2020 laptop.
28
+ #
29
+ class BCryptKeyGen
30
+
31
+ require "bcrypt"
32
+
33
+ # The iteration count is determined using the powers of
34
+ # two so if the iteration integer is 12 there will be two
35
+ # to the power of 12 ( 2^12 ) giving 4096 iterations.
36
+ # The minimum number is 4 (16 iterations) and the max is 31.
37
+ # @example
38
+ # Configuring 16 into this directive results in
39
+ # 2^16 = 65,536 iterations
40
+ #
41
+ # This is a safe default and will slow the derivation time
42
+ # to about a second on a powerful 2020 laptop.
43
+ BCRYPT_ITERATION_INTEGER = 16
44
+
45
+ # The bcrypt algorithm produces a key that is 181 bits in
46
+ # length. The algorithm then converts the binary 181 bits
47
+ # into a (6-bit) Radix64 character.
48
+ #
49
+ # 181 / 6 = 30 remainder 1 (so 31 characters are needed).
50
+ BCRYPT_KEY_LENGTH = 31
51
+
52
+
53
+ # When the key is transported using a 64 character set where
54
+ # each character is represented by 6 bits - the BCrypt key
55
+ # expands to 186 bits rather than the original 181 bits.
56
+ #
57
+ # This expansion is because of the remainder.
58
+ #
59
+ # 181 bits divided by 6 is 30 characters plus 1 character
60
+ # for the extra bit.
61
+ #
62
+ # The 31 transported characters then appear as
63
+ # 31 times 6 which equals 186 bits.
64
+ BCRYPT_KEY_TRANSPORT_LENGTH = 186
65
+
66
+ # The bcrypt algorithm salt string should be 22 characters
67
+ # and may include forward slashes and periods.
68
+ BCRYPT_SALT_LENGTH = 22
69
+
70
+ # BCrypt outputs a single line of text that holds the prefix
71
+ # then the Radix64 encoded salt and finally the Radix64
72
+ # encoded hash key.
73
+ #
74
+ # The prefix consists of <b>two sections</b> sandwiched within
75
+ # two dollar <b>$</b> signs at the extremeties and a third dollar
76
+ # separating them.
77
+ #
78
+ # The two sections are the
79
+ # - BCrypt algorithm <b>version number</b> (2a or 2b) and
80
+ # - a power of 2 integer defining the no. of interations
81
+ BCRYPT_OUTPUT_TEXT_PREFIX = "$2a$#{BCRYPT_ITERATION_INTEGER}$"
82
+
83
+
84
+ # Key generators should use this method to create a BCrypt salt
85
+ # string and then call the {generate_key} method passing in the
86
+ # salt together with a human generated password in order to derive
87
+ # a key.
88
+ #
89
+ # The salt can be persisted and then resubmitted in order to
90
+ # regenerate the same key in the future.
91
+ #
92
+ # For the BCrypt algorithm this method depends on the constant
93
+ # {BCRYPT_ITERATION_INTEGER} so that two to the power of the
94
+ # integer is the number of iterations.
95
+ #
96
+ # A generated salt looks like this assuming the algorithm version
97
+ # is 2a and the interation integer is 16.
98
+ #
99
+ # <b>$2a$16$nkyYKCwljFRtcif6FCXn3e</b>
100
+ #
101
+ # This method removes the $2a$16$ preamble string and stores only
102
+ # the actual salt string whose length should be 22 characters.
103
+ #
104
+ # @return [String]
105
+ # the salt in a printable format like base64, hex or a string
106
+ # of ones and zeroes. This salt should be submitted in the exact
107
+ # same form to the {generate_key} method.
108
+ def self.generate_salt
109
+ the_salt_str = BCrypt::Engine.generate_salt( BCRYPT_ITERATION_INTEGER )
110
+ bcrypt_salt = the_salt_str[ BCRYPT_OUTPUT_TEXT_PREFIX.length .. -1 ]
111
+ assert_bcrypt_salt bcrypt_salt
112
+ return bcrypt_salt
113
+ end
114
+
115
+
116
+
117
+ # Key generators should first use the {generate_salt} method to create
118
+ # a BCrypt salt string and then submit it to this method together with
119
+ # a human generated password in order to derive a key.
120
+ #
121
+ # The salt can be persisted and then resubmitted again to this method
122
+ # in order to regenerate the same key at any time in the future.
123
+ #
124
+ # Generate a binary key from the bcrypt password derivation function.
125
+ #
126
+ # This differs from a server side password to hash usage in that we
127
+ # are interested in the 186bit key that bcrypt produces. This method
128
+ # returns this reproducible key for use during symmetric encryption and
129
+ # decryption.
130
+ #
131
+ # @param human_secret [String]
132
+ # a robust human generated password with as much entropy as can
133
+ # be mustered. Remember that 40 characters spread randomly over
134
+ # the key space of about 90 characters and not relating to any
135
+ # dictionary word or name is the way to generate a powerful key
136
+ # that has embedded a near 100% entropy rating.
137
+ #
138
+ # @param bcrypt_salt [String]
139
+ # the salt string that has either been recently generated via the
140
+ # {generate_salt} method or read from a persistence store and
141
+ # resubmitted here (in the future) to regenerate the same key.
142
+ #
143
+ # @return [Key]
144
+ # a key holder containing the key which can then be accessed via
145
+ # many different formats.
146
+ def self.generate_key human_secret, bcrypt_salt
147
+
148
+ hashed_secret = BCrypt::Engine.hash_secret( human_secret, to_bcrypt_salt(bcrypt_salt) )
149
+ encoded64_key = BCrypt::Password.new( hashed_secret ).to_s
150
+
151
+ key_begin_index = BCRYPT_OUTPUT_TEXT_PREFIX.length + BCRYPT_SALT_LENGTH
152
+ radix64_key_str = encoded64_key[ key_begin_index .. -1 ]
153
+ key_length_mesg = "The bcrypt key length should have #{BCRYPT_KEY_LENGTH} characters."
154
+ raise RuntimeError, key_length_mesg unless radix64_key_str.length == BCRYPT_KEY_LENGTH
155
+
156
+ return Key.new(radix64_key_str)
157
+
158
+ end
159
+
160
+
161
+
162
+ private
163
+
164
+
165
+
166
+ def self.to_bcrypt_salt the_salt
167
+ return BCRYPT_OUTPUT_TEXT_PREFIX + the_salt
168
+ end
169
+
170
+ def self.assert_bcrypt_salt the_salt
171
+ raise RuntimeError, "bcrypt salt not expected to be nil." if the_salt.nil?
172
+ salt_length_msg = "A bcrypt salt is expected to contain #{BCRYPT_SALT_LENGTH} characters."
173
+ raise RuntimeError, salt_length_msg unless the_salt.length == BCRYPT_SALT_LENGTH
174
+ end
175
+
176
+
177
+ end
178
+
179
+
180
+ end
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+
4
+ module OpenKey
5
+
6
+
7
+ # PBKDF2 is a powerful leading <b>Key Derivation Function (KDF)</b> that exists to
8
+ # convert <b>low entropy</b> human created passwords into a high entropy key that
9
+ # is computationally infeasible to acquire through brute force.
10
+ #
11
+ # As human generated passwords have a relatively small key space, key derivation
12
+ # functions must be slow to compute with any implementation.
13
+ #
14
+ # PBKDF2 offers an <b>iteration count</b> that configures the number of iterations
15
+ # performed to create the key.
16
+ #
17
+ # <b>One million (1,000,000) should be the iteration count's lower bound.</b>
18
+ class Pbkdf2KeyGen
19
+
20
+
21
+ # <b>One million iterations</b> is necessary due to the
22
+ # growth of <b>GPU driven cloud based computing</b> power
23
+ # that is curently being honed by mining BitCoin and training
24
+ # neural networks.
25
+ PBKDF2_ITERATION_COUNT = 1000000
26
+
27
+
28
+ # The current output key length needed is 96 bits which is
29
+ # 12 bytes. However the algorithm's minimum byte length is
30
+ # 16 so that is what we must use.
31
+ PBKDF2_OUTPUT_KEY_LENGTH = 16
32
+
33
+
34
+ # The documented recommended salt length in bytes is in between
35
+ # 16 and 24 bytes. The setting here is at the upper bound of
36
+ # that range.
37
+ PBKDF2_SALT_LENGTH_BITS = 24 * 8
38
+
39
+
40
+ # When the key is transported using a 64 character set where
41
+ # each character is represented by 6 bits - the PBKDF2 key
42
+ # expands to 132 bits rather than the original 128 bits.
43
+ #
44
+ # This expansion is because of the remainder.
45
+ #
46
+ # 128 bits divided by 6 is 21 characters plus a remainder of two
47
+ # (2) bits which must be transported one extra base64 character.
48
+ #
49
+ # Hence the 22 transported characters are then observed to
50
+ # be 132 bits in length (22 times 6).
51
+ PBKDF2_KEY_TRANSPORT_LENGTH = 132
52
+
53
+
54
+
55
+ # Retun a random cryptographic salt generated from twenty-four
56
+ # random bytes produced by a secure random number generator. The
57
+ # returned salt is Base64 encoded.
58
+ #
59
+ # @return [String]
60
+ # a base64 encoded representation of a twenty-four (24) randomly
61
+ # and securely generated bytes.
62
+ def self.generate_salt
63
+ return Base64.urlsafe_encode64( SecureRandom.random_bytes( 24 ) )
64
+ end
65
+
66
+
67
+
68
+ # Generate a 128 bit binary key from the PBKDF2 password derivation
69
+ # function. The most important input to this function is the human
70
+ # generated key. The best responsibly sourced key with at least 95%
71
+ # entropy will contain about 40 characters spread randomly over the
72
+ # set of 95 typable characters.
73
+ #
74
+ # Aside from the human password the other inputs are
75
+ #
76
+ # - a base64 encoded randomly generated salt of 16 to 24 bytes
77
+ # - an iteration count of at least 1 million (due to GPU advances)
78
+ # - an output key length that is at least 16 bytes (128 bits)
79
+ # - a digest algorithm implementation (we use SHA512K)
80
+ #
81
+ # The {Key} returned by this method encapsulates the derived
82
+ # key of the byte (bit) length specified.
83
+ #
84
+ # @param human_secret [String]
85
+ # a robust human generated password with as much entropy as can
86
+ # be mustered. Remember that 40 characters spread randomly over
87
+ # the key space of about 95 characters and not relating to any
88
+ # dictionary word or name is the way to generate a powerful key
89
+ # that has embedded a near 100% entropy rating.
90
+ #
91
+ # @param pbkdf2_salt [String]
92
+ # the salt string that has either been recently generated via the
93
+ # {generate_salt} method or read from a persistence store and
94
+ # resubmitted here in order to regenerate the same key.
95
+ #
96
+ # @return [Key]
97
+ # a key holder containing the key which can then be accessed via
98
+ # many different formats. The {Key} returned by this method
99
+ # encapsulates the derived key with the specified byte count.
100
+ def self.generate_key human_secret, pbkdf2_salt
101
+
102
+ pbkdf2_key = OpenSSL::PKCS5.pbkdf2_hmac(
103
+ human_secret,
104
+ Base64.urlsafe_decode64( pbkdf2_salt ),
105
+ PBKDF2_ITERATION_COUNT,
106
+ PBKDF2_OUTPUT_KEY_LENGTH,
107
+ OpenSSL::Digest::SHA512.new
108
+ )
109
+
110
+ return Key.new ( Base64.urlsafe_encode64( pbkdf2_key ) )
111
+
112
+
113
+ # ----> -----------------------------------------------------
114
+ # ----> -----------------------------------------------------
115
+ # ----> ruby --version
116
+ # ----> ruby 2.3.1p112 (2016-04-26) [x86_64-linux-gnu]
117
+ # ----> -----------------------------------------------------
118
+ # ----> -----------------------------------------------------
119
+
120
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
121
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
122
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
123
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
124
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
125
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
126
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
127
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
128
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
129
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
130
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
131
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
132
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
133
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
134
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
135
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
136
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
137
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
138
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
139
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
140
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
141
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
142
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
143
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
144
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
145
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
146
+ ########### If Condition => IF is Ruby 2.4.1 or greater Use this else use one above
147
+
148
+ pbkdf2_key = OpenSSL::KDF.pbkdf2_hmac(
149
+ human_secret,
150
+ Base64.urlsafe_decode64( @crypt_salt ),
151
+ PBKDF2_ITERATION_COUNT,
152
+ PBKDF2_OUTPUT_KEY_LENGTH,
153
+ OpenSSL::Digest::SHA512.new
154
+ )
155
+
156
+ return Key.new ( Base64.urlsafe_encode64( pbkdf2_key ) )
157
+
158
+ end
159
+
160
+
161
+ end
162
+
163
+
164
+ end