opensecret 0.0.988 → 0.0.9925

Sign up to get free protection for your applications and to get access to all the features.
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