opensecret 0.0.988 → 0.0.9925
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/README.md +56 -159
- data/bin/opensecret +2 -2
- data/bin/ops +17 -2
- data/lib/extension/string.rb +14 -16
- data/lib/{interpreter.rb → interprete.rb} +53 -29
- data/lib/keytools/binary.map.rb +49 -0
- data/lib/keytools/kdf.api.rb +249 -0
- data/lib/keytools/kdf.bcrypt.rb +64 -29
- data/lib/keytools/kdf.pbkdf2.rb +92 -83
- data/lib/keytools/kdf.scrypt.rb +190 -0
- data/lib/keytools/key.64.rb +326 -0
- data/lib/keytools/key.algo.rb +109 -0
- data/lib/keytools/key.api.rb +1281 -0
- data/lib/keytools/key.db.rb +265 -0
- data/lib/keytools/{key.module.rb → key.docs.rb} +55 -0
- data/lib/keytools/key.error.rb +110 -0
- data/lib/keytools/key.id.rb +271 -0
- data/lib/keytools/key.iv.rb +107 -0
- data/lib/keytools/key.local.rb +265 -0
- data/lib/keytools/key.mach.rb +248 -0
- data/lib/keytools/key.now.rb +402 -0
- data/lib/keytools/key.pair.rb +259 -0
- data/lib/keytools/key.pass.rb +120 -0
- data/lib/keytools/key.rb +428 -298
- data/lib/keytools/keydebug.txt +295 -0
- data/lib/logging/gem.logging.rb +3 -3
- data/lib/modules/cryptology/collect.rb +20 -0
- data/lib/session/require.gem.rb +1 -1
- data/lib/usecase/cmd.rb +417 -0
- data/lib/usecase/id.rb +36 -0
- data/lib/usecase/import.rb +174 -0
- data/lib/usecase/init.rb +78 -0
- data/lib/usecase/login.rb +70 -0
- data/lib/usecase/logout.rb +30 -0
- data/lib/usecase/open.rb +126 -0
- data/lib/{interprete → usecase}/put.rb +100 -47
- data/lib/usecase/read.rb +89 -0
- data/lib/{interprete → usecase}/safe.rb +0 -0
- data/lib/{interprete → usecase}/set.rb +0 -0
- data/lib/usecase/token.rb +111 -0
- data/lib/{interprete → usecase}/use.rb +0 -0
- data/lib/version.rb +1 -1
- data/opensecret.gemspec +4 -3
- metadata +39 -33
- data/lib/exception/cli.error.rb +0 -53
- data/lib/exception/errors/cli.errors.rb +0 -31
- data/lib/interprete/begin.rb +0 -232
- data/lib/interprete/cmd.rb +0 -621
- data/lib/interprete/export.rb +0 -163
- data/lib/interprete/init.rb +0 -205
- data/lib/interprete/key.rb +0 -119
- data/lib/interprete/open.rb +0 -148
- data/lib/interprete/seal.rb +0 -129
- data/lib/keytools/digester.rb +0 -245
- data/lib/keytools/key.data.rb +0 -227
- data/lib/keytools/key.derivation.rb +0 -341
- data/lib/modules/mappers/collateral.rb +0 -282
- data/lib/modules/mappers/envelope.rb +0 -127
- data/lib/modules/mappers/settings.rb +0 -170
- data/lib/notepad/scratch.pad.rb +0 -224
- data/lib/store-commands.txt +0 -180
data/lib/keytools/key.data.rb
DELETED
@@ -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
|