safedb 0.01.0001
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.yardopts +3 -0
- data/Gemfile +10 -0
- data/LICENSE +21 -0
- data/README.md +793 -0
- data/Rakefile +16 -0
- data/bin/safe +5 -0
- data/lib/configs/README.md +58 -0
- data/lib/extension/array.rb +162 -0
- data/lib/extension/dir.rb +35 -0
- data/lib/extension/file.rb +123 -0
- data/lib/extension/hash.rb +33 -0
- data/lib/extension/string.rb +572 -0
- data/lib/factbase/facts.safedb.net.ini +38 -0
- data/lib/interprete.rb +462 -0
- data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
- data/lib/keytools/kdf.api.rb +243 -0
- data/lib/keytools/kdf.bcrypt.rb +265 -0
- data/lib/keytools/kdf.pbkdf2.rb +262 -0
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1391 -0
- data/lib/keytools/key.db.rb +330 -0
- data/lib/keytools/key.docs.rb +195 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.ident.rb +243 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +259 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +585 -0
- data/lib/logging/gem.logging.rb +132 -0
- data/lib/modules/README.md +43 -0
- data/lib/modules/cryptology/aes-256.rb +154 -0
- data/lib/modules/cryptology/amalgam.rb +70 -0
- data/lib/modules/cryptology/blowfish.rb +130 -0
- data/lib/modules/cryptology/cipher.rb +207 -0
- data/lib/modules/cryptology/collect.rb +138 -0
- data/lib/modules/cryptology/crypt.io.rb +225 -0
- data/lib/modules/cryptology/engineer.rb +99 -0
- data/lib/modules/mappers/dictionary.rb +288 -0
- data/lib/modules/storage/coldstore.rb +186 -0
- data/lib/modules/storage/git.store.rb +399 -0
- data/lib/session/fact.finder.rb +334 -0
- data/lib/session/require.gem.rb +112 -0
- data/lib/session/time.stamp.rb +340 -0
- data/lib/session/user.home.rb +49 -0
- data/lib/usecase/cmd.rb +487 -0
- data/lib/usecase/config/README.md +57 -0
- data/lib/usecase/docker/README.md +146 -0
- data/lib/usecase/docker/docker.rb +49 -0
- data/lib/usecase/edit/README.md +43 -0
- data/lib/usecase/edit/delete.rb +46 -0
- data/lib/usecase/export.rb +40 -0
- data/lib/usecase/files/README.md +37 -0
- data/lib/usecase/files/eject.rb +56 -0
- data/lib/usecase/files/file_me.rb +78 -0
- data/lib/usecase/files/read.rb +169 -0
- data/lib/usecase/files/write.rb +89 -0
- data/lib/usecase/goto.rb +57 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +157 -0
- data/lib/usecase/init.rb +63 -0
- data/lib/usecase/jenkins/README.md +146 -0
- data/lib/usecase/jenkins/jenkins.rb +208 -0
- data/lib/usecase/login.rb +71 -0
- data/lib/usecase/logout.rb +28 -0
- data/lib/usecase/open.rb +71 -0
- data/lib/usecase/print.rb +40 -0
- data/lib/usecase/put.rb +81 -0
- data/lib/usecase/set.rb +44 -0
- data/lib/usecase/show.rb +138 -0
- data/lib/usecase/terraform/README.md +91 -0
- data/lib/usecase/terraform/terraform.rb +121 -0
- data/lib/usecase/token.rb +35 -0
- data/lib/usecase/update/README.md +55 -0
- data/lib/usecase/update/rename.rb +180 -0
- data/lib/usecase/use.rb +41 -0
- data/lib/usecase/verse.rb +20 -0
- data/lib/usecase/view.rb +71 -0
- data/lib/usecase/vpn/README.md +150 -0
- data/lib/usecase/vpn/vpn.ini +31 -0
- data/lib/usecase/vpn/vpn.rb +54 -0
- data/lib/version.rb +3 -0
- data/safedb.gemspec +34 -0
- metadata +193 -0
@@ -0,0 +1,243 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
# The SafeDb underlying security strategy is to lock a master index file
|
7
|
+
# with a <b>symmetric encryption key</b> that is based on two randomly generated
|
8
|
+
# and amalgamated <b>55 and 45 character keys</b> and then to lock that key
|
9
|
+
# <b>(and only that key)</b> with a 256 bit symmetric encryption key derived from
|
10
|
+
# a human password and generated by at least two cryptographic workhorses known
|
11
|
+
# as <b>key derivation functions</b>.
|
12
|
+
#
|
13
|
+
# Random powerful keys are derived are seeded with 55 random bytes and
|
14
|
+
# then fed through the master key generator and its two key derivation
|
15
|
+
# functions (BCrypt and PBKDF2).
|
16
|
+
#
|
17
|
+
# == What Does the Master Encryption Key Generator Do?
|
18
|
+
#
|
19
|
+
# This class sits at the core of implementing that strategy and works to produce
|
20
|
+
# 256 bit encryption key derived from a human password which is then minced by
|
21
|
+
# two best of breed key derivation functions (BCrypt and PBKDF2).
|
22
|
+
#
|
23
|
+
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
24
|
+
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
25
|
+
# into a high entropy key that is computationally infeasible to acquire via brute
|
26
|
+
# force.
|
27
|
+
#
|
28
|
+
# == How to Create the Encryption Key
|
29
|
+
#
|
30
|
+
# To create a high entropy encryption key this method takes the first
|
31
|
+
# 168 bits from the 186 bit BCrypt key and the first 96 bits from the
|
32
|
+
# 132 bit PBKDF2 key and amalgamates them to produce a 264 bit key.
|
33
|
+
#
|
34
|
+
# The 264 bit key is then digested to produce a 256bit encryption key.
|
35
|
+
class KdfApi
|
36
|
+
|
37
|
+
|
38
|
+
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
39
|
+
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
40
|
+
# into a high entropy key that is computationally infeasible to acquire via brute
|
41
|
+
# force.
|
42
|
+
BCRYPT_SALT_KEY_NAME = "bcrypt.salt"
|
43
|
+
|
44
|
+
|
45
|
+
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
46
|
+
# whose modus operandi is to convert <b>low entropy</b> human generated passwords
|
47
|
+
# into a high entropy key that is computationally infeasible to acquire via brute
|
48
|
+
# force.
|
49
|
+
PBKDF2_SALT_KEY_NAME = "pbkdf2.salt"
|
50
|
+
|
51
|
+
|
52
|
+
# To create a high entropy encryption key we use the full 180 bits
|
53
|
+
# from the returned 180 bit BCrypt key.
|
54
|
+
#
|
55
|
+
# When amalgamated with the <b>332 bits from the PBKDF2 Key</b> we
|
56
|
+
# achieve a powerful <b>union key length</b> of 512 bits.
|
57
|
+
BCRYPT_KEY_CONTRIBUTION_SIZE = 180
|
58
|
+
|
59
|
+
|
60
|
+
# The first 332 bits are used from the 384 bit key returned by the
|
61
|
+
# PBKDF2 algorithm.
|
62
|
+
#
|
63
|
+
# When amalgamated with the <b>180 bits from the BCrypt Key</b> we
|
64
|
+
# achieve a powerful <b>union key length</b> of 512 bits.
|
65
|
+
PBKDF2_KEY_CONTRIBUTION_SIZE = 332
|
66
|
+
|
67
|
+
|
68
|
+
# To create a high entropy encryption key we use the full 180 bits
|
69
|
+
# from the returned 180 bit BCrypt key and the first 332 bits from
|
70
|
+
# the 384 bit PBKDF2 key.
|
71
|
+
#
|
72
|
+
# On amalgamation, the outcome is a quality <b>union key length</b>
|
73
|
+
# of <b>512 bits</b>.
|
74
|
+
AMALGAM_KEY_RAW_BIT_SIZE = BCRYPT_KEY_CONTRIBUTION_SIZE + PBKDF2_KEY_CONTRIBUTION_SIZE
|
75
|
+
|
76
|
+
|
77
|
+
# This method generates a 256 bit symmetric encryption key by passing a
|
78
|
+
# textual human sourced secret into two <b>key derivation functions</b>,
|
79
|
+
# namely <b>BCrypt and PBKDF2</b>. BCrypt, PBKDF2 and SCrypt are today's
|
80
|
+
# <b>in form best of breed</b> cryptographic workhorses for producing a
|
81
|
+
# high entropy key from possibly weak human sourced secret text.
|
82
|
+
#
|
83
|
+
# <b>Example | Derive Key from Password</b>
|
84
|
+
#
|
85
|
+
# key_store = KeyPair.new( "/path/to/kdf-salt-data.ini" )
|
86
|
+
# key_store.use( "peter-pan" )
|
87
|
+
# human_key = KdfApi.generate_from_password( "my_s3cr3t", key_store )
|
88
|
+
#
|
89
|
+
# strong_key = Key.from_random()
|
90
|
+
# human_key.encrypt_key( strong_key, key_store )
|
91
|
+
#
|
92
|
+
# strong_key.encrypt_file "/path/to/file-to-encrypt.pdf"
|
93
|
+
# strong_key.encrypt_text "I am the text to encrypt."
|
94
|
+
#
|
95
|
+
# ---
|
96
|
+
#
|
97
|
+
# <b>Do not use the key derived from a human secret</b> to encrypt anything
|
98
|
+
# other than a <b>high entropy key</b> randomly sourced from 48 bytes.
|
99
|
+
#
|
100
|
+
# Every time the user logs in, generate (recycle), another human key and
|
101
|
+
# another strong key and discard the previously outputted cipher texts.
|
102
|
+
#
|
103
|
+
# == BCrypt and the PBKDF2 Cryptographic Algorithms
|
104
|
+
#
|
105
|
+
# BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
|
106
|
+
# that exists to convert <b>low entropy</b> human generated passwords into a high
|
107
|
+
# entropy key that is computationally infeasible to acquire through brute force.
|
108
|
+
#
|
109
|
+
# On amalgamation, the outcome is a quality <b>union key length</b>
|
110
|
+
# of <b>512 bits</b>.
|
111
|
+
#
|
112
|
+
# == Creating a High Entropy Encryption Key
|
113
|
+
#
|
114
|
+
# To create a high entropy encryption key this method takes the first
|
115
|
+
# 168 bits from the 186 bit BCrypt and the first 96 bits from the 132
|
116
|
+
# bit PBKDF2 key and amalgamates them to produce a 264 bit key.
|
117
|
+
#
|
118
|
+
# Note that all four of the above numbers are divisable by six (6), for
|
119
|
+
# representation with a 64 character set, and eight (8), for transport
|
120
|
+
# via the byte (8 bit) protocols.
|
121
|
+
#
|
122
|
+
# <b>Size of BCrypt and PBKDF2 Derived Keys</b>
|
123
|
+
#
|
124
|
+
# + --------- - --------- +
|
125
|
+
# + --------- | --------- +
|
126
|
+
# | Algorithm | Bit Count |
|
127
|
+
# ----------- | --------- |
|
128
|
+
# | BCrypt | 180 Bits |
|
129
|
+
# | Pbkdf2 | 332 Bits |
|
130
|
+
# ----------- | --------- |
|
131
|
+
# | Total | 512 Bits |
|
132
|
+
# + --------- | --------- +
|
133
|
+
# + --------- - --------- +
|
134
|
+
#
|
135
|
+
# <b>256 Bit Encryption Key | Remove 8 Bits</b>
|
136
|
+
#
|
137
|
+
# The manufactured encryption key, an amalgam of the above now has
|
138
|
+
# 264 bits carried by 44 Base64 characters.
|
139
|
+
#
|
140
|
+
# Just before it is used to encrypt vital keys, eight (8) bits are
|
141
|
+
# removed from the end of the key. The key is then converted into a
|
142
|
+
# powerful 32 byte (256 bit) encryption agent and is hashed by the
|
143
|
+
# SHA256 digest and delivered.
|
144
|
+
#
|
145
|
+
# @param human_secret [String]
|
146
|
+
# a robust human generated password with as much entropy as can
|
147
|
+
# be mustered. Remember that 40 characters spread randomly over
|
148
|
+
# the key space of about 90 characters and not relating to any
|
149
|
+
# dictionary word or name is the way to generate a powerful key
|
150
|
+
# that has embedded a near 100% entropy rating.
|
151
|
+
#
|
152
|
+
# @param key_map [KeyPair]
|
153
|
+
# The KeyPair storage service must have been initialized and a
|
154
|
+
# section specified using {KeyPair.use} thus allowing this method
|
155
|
+
# to <b>write key-value pairs</b> representing the BCrypt and
|
156
|
+
# PBKDF2 salts through the {KeyPair.set} behaviour.
|
157
|
+
#
|
158
|
+
# @return [Key]
|
159
|
+
# the 256 bit symmetric encryption key derived from a human password
|
160
|
+
# and passed through two cryptographic workhorses.
|
161
|
+
def self.generate_from_password human_secret, key_map
|
162
|
+
|
163
|
+
bcrypt_salt = KdfBCrypt.generate_bcrypt_salt
|
164
|
+
pbkdf2_salt = KeyPbkdf2.generate_pbkdf2_salt
|
165
|
+
|
166
|
+
key_map.set( BCRYPT_SALT_KEY_NAME, bcrypt_salt )
|
167
|
+
key_map.set( PBKDF2_SALT_KEY_NAME, pbkdf2_salt )
|
168
|
+
|
169
|
+
return derive_and_amalgamate( human_secret, bcrypt_salt, pbkdf2_salt )
|
170
|
+
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
# Regenerate the viciously unretrievable nor reversable key that was
|
175
|
+
# generated in the past and with the same salts that were used during
|
176
|
+
# the original key derivation process.
|
177
|
+
#
|
178
|
+
# @param key_map [Hash]
|
179
|
+
# an instantiated and populated hash object containing the salts
|
180
|
+
# which were created in the past during the generation. These are
|
181
|
+
# now vital for a successful regeneration.
|
182
|
+
#
|
183
|
+
# @return [Key]
|
184
|
+
# the 256 bit symmetric encryption key that was previously generated
|
185
|
+
# from the secret and the cryptographic salts within the key_map.
|
186
|
+
def self.regenerate_from_salts human_secret, key_map
|
187
|
+
|
188
|
+
bcrypt_salt = key_map.get( BCRYPT_SALT_KEY_NAME )
|
189
|
+
pbkdf2_salt = key_map.get( PBKDF2_SALT_KEY_NAME )
|
190
|
+
|
191
|
+
return derive_and_amalgamate( human_secret, bcrypt_salt, pbkdf2_salt )
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
|
197
|
+
private
|
198
|
+
|
199
|
+
|
200
|
+
|
201
|
+
def self.derive_and_amalgamate( human_secret, bcrypt_salt, pbkdf2_salt )
|
202
|
+
|
203
|
+
bcrypt_key = KdfBCrypt.generate_key( human_secret, bcrypt_salt )
|
204
|
+
pbkdf2_key = KeyPbkdf2.generate_key( human_secret.reverse, pbkdf2_salt )
|
205
|
+
|
206
|
+
assert_bcrypt_key_bit_length bcrypt_key
|
207
|
+
assert_pbkdf2_key_bit_length pbkdf2_key
|
208
|
+
|
209
|
+
amalgam_key = Key.new ( bcrypt_key.to_s[ 0 .. (BCRYPT_KEY_CONTRIBUTION_SIZE-1) ] + pbkdf2_key.to_s[ 0 .. (PBKDF2_KEY_CONTRIBUTION_SIZE-1) ] )
|
210
|
+
|
211
|
+
assert_amalgam_key_bit_length amalgam_key
|
212
|
+
|
213
|
+
return amalgam_key
|
214
|
+
|
215
|
+
end
|
216
|
+
|
217
|
+
|
218
|
+
def self.assert_bcrypt_key_bit_length bcrypt_key
|
219
|
+
bcrypt_key_bit_length = bcrypt_key.to_s.bytesize
|
220
|
+
bcrypt_keysize_msg = "Expecting #{KdfBCrypt::BCRYPT_KEY_EXPORT_BIT_LENGTH} not #{bcrypt_key_bit_length} bits in bcrypt key."
|
221
|
+
raise RuntimeError, bcrypt_keysize_msg unless bcrypt_key_bit_length == KdfBCrypt::BCRYPT_KEY_EXPORT_BIT_LENGTH
|
222
|
+
end
|
223
|
+
|
224
|
+
|
225
|
+
def self.assert_pbkdf2_key_bit_length pbkdf2_key
|
226
|
+
pbkdf2_key_bit_length = pbkdf2_key.to_s.bytesize
|
227
|
+
pbkdf2_keysize_msg = "Expecting #{KeyPbkdf2::PBKDF2_EXPORT_BIT_LENGTH} not #{pbkdf2_key_bit_length} bits in pbkdf2 key."
|
228
|
+
raise RuntimeError, pbkdf2_keysize_msg unless pbkdf2_key_bit_length == KeyPbkdf2::PBKDF2_EXPORT_BIT_LENGTH
|
229
|
+
end
|
230
|
+
|
231
|
+
|
232
|
+
def self.assert_amalgam_key_bit_length amalgam_key
|
233
|
+
|
234
|
+
amalgam_key_bit_length = amalgam_key.to_s.bytesize
|
235
|
+
amalgam_keysize_msg = "Expecting #{AMALGAM_KEY_RAW_BIT_SIZE} not #{amalgam_key_bit_length} bits in amalgam key."
|
236
|
+
raise RuntimeError, amalgam_keysize_msg unless amalgam_key_bit_length == AMALGAM_KEY_RAW_BIT_SIZE
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
# coding: utf-8
|
3
|
+
|
4
|
+
module SafeDb
|
5
|
+
|
6
|
+
# BCrypt is a <b>Blowfish based Key Derivation Function (KDF)</b> that exists to
|
7
|
+
# convert <b>low entropy</b> human created passwords into a high entropy key that
|
8
|
+
# is computationally infeasible to acquire through brute force.
|
9
|
+
#
|
10
|
+
# As human generated passwords have a relatively small key space, key derivation
|
11
|
+
# functions must be slow to compute with any implementation.
|
12
|
+
#
|
13
|
+
# BCrypt offers a <b>cost parameter</b> that determines (via the powers of two)
|
14
|
+
# the number of iterations performed.
|
15
|
+
#
|
16
|
+
# If the cost parameter is 12, then 4096 iterations (two to the power of 12) will
|
17
|
+
# be enacted.
|
18
|
+
#
|
19
|
+
# == A Cost of 16 is 65,536 iterations
|
20
|
+
#
|
21
|
+
# The <b>minimum cost</b> is 4 (16 iterations) and the maximum is 31.
|
22
|
+
#
|
23
|
+
# <b>A cost of 16 will result in 2^16 = 65,536 iterations</b> and will slow the
|
24
|
+
# derivation time to about a second on a powerful 2020 laptop.
|
25
|
+
#
|
26
|
+
# == BCrypt Cost Iteration Timings on an Intel i-5 Laptop
|
27
|
+
#
|
28
|
+
# The benchmark timings were incredibly consistent and
|
29
|
+
# took almost exactly twice as long for every step.
|
30
|
+
#
|
31
|
+
# An IBM ThinkPad was used to generate the timings.
|
32
|
+
#
|
33
|
+
# Memory RAM ~> 15GiB
|
34
|
+
# Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
|
35
|
+
#
|
36
|
+
# The timing results (for 2 steps) multiplied by four (4).
|
37
|
+
#
|
38
|
+
# 3.84 seconds for 2^16 (65,536) iterations
|
39
|
+
# 0.96 seconds for 2^14 (16,384) iterations
|
40
|
+
# 0.24 seconds for 2^12 ( 4,096) iterations
|
41
|
+
# 0.06 seconds for 2^10 ( 1,024) iterations
|
42
|
+
#
|
43
|
+
# A double digit iteration cost must be provided to avoid
|
44
|
+
# an in-built failure trap. The default cost is now 10.
|
45
|
+
class KdfBCrypt
|
46
|
+
|
47
|
+
require "bcrypt"
|
48
|
+
|
49
|
+
# The iteration count is determined using the powers of
|
50
|
+
# two so if the iteration integer is 12 there will be two
|
51
|
+
# to the power of 12 ( 2^12 ) giving 4096 iterations.
|
52
|
+
# The minimum number is 4 (16 iterations) and the max is 31.
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
# Configuring 16 into this directive results in
|
56
|
+
# 2^16 = 65,536 iterations
|
57
|
+
#
|
58
|
+
# == BCrypt Cost Iteration Timings on an Intel i-5 Laptop
|
59
|
+
#
|
60
|
+
# The benchmark timings were incredibly consistent and
|
61
|
+
# took almost exactly twice as long for every step.
|
62
|
+
#
|
63
|
+
# An IBM ThinkPad was used to generate the timings.
|
64
|
+
#
|
65
|
+
# Memory RAM ~> 15GiB
|
66
|
+
# Processors ~> Intel(R) Core(TM) i5-7200U CPU @ 2.50GHz
|
67
|
+
#
|
68
|
+
# The timing results (for 2 steps) multiplied by four (4).
|
69
|
+
#
|
70
|
+
# 3.84 seconds for 2^16 (65,536) iterations
|
71
|
+
# 0.96 seconds for 2^14 (16,384) iterations
|
72
|
+
# 0.24 seconds for 2^12 ( 4,096) iterations
|
73
|
+
# 0.06 seconds for 2^10 ( 1,024) iterations
|
74
|
+
#
|
75
|
+
# A double digit iteration cost must be provided to avoid
|
76
|
+
# an in-built failure trap. The default cost is now 10.
|
77
|
+
BCRYPT_ITERATION_INTEGER = 10
|
78
|
+
|
79
|
+
# The bcrypt algorithm produces a key that is 181 bits in
|
80
|
+
# length. The algorithm then converts the binary 181 bits
|
81
|
+
# into a (6-bit) Radix64 character.
|
82
|
+
#
|
83
|
+
# 181 / 6 = 30 remainder 1 (so 31 characters are needed).
|
84
|
+
BCRYPT_KEY_LENGTH = 31
|
85
|
+
|
86
|
+
# BCrypt key derivation (from text) implementations truncate
|
87
|
+
# the first 55 characters of the incoming text.
|
88
|
+
BCRYPT_MAX_IN_TEXT_LENGTH = 55
|
89
|
+
|
90
|
+
# The BCrypt algorithm produces 181 raw binary bits which is just
|
91
|
+
# one bit more than a 30 character base64 string. Hence the algorithm
|
92
|
+
# puts out 31 characters.
|
93
|
+
#
|
94
|
+
# We discard the 31st character because 5 of its 6 bits are 100%
|
95
|
+
# predictable. Thus the returned key will contribute 180 bits.
|
96
|
+
BCRYPT_KEY_EXPORT_BIT_LENGTH = 180
|
97
|
+
|
98
|
+
# The BCrypt algorithm salt string should be 22 characters
|
99
|
+
# and may include forward slashes and periods.
|
100
|
+
BCRYPT_SALT_LENGTH = 22
|
101
|
+
|
102
|
+
# BCrypt outputs a single line of text that holds the prefix
|
103
|
+
# then the Radix64 encoded salt and finally the Radix64
|
104
|
+
# encoded hash key.
|
105
|
+
#
|
106
|
+
# The prefix consists of <b>two sections</b> sandwiched within
|
107
|
+
# two dollar <b>$</b> signs at the extremeties and a third dollar
|
108
|
+
# separating them.
|
109
|
+
#
|
110
|
+
# The two sections are the
|
111
|
+
# - BCrypt algorithm <b>version number</b> (2a or 2b) and
|
112
|
+
# - a power of 2 integer defining the no. of interations
|
113
|
+
BCRYPT_OUTPUT_TEXT_PREFIX = "$2x$#{BCRYPT_ITERATION_INTEGER}$"
|
114
|
+
|
115
|
+
|
116
|
+
# Key generators should use this method to create a BCrypt salt
|
117
|
+
# string and then call the {generate_key} method passing in the
|
118
|
+
# salt together with a human generated password in order to derive
|
119
|
+
# a key.
|
120
|
+
#
|
121
|
+
# The salt can be persisted and then resubmitted in order to
|
122
|
+
# regenerate the same key in the future.
|
123
|
+
#
|
124
|
+
# For the BCrypt algorithm this method depends on the constant
|
125
|
+
# {BCRYPT_ITERATION_INTEGER} so that two to the power of the
|
126
|
+
# integer is the number of iterations.
|
127
|
+
#
|
128
|
+
# A generated salt looks like this assuming the algorithm version
|
129
|
+
# is 2a and the interation integer is 16.
|
130
|
+
#
|
131
|
+
# <b>$2a$16$nkyYKCwljFRtcif6FCXn3e</b>
|
132
|
+
#
|
133
|
+
# This method removes the $2a$16$ preamble string and stores only
|
134
|
+
# the actual salt string whose length should be 22 characters.
|
135
|
+
#
|
136
|
+
# <b>Why do BCrypt salts always end with zero, e, u or period</b>?
|
137
|
+
#
|
138
|
+
# Two <b>(2) leftover bits</b> is the short answer.
|
139
|
+
#
|
140
|
+
# This is because the salts are a random 16 bytes and must be
|
141
|
+
# stored in base64. The 16 bytes equals 128bits which when converted
|
142
|
+
# to base64 (6bits per character) results in 21 characters and only
|
143
|
+
# two leftover bits.
|
144
|
+
#
|
145
|
+
# BCrypt Salt => t4bDqoJlHbb/k7bkt4/1Ku (22 characters)
|
146
|
+
# BCrypt Salt => 9BjuJU67IG9Lz5tYUhOqeO (22 characters)
|
147
|
+
# BCrypt Salt => grz.QREI35585Y3AaCoCTe (22 characters)
|
148
|
+
# BCrypt Salt => zsxrVW2RGIltSu.AoS4E7e (22 characters)
|
149
|
+
# BCrypt Salt => dTlRJZ6ijDDVk2cFoCQHPO (22 characters)
|
150
|
+
# BCrypt Salt => S9B1azH7oD8L3.CQfxxzJO (22 characters)
|
151
|
+
# BCrypt Salt => LoZh.q3NdnTIuOmR6gHJF. (22 characters)
|
152
|
+
# BCrypt Salt => y6DKk23SmgNR863pTZ8nYe (22 characters)
|
153
|
+
# BCrypt Salt => rokdUF6tg6wHV6F0ymKFme (22 characters)
|
154
|
+
# BCrypt Salt => jrDpNgh.0OEIYaxsR7E7d. (22 characters)
|
155
|
+
#
|
156
|
+
# Don't forget BCrypt uses Radix64 (from OpenBSD). So the two (2)
|
157
|
+
# leftover bits result in 4 possible values which effectively is
|
158
|
+
#
|
159
|
+
# a period (.)
|
160
|
+
# a zero (0)
|
161
|
+
# an e (e)
|
162
|
+
# or a u (u)
|
163
|
+
#
|
164
|
+
# @return [String]
|
165
|
+
# the salt in a printable format like base64, hex or a string
|
166
|
+
# of ones and zeroes. This salt should be submitted in the exact
|
167
|
+
# same form to the {generate_key} method.
|
168
|
+
def self.generate_bcrypt_salt
|
169
|
+
|
170
|
+
full_bcrypt_salt = BCrypt::Engine.generate_salt( BCRYPT_ITERATION_INTEGER )
|
171
|
+
main_bcrypt_salt = full_bcrypt_salt[ BCRYPT_OUTPUT_TEXT_PREFIX.length .. -1 ]
|
172
|
+
keep_bcrypt_salt = "#{BCRYPT_ITERATION_INTEGER}#{main_bcrypt_salt}"
|
173
|
+
assert_bcrypt_salt( keep_bcrypt_salt )
|
174
|
+
return keep_bcrypt_salt
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
# Key generators should first use the {generate_salt} method to create
|
180
|
+
# a BCrypt salt string and then submit it to this method together with
|
181
|
+
# a human generated password in order to derive a key.
|
182
|
+
#
|
183
|
+
# The salt can be persisted and then resubmitted again to this method
|
184
|
+
# in order to regenerate the same key at any time in the future.
|
185
|
+
#
|
186
|
+
# Generate a binary key from the bcrypt password derivation function.
|
187
|
+
#
|
188
|
+
# This differs from a server side password to hash usage in that we
|
189
|
+
# are interested in the 186bit key that bcrypt produces. This method
|
190
|
+
# returns this reproducible key for use during symmetric encryption and
|
191
|
+
# decryption.
|
192
|
+
#
|
193
|
+
# @param human_secret [String]
|
194
|
+
# a robust human generated password with as much entropy as can
|
195
|
+
# be mustered. Remember that 40 characters spread randomly over
|
196
|
+
# the key space of about 90 characters and not relating to any
|
197
|
+
# dictionary word or name is the way to generate a powerful key
|
198
|
+
# that has embedded a near 100% entropy rating.
|
199
|
+
#
|
200
|
+
# @param bcrypt_salt [String]
|
201
|
+
# the salt string that has either been recently generated via the
|
202
|
+
# {generate_salt} method or read from a persistence store and
|
203
|
+
# resubmitted here (in the future) to regenerate the same key.
|
204
|
+
#
|
205
|
+
# @return [Key]
|
206
|
+
# an {SafeDb::Key} that has been initialized from the 30 RADIX64
|
207
|
+
# character output from the BCrypt algorithm.
|
208
|
+
#
|
209
|
+
# The BCrypt algorithm produces 181 raw binary bits which is just
|
210
|
+
# one bit more than a 30 character base64 string. Hence the algorithm
|
211
|
+
# puts out 31 characters.
|
212
|
+
#
|
213
|
+
# We discard the 31st character because 5 of its 6 bits are 100%
|
214
|
+
# predictable. Thus the returned key will contribute 180 bits.
|
215
|
+
def self.generate_key human_secret, bcrypt_salt
|
216
|
+
|
217
|
+
iteration_int = bcrypt_salt[ 0 .. 1 ]
|
218
|
+
bcrypt_prefix = "$2x$#{iteration_int}$"
|
219
|
+
full_salt_str = bcrypt_prefix + bcrypt_salt[ 2 .. -1 ]
|
220
|
+
|
221
|
+
assert_bcrypt_salt( bcrypt_salt )
|
222
|
+
|
223
|
+
hashed_secret = BCrypt::Engine.hash_secret( human_secret, full_salt_str )
|
224
|
+
encoded64_key = BCrypt::Password.new( hashed_secret ).to_s
|
225
|
+
key_begin_index = BCRYPT_OUTPUT_TEXT_PREFIX.length + BCRYPT_SALT_LENGTH
|
226
|
+
radix64_key_str = encoded64_key[ key_begin_index .. -1 ]
|
227
|
+
key_length_mesg = "The BCrypt key length should have #{BCRYPT_KEY_LENGTH} characters."
|
228
|
+
raise RuntimeError, key_length_mesg unless radix64_key_str.length == BCRYPT_KEY_LENGTH
|
229
|
+
chopped_radix64_key = radix64_key_str.chop()
|
230
|
+
|
231
|
+
return Key.from_radix64( chopped_radix64_key )
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
|
236
|
+
private
|
237
|
+
|
238
|
+
|
239
|
+
# ---
|
240
|
+
# --- Timings Code
|
241
|
+
# ---
|
242
|
+
# --- chopped_radix64_key = NIL
|
243
|
+
# --- require 'benchmark'
|
244
|
+
# --- timings = Benchmark.measure {
|
245
|
+
# ---
|
246
|
+
# --- -- wrapped up code block
|
247
|
+
# ---
|
248
|
+
# --- }
|
249
|
+
# ---
|
250
|
+
# --- log.info(x) { "BCrypt key generation timings ~> #{timings}" }
|
251
|
+
# ---
|
252
|
+
|
253
|
+
|
254
|
+
def self.assert_bcrypt_salt the_salt
|
255
|
+
raise RuntimeError, "bcrypt salt not expected to be nil." if the_salt.nil?
|
256
|
+
bcrypt_total_length = 2 + BCRYPT_SALT_LENGTH
|
257
|
+
salt_length_msg = "BCrypt salt #{the_salt} is #{the_salt.length} and not #{bcrypt_total_length} characters."
|
258
|
+
raise RuntimeError, salt_length_msg unless the_salt.length == bcrypt_total_length
|
259
|
+
end
|
260
|
+
|
261
|
+
|
262
|
+
end
|
263
|
+
|
264
|
+
|
265
|
+
end
|