opensecret 0.0.988 → 0.0.9925

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 (62) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +56 -159
  3. data/bin/opensecret +2 -2
  4. data/bin/ops +17 -2
  5. data/lib/extension/string.rb +14 -16
  6. data/lib/{interpreter.rb → interprete.rb} +53 -29
  7. data/lib/keytools/binary.map.rb +49 -0
  8. data/lib/keytools/kdf.api.rb +249 -0
  9. data/lib/keytools/kdf.bcrypt.rb +64 -29
  10. data/lib/keytools/kdf.pbkdf2.rb +92 -83
  11. data/lib/keytools/kdf.scrypt.rb +190 -0
  12. data/lib/keytools/key.64.rb +326 -0
  13. data/lib/keytools/key.algo.rb +109 -0
  14. data/lib/keytools/key.api.rb +1281 -0
  15. data/lib/keytools/key.db.rb +265 -0
  16. data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
  17. data/lib/keytools/key.error.rb +110 -0
  18. data/lib/keytools/key.id.rb +271 -0
  19. data/lib/keytools/key.iv.rb +107 -0
  20. data/lib/keytools/key.local.rb +265 -0
  21. data/lib/keytools/key.mach.rb +248 -0
  22. data/lib/keytools/key.now.rb +402 -0
  23. data/lib/keytools/key.pair.rb +259 -0
  24. data/lib/keytools/key.pass.rb +120 -0
  25. data/lib/keytools/key.rb +428 -298
  26. data/lib/keytools/keydebug.txt +295 -0
  27. data/lib/logging/gem.logging.rb +3 -3
  28. data/lib/modules/cryptology/collect.rb +20 -0
  29. data/lib/session/require.gem.rb +1 -1
  30. data/lib/usecase/cmd.rb +417 -0
  31. data/lib/usecase/id.rb +36 -0
  32. data/lib/usecase/import.rb +174 -0
  33. data/lib/usecase/init.rb +78 -0
  34. data/lib/usecase/login.rb +70 -0
  35. data/lib/usecase/logout.rb +30 -0
  36. data/lib/usecase/open.rb +126 -0
  37. data/lib/{interprete → usecase}/put.rb +100 -47
  38. data/lib/usecase/read.rb +89 -0
  39. data/lib/{interprete → usecase}/safe.rb +0 -0
  40. data/lib/{interprete → usecase}/set.rb +0 -0
  41. data/lib/usecase/token.rb +111 -0
  42. data/lib/{interprete → usecase}/use.rb +0 -0
  43. data/lib/version.rb +1 -1
  44. data/opensecret.gemspec +4 -3
  45. metadata +39 -33
  46. data/lib/exception/cli.error.rb +0 -53
  47. data/lib/exception/errors/cli.errors.rb +0 -31
  48. data/lib/interprete/begin.rb +0 -232
  49. data/lib/interprete/cmd.rb +0 -621
  50. data/lib/interprete/export.rb +0 -163
  51. data/lib/interprete/init.rb +0 -205
  52. data/lib/interprete/key.rb +0 -119
  53. data/lib/interprete/open.rb +0 -148
  54. data/lib/interprete/seal.rb +0 -129
  55. data/lib/keytools/digester.rb +0 -245
  56. data/lib/keytools/key.data.rb +0 -227
  57. data/lib/keytools/key.derivation.rb +0 -341
  58. data/lib/modules/mappers/collateral.rb +0 -282
  59. data/lib/modules/mappers/envelope.rb +0 -127
  60. data/lib/modules/mappers/settings.rb +0 -170
  61. data/lib/notepad/scratch.pad.rb +0 -224
  62. data/lib/store-commands.txt +0 -180
