safedb 0.01.0001

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.yardopts +3 -0
  4. data/Gemfile +10 -0
  5. data/LICENSE +21 -0
  6. data/README.md +793 -0
  7. data/Rakefile +16 -0
  8. data/bin/safe +5 -0
  9. data/lib/configs/README.md +58 -0
  10. data/lib/extension/array.rb +162 -0
  11. data/lib/extension/dir.rb +35 -0
  12. data/lib/extension/file.rb +123 -0
  13. data/lib/extension/hash.rb +33 -0
  14. data/lib/extension/string.rb +572 -0
  15. data/lib/factbase/facts.safedb.net.ini +38 -0
  16. data/lib/interprete.rb +462 -0
  17. data/lib/keytools/PRODUCE_RAND_SEQ_USING_DEV_URANDOM.txt +0 -0
  18. data/lib/keytools/kdf.api.rb +243 -0
  19. data/lib/keytools/kdf.bcrypt.rb +265 -0
  20. data/lib/keytools/kdf.pbkdf2.rb +262 -0
  21. data/lib/keytools/kdf.scrypt.rb +190 -0
  22. data/lib/keytools/key.64.rb +326 -0
  23. data/lib/keytools/key.algo.rb +109 -0
  24. data/lib/keytools/key.api.rb +1391 -0
  25. data/lib/keytools/key.db.rb +330 -0
  26. data/lib/keytools/key.docs.rb +195 -0
  27. data/lib/keytools/key.error.rb +110 -0
  28. data/lib/keytools/key.id.rb +271 -0
  29. data/lib/keytools/key.ident.rb +243 -0
  30. data/lib/keytools/key.iv.rb +107 -0
  31. data/lib/keytools/key.local.rb +259 -0
  32. data/lib/keytools/key.now.rb +402 -0
  33. data/lib/keytools/key.pair.rb +259 -0
  34. data/lib/keytools/key.pass.rb +120 -0
  35. data/lib/keytools/key.rb +585 -0
  36. data/lib/logging/gem.logging.rb +132 -0
  37. data/lib/modules/README.md +43 -0
  38. data/lib/modules/cryptology/aes-256.rb +154 -0
  39. data/lib/modules/cryptology/amalgam.rb +70 -0
  40. data/lib/modules/cryptology/blowfish.rb +130 -0
  41. data/lib/modules/cryptology/cipher.rb +207 -0
  42. data/lib/modules/cryptology/collect.rb +138 -0
  43. data/lib/modules/cryptology/crypt.io.rb +225 -0
  44. data/lib/modules/cryptology/engineer.rb +99 -0
  45. data/lib/modules/mappers/dictionary.rb +288 -0
  46. data/lib/modules/storage/coldstore.rb +186 -0
  47. data/lib/modules/storage/git.store.rb +399 -0
  48. data/lib/session/fact.finder.rb +334 -0
  49. data/lib/session/require.gem.rb +112 -0
  50. data/lib/session/time.stamp.rb +340 -0
  51. data/lib/session/user.home.rb +49 -0
  52. data/lib/usecase/cmd.rb +487 -0
  53. data/lib/usecase/config/README.md +57 -0
  54. data/lib/usecase/docker/README.md +146 -0
  55. data/lib/usecase/docker/docker.rb +49 -0
  56. data/lib/usecase/edit/README.md +43 -0
  57. data/lib/usecase/edit/delete.rb +46 -0
  58. data/lib/usecase/export.rb +40 -0
  59. data/lib/usecase/files/README.md +37 -0
  60. data/lib/usecase/files/eject.rb +56 -0
  61. data/lib/usecase/files/file_me.rb +78 -0
  62. data/lib/usecase/files/read.rb +169 -0
  63. data/lib/usecase/files/write.rb +89 -0
  64. data/lib/usecase/goto.rb +57 -0
  65. data/lib/usecase/id.rb +36 -0
  66. data/lib/usecase/import.rb +157 -0
  67. data/lib/usecase/init.rb +63 -0
  68. data/lib/usecase/jenkins/README.md +146 -0
  69. data/lib/usecase/jenkins/jenkins.rb +208 -0
  70. data/lib/usecase/login.rb +71 -0
  71. data/lib/usecase/logout.rb +28 -0
  72. data/lib/usecase/open.rb +71 -0
  73. data/lib/usecase/print.rb +40 -0
  74. data/lib/usecase/put.rb +81 -0
  75. data/lib/usecase/set.rb +44 -0
  76. data/lib/usecase/show.rb +138 -0
  77. data/lib/usecase/terraform/README.md +91 -0
  78. data/lib/usecase/terraform/terraform.rb +121 -0
  79. data/lib/usecase/token.rb +35 -0
  80. data/lib/usecase/update/README.md +55 -0
  81. data/lib/usecase/update/rename.rb +180 -0
  82. data/lib/usecase/use.rb +41 -0
  83. data/lib/usecase/verse.rb +20 -0
  84. data/lib/usecase/view.rb +71 -0
  85. data/lib/usecase/vpn/README.md +150 -0
  86. data/lib/usecase/vpn/vpn.ini +31 -0
  87. data/lib/usecase/vpn/vpn.rb +54 -0
  88. data/lib/version.rb +3 -0
  89. data/safedb.gemspec +34 -0
  90. 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