opensecret 0.0.962 → 0.0.988
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +16 -10
- data/bin/opensecret +3 -4
- data/bin/ops +5 -0
- data/lib/extension/string.rb +114 -0
- data/lib/factbase/facts.opensecret.io.ini +9 -21
- data/lib/interprete/begin.rb +232 -0
- data/lib/interprete/cmd.rb +621 -0
- data/lib/{plugins/usecases/unlock.rb → interprete/export.rb} +25 -70
- data/lib/interprete/init.rb +205 -0
- data/lib/interprete/key.rb +119 -0
- data/lib/interprete/open.rb +148 -0
- data/lib/{plugins/usecases → interprete}/put.rb +19 -6
- data/lib/{plugins/usecases → interprete}/safe.rb +2 -1
- data/lib/{plugins/usecases/lock.rb → interprete/seal.rb} +24 -34
- data/lib/interprete/set.rb +46 -0
- data/lib/interprete/use.rb +43 -0
- data/lib/interpreter.rb +165 -0
- data/lib/keytools/binary.map.rb +245 -0
- data/lib/keytools/digester.rb +245 -0
- data/lib/keytools/doc.conversion.to.ones.and.zeroes.ruby +179 -0
- data/lib/keytools/doc.rsa.radix.binary-mapping.ruby +190 -0
- data/lib/keytools/doc.star.schema.strategy.txt +77 -0
- data/lib/keytools/doc.using.pbkdf2.kdf.ruby +95 -0
- data/lib/keytools/doc.using.pbkdf2.pkcs.ruby +266 -0
- data/lib/keytools/kdf.bcrypt.rb +180 -0
- data/lib/keytools/kdf.pbkdf2.rb +164 -0
- data/lib/keytools/key.data.rb +227 -0
- data/lib/keytools/key.derivation.rb +341 -0
- data/lib/keytools/key.module.rb +140 -0
- data/lib/keytools/key.rb +481 -0
- data/lib/logging/gem.logging.rb +1 -2
- data/lib/modules/cryptology.md +43 -0
- data/lib/{plugins/ciphers → modules/cryptology}/aes-256.rb +6 -0
- data/lib/{crypto → modules/cryptology}/amalgam.rb +6 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +118 -0
- data/lib/{plugins → modules/cryptology}/crypt.io.rb +5 -0
- data/lib/{crypto → modules/cryptology}/engineer.rb +7 -1
- data/lib/{crypto → modules/cryptology}/open.bcrypt.rb +0 -0
- data/lib/modules/mappers/collateral.rb +282 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/mappers/envelope.rb +127 -0
- data/lib/modules/mappers/settings.rb +170 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/{opensecret/plugins.io/git/git.flow.rb → modules/storage/git.store.rb} +11 -0
- data/lib/notepad/scratch.pad.rb +17 -0
- data/lib/session/fact.finder.rb +13 -0
- data/lib/session/require.gem.rb +5 -0
- data/lib/store-commands.txt +180 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +5 -6
- metadata +74 -29
- data/lib/crypto/blowfish.rb +0 -85
- data/lib/crypto/collect.rb +0 -140
- data/lib/crypto/verify.rb +0 -33
- data/lib/opensecret.rb +0 -236
- data/lib/plugins/cipher.rb +0 -203
- data/lib/plugins/ciphers/blowfish.rb +0 -126
- data/lib/plugins/coldstore.rb +0 -181
- data/lib/plugins/envelope.rb +0 -116
- data/lib/plugins/secrets.uc.rb +0 -94
- data/lib/plugins/usecase.rb +0 -239
- data/lib/plugins/usecases/init.rb +0 -145
- data/lib/plugins/usecases/open.rb +0 -108
- data/lib/session/attributes.rb +0 -279
- data/lib/session/dictionary.rb +0 -191
- data/lib/session/file.path.rb +0 -53
- 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
|