@@ -1,227 +0,0 @@
1
- #!/usr/bin/ruby
2
- # coding: utf-8
3
-
4
- module OpenKey
5
-
6
- require 'inifile'
7
-
8
- # This is a key-value store backed by unencrypted (plain-text) permanent
9
- # file-system storage in INI format.
10
- #
11
- # == Key-Value Pair Groupings
12
- #
13
- # The key-value pairs can be collated into a
14
- #
15
- # - custom group with a name specified to methods {read} and {write}
16
- # - default group that is accessible via the methods {get} and {put}
17
- #
18
- # The name given to the default group can be specified to the constructor.
19
- # If none is provided the aptly named "default" is used.
20
-
21
-
22
- # An OpenSession dictionary is a <b>2D (two dimensional) hash</b> data
23
- # structure backed by an encrypted file.
24
- #
25
- # It supports operations to <b>read from</b> and <b>write to</b> a known
26
- # filepath and given the correct symmetric encryption key it will
27
- #
28
- # - decrypt <b>after reading from</b> the file and
29
- # - encrypt <b>before writing to</b> the file
30
- #
31
- # This dictionary extends {Hash} in order to deliver on its core key value
32
- # storage and retrieve use cases. Extend this dictionary and provide
33
- # context specific methods through constants to read and write context
34
- # specific data.
35
- #
36
- # == The <em>Current</em> Dictionary Section
37
- #
38
- # This KeyData is <b>two-dimensional</b> so all key-value pairs are stored
39
- # under the auspices of a section.
40
- #
41
- # The KeyData can track the <b>current section</b> for you and all data
42
- # exchanges can occur in lieu of a single section if you so wish by using
43
- # the provided {put} and {get} methods.
44
- #
45
- # To employ section management functionality you should pass in a current
46
- # <b>section id</b> when creating the dictionary.
47
- #
48
- # @example
49
- # To use the dictionary in the raw (unextended) format you create
50
- # write and read it like this.
51
- #
52
- # ----------------------------------------------------------------------
53
- #
54
- # my_dictionary = KeyData.create( "/path/to/backing/file" )
55
- #
56
- # my_dictionary["user23"] = {}
57
- # my_dictionary["user23"]["Name"] = "Joe Bloggs"
58
- # my_dictionary["user23"]["Email"] = "joebloggs@example.com"
59
- # my_dictionary["user23"]["Phone"] = "+44 07342 800080"
60
- #
61
- # my_dictionary.write( "crypt-key-1234-wxyz" )
62
- #
63
- # ----------------------------------------------------------------------
64
- #
65
- # my_dictionary = KeyData.create( "/path/to/backing/file", "crypt-key-1234-wxyz" )
66
- # puts my_dictionary.has_key? "user23" # => true
67
- # puts my_dictionary["user23"].length # => 3
68
- # puts my_dictionary["user23"]["Email"] # => "joebloggs@example.com"
69
- #
70
- # ----------------------------------------------------------------------
71
- class KeyData
72
-
73
-
74
- # Initialize the key value store and auto write a time stamp that
75
- # has nano-second accuracy with a key whose name is gleened from
76
- # the constant {KeyData::INIT_TIME_STAMP_NAME}.
77
- #
78
- # The path to the backing INI file is gleened from the first
79
- # backing file path parameter.
80
- #
81
- # @param backing_file_path [String]
82
- # the expected location of the file-backed key-value store.
83
- # If the folder and/or file do not exist the folder is created
84
- # and then the file is created along with the time stamps.
85
- #
86
- # @param the_default_group [String]
87
- # the name of the default group. If none is presented this value
88
- # will default to the aptly named "default".
89
- def initialize backing_file_path, the_reference
90
-
91
- @file_path = backing_file_path
92
- @reference = the_reference
93
-
94
- create_dir_if_necessary
95
- put_stamps_if_necessary
96
-
97
- end
98
-
99
-
100
- # Stash the setting directive and its value into the configuration file
101
- # using the default settings group.
102
- #
103
- # The default settings group is resolved via {Collateral::CONTEXT_NAME}
104
- #
105
- # @param key_name [String] the name of the key whose value is to be written
106
- # @param key_value [String] the data item value of the key specified
107
- def put key_name, key_value
108
- write @reference, key_name, key_value
109
- end
110
-
111
-
112
- # Stash the setting directive and its value into the configuration file
113
- # using the default settings group.
114
- #
115
- # The default settings group is resolved via {Collateral::CONTEXT_NAME}
116
- #
117
- # @param key_name [String] the name of the key whose value is to be written
118
- # @return [String]
119
- # return the value of the configuration directive in the default group
120
- def get key_name
121
- read @reference, key_name
122
- end
123
-
124
-
125
- # Write the key/value pair in the parameter into this key/value store's
126
- # base file-system backing INI file.
127
- #
128
- # This method assumes the existence of the backing configuration file at
129
- # the @file_path instance variable that was set during initialization.
130
- #
131
- # Observable value is the written key/value pair within the specified
132
- # section. The alternate flows are
133
- #
134
- # - if the section does not exist it is created
135
- # - if the section and key exist the value is inserted or overwritten
136
- #
137
- # @param section_name [String] name grouping the section of config values
138
- # @param key [String] the key name of config directive to be written into the file
139
- # @param value [String] value of the config directive to be written into the file
140
- #
141
- def write section_name, key, value
142
-
143
- config_map = IniFile.new( :filename => @file_path, :encoding => 'UTF-8' )
144
- config_map = IniFile.load( @file_path ) if File.file? @file_path
145
- config_map[section_name][key] = value
146
- config_map.write
147
-
148
- end
149
-
150
-
151
- # Given the configuration key name and the context name, get the
152
- # corresponding key value from the configuration file whose path
153
- # is acquired using the {self#get_filepath} method.
154
- #
155
- # @param key_name [String] the key whose value is to be retrieved
156
- #
157
- # @return [String] the value configured for the parameter key
158
- #
159
- # @raise ArgumentError for any one of a long list of reasons that
160
- # cause the key value to not be retrieved. This can range from
161
- # non-existent directories and files, non readable files, incorrect
162
- # configurations right down to missing keys or even missing values.
163
- def read section_name, key_name
164
-
165
- raise ArgumentError.new "No configuration file found => [ #{@file_path} ]" unless File.exists? @file_path
166
-
167
- the_text = File.read @file_path
168
- raise ArgumentError.new "Configuration file is empty => [ #{@file_path} ]" if the_text.empty?
169
-
170
- the_data = IniFile.load @file_path
171
- key_exists = the_data[ section_name ].has_key?( key_name )
172
- raise ArgumentError.new "Key [#{key_name}] not found in section [#{section_name}] => #{the_data.to_s}" unless key_exists
173
-
174
- rawvalue = the_data[section_name][key_name]
175
- raise ArgumentError.new "Empty value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if rawvalue.empty?
176
-
177
- keyvalue = rawvalue.chomp.strip
178
- raise ArgumentError.new "Whitespace value 4 key [#{section_name}][#{key_name}] => #{the_data.to_s}" if keyvalue.empty?
179
-
180
- return keyvalue
181
-
182
- end
183
-
184
-
185
- # Get the time stamp that was written to the key-value store at
186
- # the point it was first initialized and then subsequently written
187
- # out (serialized) onto the file-system.
188
- #
189
- # The time stamp returned marks the first time this key-value store
190
- # was conceived by a use case actor and subsequently serialized.
191
- #
192
- # @return [String]
193
- # the string time stamp denoting the first time this key-value
194
- # store was first initialized and then subsequently written out
195
- # (serialized) onto the file-system.
196
- def time_stamp
197
- return get INIT_TIME_STAMP_NAME
198
- end
199
-
200
-
201
-
202
- private
203
-
204
-
205
-
206
- def create_dir_if_necessary
207
-
208
- config_directory = File.dirname @file_path
209
- return if (File.exist? config_directory) && (File.directory? config_directory)
210
- FileUtils.mkdir_p config_directory
211
-
212
- end
213
-
214
-
215
- def put_stamps_if_necessary
216
-
217
- return if File.file? @file_path
218
-
219
- put INIT_TIME_STAMP_NAME, OpenSession::Stamp.yyjjj_hhmm_ss_nanosec
220
-
221
- end
222
-
223
-
224
- end
225
-
226
-
227
- end
@@ -1,341 +0,0 @@
1
- #!/usr/bin/ruby
2
- # coding: utf-8
3
-
4
- module OpenKey
5
-
6
-
7
- # The OpenKey underlying security strategy is to lock a master index file
8
- # with a <b>symmetric encryption key</b> that is based on two randomly generated
9
- # and amalgamated <b>55 and 45 character keys</b> and then to lock that key
10
- # <b>(and only that key)</b> with a 256 bit symmetric encryption key derived from
11
- # a human password and generated by at least two cryptographic workhorses known
12
- # as <b>key derivation functions</b>.
13
- #
14
- # Random powerful keys are derived are seeded with 55 random bytes and
15
- # then fed through the master key generator and its two key derivation
16
- # functions (BCrypt and PBKDF2).
17
- #
18
- # == What Does the Master Encryption Key Generator Do?
19
- #
20
- # This class sits at the core of implementing that strategy and works to produce
21
- # 256 bit encryption key derived from a human password which is then minced by
22
- # two best of breed key derivation functions (BCrypt and PBKDF2).
23
- #
24
- # BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
25
- # whose modus operandi is to convert <b>low entropy</b> human generated passwords
26
- # into a high entropy key that is computationally infeasible to acquire via brute
27
- # force.
28
- #
29
- # == How to Create the Encryption Key
30
- #
31
- # To create a high entropy encryption key this method takes the first
32
- # 168 bits from the 186 bit BCrypt key produced by {BCryptKeyGen} and
33
- # the first 96 bits from the 132 bit PBKDF2 key produced inside the
34
- # {Pbkdf2KeyGen} class and amalgamates them to produce a 264 bit key.
35
- #
36
- # The 264 bit key is then digested to produce a 256bit encryption key.
37
- class KeyDerivation
38
-
39
-
40
- # BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
41
- # whose modus operandi is to convert <b>low entropy</b> human generated passwords
42
- # into a high entropy key that is computationally infeasible to acquire via brute
43
- # force.
44
- BCRYPT_SALT_KEY_NAME = "bcrypt.salt"
45
-
46
-
47
- # BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
48
- # whose modus operandi is to convert <b>low entropy</b> human generated passwords
49
- # into a high entropy key that is computationally infeasible to acquire via brute
50
- # force.
51
- PBKDF2_SALT_KEY_NAME = "pbkdf2.salt"
52
-
53
-
54
- # To create a high entropy encryption key the first 168 bits from the
55
- # 186 bit BCrypt key produced by {BCryptKeyGen} is sliced off and used
56
- # as the lead part of the generated key.
57
- BCRYPT_KEY_CONTRIBUTION_SIZE = 168
58
-
59
-
60
- # The first 96 bits from the 132 bit PBKDF2 key produced inside the
61
- # {Pbkdf2KeyGen} class is amalgamated to the BCrypt 168 bit key to produce
62
- # a 264 bit key.
63
- PBKDF2_KEY_CONTRIBUTION_SIZE = 96
64
-
65
-
66
- AMALGAM_KEY_RAW_BIT_SIZE = BCRYPT_KEY_CONTRIBUTION_SIZE + PBKDF2_KEY_CONTRIBUTION_SIZE
67
-
68
- AMALGAM_KEY_SIX_BIT_COUNT = AMALGAM_KEY_RAW_BIT_SIZE / 6
69
-
70
- AMALGAM_KEY_EIGHT_BYTE_COUNT = AMALGAM_KEY_RAW_BIT_SIZE / 8
71
-
72
-
73
- # To acquire a <b>machine generated symmetric encryption key</b> pass
74
- # in a {Key} initialized with {Key.from_random_bytes} and this method
75
- # will digest it for extra security and produce a gold standard 256 bit
76
- # encryption key ready to use with the AES256 algorithm.
77
- #
78
- # Do not use the {Key.from_random_bytes} as an encryption key, instead
79
- # <b>encrypt and then persist</b> the key if you will need to decrypt the
80
- # cipher text at some future date.
81
- #
82
- # <b>The 48 Bytes map to 64 Base64 Characters</b>
83
- #
84
- # To re-acquire the key for decryption, <b>read and unencrypt</b> the
85
- # <b>64 base64 characters</b> with <b>Key.from_base64</b> and then pass
86
- # it again to this method to <b>re-acquire</b> the original symmetric
87
- # encryption/decryption key.
88
- #
89
- # | -------- | ------------ | -------------------------------- |
90
- # | Bits | Bytes | Base64 |
91
- # | -------- | ------------ | -------------------------------- |
92
- # | 384 Bits | is 48 bytes | and 64 characters |
93
- # | 256 Bits | 32 precisely | 43 Chars (42 + 4 remainder bits) |
94
- # | -------- | ------------ | -------------------------------- |
95
- #
96
- # For <b>simplicity's sake</b>, try to employ a <b>bit length</b> with
97
- # a bit count that is a <b>multiple of both 6 and 8</b>. This method mashes
98
- # up the raw key and provides you with a powerful 256 bit key.
99
- #
100
- # @param the_key [OpenKey::Key]
101
- # use <b>Key.from_random_bytes</b> to create a seed whose bit length
102
- # is a <b>multiple of <em>both 6 and 8</em></b>. This method will mash
103
- # up the raw key, thus provisioning a powerful 256 bit encryption key.
104
- #
105
- # @return [OpenKey::Key]
106
- # the raw key will be mashed up and this method will faithfully return
107
- # a powerful 256 bit encryption key.
108
- def self.from_key the_key
109
- return Digest::SHA256.digest( Digest::SHA384.digest( the_key.to_binary_bytes ) )
110
- end
111
-
112
-
113
-
114
- # This method generates a 256 bit symmetric encryption key derived from
115
- # a human password and passed through two cryptographic workhorses
116
- # (BCrypt and PBKDF2), the best of breed <b>key derivation functions</b>.
117
- #
118
- # == BCrypt and the PBKDF2 Cryptographic Algorithms
119
- #
120
- # BCrypt (Blowfish) and PBKDF2 are the leading <b>key derivation functions</b>
121
- # that exists to convert <b>low entropy</b> human generated passwords into a high
122
- # entropy key that is computationally infeasible to acquire through brute force.
123
- #
124
- # == Creating a High Entropy Encryption Key
125
- #
126
- # To create a high entropy encryption key this method takes the first
127
- # 168 bits from the 186 bit BCrypt key produced by {BCryptKeyGen} and
128
- # the first 96 bits from the 132 bit PBKDF2 key produced inside the
129
- # {Pbkdf2KeyGen} class and amalgamates them to produce a 264 bit key.
130
- #
131
- # Note that all four of the above numbers are divisable by six (6), for
132
- # representation with a 64 character set, and eight (8), for transport
133
- # via the byte (8 bit) protocols.
134
- #
135
- # <b>Size of BCrypt and PBKDF2 Derived Keys</b>
136
- #
137
- # ----------- | --------- | ----------------- | ----------- |
138
- # ----------- | --------- | ----------------- | ----------- |
139
- # | Algorithm | Bit Count | Base64 Chars | 8 Bit Bytes |
140
- # ----------- | --------- | ----------------- | ----------- |
141
- # | BCrypt | 168 Bits | 28 characters | 21 bytes |
142
- # | Pbkdf2 | 96 Bits | 16 characters | 12 bytes |
143
- # ----------- | --------- | ----------------- | ----------- |
144
- # | Total | 264 Bits | 44 characters | 33 bytes |
145
- # ----------- | --------- | ----------------- | ----------- |
146
- #
147
- # <b>256 Bit Encryption Key | Remove 8 Bits</b>
148
- #
149
- # The manufactured encryption key, an amalgam of the above now has
150
- # 264 bits carried by 44 Base64 characters.
151
- #
152
- # Just before it is used to encrypt vital keys, eight (8) bits are
153
- # removed from the end of the key. The key is then converted into a
154
- # powerful 32 byte (256 bit) encryption agent and is hashed by the
155
- # SHA256 digest and delivered.
156
- #
157
- # @param human_secret [String]
158
- # a robust human generated password with as much entropy as can
159
- # be mustered. Remember that 40 characters spread randomly over
160
- # the key space of about 90 characters and not relating to any
161
- # dictionary word or name is the way to generate a powerful key
162
- # that has embedded a near 100% entropy rating.
163
- #
164
- # @param dictionary [Hash]
165
- # an instantiated hash object in which we will write the salts to
166
- # be persisted and regurgitated during the regenerate process.
167
- #
168
- # @return [Key]
169
- # the 256 bit symmetric encryption key derived from a human password
170
- # and passed through two cryptographic workhorses.
171
- def self.from_password human_secret, dictionary
172
-
173
- bcrypt_salt = BCryptKeyGen.generate_salt
174
- pbkdf2_salt = Pbkdf2KeyGen.generate_salt
175
-
176
- dictionary.put( BCRYPT_SALT_KEY_NAME, bcrypt_salt )
177
- dictionary.put( PBKDF2_SALT_KEY_NAME, pbkdf2_salt )
178
-
179
- return generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
180
-
181
- end
182
-
183
-
184
- # Regenerate the viciously unretrievable nor reversable key that was
185
- # generated in the past and with the same salts that were used during
186
- # the original key derivation process.
187
- #
188
- # @param dictionary [Hash]
189
- # an instantiated and populated hash object containing the salts
190
- # which were created in the past during the generation. These are
191
- # now vital for a successful regeneration.
192
- #
193
- # @return [Key]
194
- # the 256 bit symmetric encryption key that was previously generated
195
- # from the secret and the cryptographic salts within the dictionary.
196
- def self.regenerate human_secret, dictionary
197
-
198
- bcrypt_salt = dictionary.get( BCRYPT_SALT_KEY_NAME )
199
- pbkdf2_salt = dictionary.get( PBKDF2_SALT_KEY_NAME )
200
- return generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
201
-
202
- end
203
-
204
-
205
- # Derive a <b>short term (session scoped) encryption key</b> from the
206
- # surrounding shell execution environment whilst giving two (2) important
207
- # guarantees.
208
- #
209
- # The two guarantees governing the returned key are that it is
210
- #
211
- # - <b>the same</b> whenever called within this executing shell
212
- # - <b>different</b> different when another shell is employed
213
- #
214
- # The much higher collision rate is tolerable because the key's lifetime
215
- # is only <b>as long as commands are being typed into a given shell</b>
216
- # or command prompt in the case of Windows.
217
- #
218
- # This method uses a one-way function to return a combinatorial digested
219
- # session identification string using a number of distinct parameters that
220
- # deliver the important behaviours of changing in certain circumstances
221
- # and remaining unchanged in others.
222
- #
223
- # <b>Change | When Should the key Change?</b>
224
- #
225
- # What is really important is that the <b>key changes when</b>
226
- #
227
- # - the <b>command shell</b> changes
228
- # - the workstation <b>shell user is switched</b>
229
- # - the host machine <b>workstation</b> is changed
230
- # - the user <b>SSH's</b> into another shell
231
- #
232
- # A distinct workstation is identified by the first MAC address and the
233
- # hostname of the machine.
234
- #
235
- # <b>Unchanged | When Should the Key Remain Unchanged?</b>
236
- #
237
- # Remaining <b>unchanged</b> in certain scenarios is a feature that is
238
- # just as important as changing in others. The key must remain
239
- # <b>unchanged</b> when
240
- #
241
- # - the <b>user returns to a command shell</b>
242
- # - the user exits their <b>remote SSH session</b>
243
- # - <b>sudo is used</b> to execute the commands
244
- # - the user comes back to their <b>workstation</b>
245
- # - the clock ticks into another day, month, year ...
246
- #
247
- # @return [OpenKey::Key]
248
- # a digested key suitable for short term (session scoped) use with the
249
- # guarantee that the same key will be returned whenever called from within
250
- # the same executing shell environment and a different key when not.
251
- def self.from_session
252
-
253
- require 'macaddr'
254
-
255
- # Do not change the order of this data because it is reversed and
256
- # the parent's shell ID hotly followed by the MAC address are the
257
- # most significant data points.
258
-
259
- raw_data_string = [
260
- Socket.gethostname,
261
- OpenSession::Home.instance.username,
262
- Mac.addr.to_alphanumeric,
263
- Process.ppid.to_s
264
- ].join.reverse
265
-
266
- return Digest::SHA256.digest( Digest::SHA512.digest( raw_data_string ) )
267
-
268
- end
269
-
270
-
271
-
272
- private
273
-
274
-
275
-
276
- def self.generate_from_secret_and_salts human_secret, bcrypt_salt, pbkdf2_salt
277
- bcrypt_key = OpenKey::BCryptKeyGen.generate_key( human_secret, bcrypt_salt )
278
- pbkdf2_key = OpenKey::Pbkdf2KeyGen.generate_key( human_secret.reverse, pbkdf2_salt )
279
- return merge_then_digest( bcrypt_key, pbkdf2_key )
280
-
281
- end
282
-
283
-
284
- def self.merge_then_digest bcrypt_key, pbkdf2_key
285
-
286
- assert_bcrypt_key_bit_length bcrypt_key
287
- assert_pbkdf2_key_bit_length pbkdf2_key
288
-
289
- raw_key = bcrypt_key.to_s[ 0 .. (BCRYPT_KEY_CONTRIBUTION_SIZE-1) ] + pbkdf2_key.to_s[ 0 .. (PBKDF2_KEY_CONTRIBUTION_SIZE-1) ]
290
-
291
- assert_amalgam_key_bit_length raw_key
292
- assert_amalgam_key_six_bit_count raw_key
293
- assert_amalgam_key_eight_bit_count raw_key
294
-
295
- rawbytes_key = [ raw_key.to_s ].pack("B*")
296
- digested_key = OpenSSL::Digest::SHA256.new.digest( rawbytes_key )
297
- return Key.new ( Base64.urlsafe_encode64( digested_key ) )
298
-
299
- end
300
-
301
-
302
- def self.assert_bcrypt_key_bit_length bcrypt_key
303
- bcrypt_key_bit_length = bcrypt_key.to_s.bytesize
304
- bcrypt_keysize_msg = "Expecting #{BCryptKeyGen::BCRYPT_KEY_TRANSPORT_LENGTH} not #{bcrypt_key_bit_length} bits in bcrypt key."
305
- raise RuntimeError, bcrypt_keysize_msg unless bcrypt_key_bit_length == BCryptKeyGen::BCRYPT_KEY_TRANSPORT_LENGTH
306
- end
307
-
308
-
309
- def self.assert_pbkdf2_key_bit_length pbkdf2_key
310
- pbkdf2_key_bit_length = pbkdf2_key.to_s.bytesize
311
- pbkdf2_keysize_msg = "Expecting #{Pbkdf2KeyGen::PBKDF2_KEY_TRANSPORT_LENGTH} not #{pbkdf2_key_bit_length} bits in pbkdf2 key."
312
- raise RuntimeError, pbkdf2_keysize_msg unless pbkdf2_key_bit_length == Pbkdf2KeyGen::PBKDF2_KEY_TRANSPORT_LENGTH
313
- end
314
-
315
-
316
- def self.assert_amalgam_key_bit_length amalgam_key
317
-
318
- amalgam_key_bit_length = amalgam_key.to_s.bytesize
319
- amalgam_keysize_msg = "Expecting #{AMALGAM_KEY_RAW_BIT_SIZE} not #{amalgam_key_bit_length} bits in amalgam key."
320
- raise RuntimeError, amalgam_keysize_msg unless amalgam_key_bit_length == AMALGAM_KEY_RAW_BIT_SIZE
321
- end
322
-
323
-
324
- def self.assert_amalgam_key_six_bit_count amalgam_key
325
- amalgam_key_six_bit_length = amalgam_key.to_s.bytesize / 6
326
- amalgam_key_six_bit_msg = "Expecting #{AMALGAM_KEY_SIX_BIT_COUNT} six bit blocks not #{amalgam_key_six_bit_length}."
327
- raise RuntimeError, amalgam_key_six_bit_msg unless amalgam_key_six_bit_length == AMALGAM_KEY_SIX_BIT_COUNT
328
- end
329
-
330
-
331
- def self.assert_amalgam_key_eight_bit_count amalgam_key
332
- amalgam_key_eight_bit_length = amalgam_key.to_s.bytesize / 8
333
- amalgam_key_eight_bit_msg = "Expecting #{AMALGAM_KEY_EIGHT_BYTE_COUNT} eight bit blocks not #{amalgam_key_eight_bit_length}."
334
- raise RuntimeError, amalgam_key_eight_bit_msg unless amalgam_key_eight_bit_length == AMALGAM_KEY_EIGHT_BYTE_COUNT
335
- end
336
-
337
-
338
- end
339
-
340
-
341
- end