safedb 0.01.0001

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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