opensecret 0.0.960 → 0.0.962
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/notepad/{blow.rb → scratch.pad.rb} +1 -1
- data/lib/opensecret.rb +36 -5
- data/lib/plugins/cipher.rb +100 -201
- data/lib/plugins/ciphers/aes-256.rb +85 -101
- data/lib/plugins/ciphers/blowfish.rb +2 -2
- data/lib/plugins/coldstore.rb +38 -2
- data/lib/plugins/crypt.io.rb +220 -0
- data/lib/plugins/secrets.uc.rb +44 -0
- data/lib/plugins/usecases/init.rb +9 -1
- data/lib/plugins/usecases/lock.rb +3 -3
- data/lib/plugins/usecases/open.rb +4 -4
- data/lib/plugins/usecases/put.rb +1 -1
- data/lib/plugins/usecases/unlock.rb +208 -0
- data/lib/version.rb +1 -1
- metadata +5 -10
- data/lib/opensecret/executors/crypt.keys/crypt.keys.ini +0 -26
- data/lib/opensecret/executors/crypt.keys/crypt.keys.rb +0 -68
- data/lib/opensecret/executors/decrypt/decrypt.ini +0 -64
- data/lib/opensecret/executors/decrypt/decrypt.rb +0 -49
- data/lib/opensecret/executors/encrypt/encrypt.ini +0 -55
- data/lib/opensecret/executors/encrypt/encrypt.rb +0 -82
- data/lib/using.txt +0 -247
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06aed72d992d1b8f9ae2533681c47e20d9974e05
|
4
|
+
data.tar.gz: 04b41b862706f7f98d95f78bdbc18405c0e5b80a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 993f8128c462a648fb3599326cac9ee378a17d8bbaea620aee11223a6b10584205df2c2c68b2856223befccb2a0e02207188ddbd80dfe3e0bd615c6f1d95a66f
|
7
|
+
data.tar.gz: 6684892e42493e09e13549591ef8b048ad95d0b586c4a8f1226e4d1f063fc42401241326a814c35c78699aa2a2d441fceb9f69ea9ce050c909eed090258168dd
|
data/lib/opensecret.rb
CHANGED
@@ -65,21 +65,52 @@ class CliInterpreter < Thor
|
|
65
65
|
end
|
66
66
|
|
67
67
|
|
68
|
-
# Description of the open
|
69
|
-
desc "open
|
68
|
+
# Description of the open use case command.
|
69
|
+
desc "open OUTER_PATH", "OUTER_PATH to envelope of secrets to stuff and then lock."
|
70
70
|
|
71
71
|
# Open up a conduit from which we can add, subtract, update and list secrets
|
72
72
|
# before they are committed (and pushed) into permanent locked storage.
|
73
73
|
#
|
74
|
-
# @param
|
75
|
-
def open
|
74
|
+
# @param outer_path [String] the path to USB key for storing encrypted keys
|
75
|
+
def open outer_path
|
76
76
|
|
77
77
|
open_uc = OpenSecret::Open.new
|
78
|
-
open_uc.
|
78
|
+
open_uc.outer_path = outer_path
|
79
79
|
open_uc.flow_of_events
|
80
80
|
|
81
81
|
end
|
82
82
|
|
83
|
+
# Description of the unlock use case command.
|
84
|
+
desc "unlock OUTER_PATH", "OUTER_PATH to locked secrets to open for reading or stuffing."
|
85
|
+
|
86
|
+
# If confident that command history cannot be exploited to gain the human password
|
87
|
+
# or if the agent running opensecret is itself a script, the <tt>with</tt> option can
|
88
|
+
# be used to convey the password.
|
89
|
+
option :with
|
90
|
+
|
91
|
+
# Unlock a secrets envelope at the specified outer path so that we can read, put
|
92
|
+
# and discard secrets.
|
93
|
+
#
|
94
|
+
# This use case requires the human (agent) password unless the <tt>--no-human-password</tt>
|
95
|
+
# flag was posted along with the <tt>init</tt> command.
|
96
|
+
#
|
97
|
+
# There are two ways to provide the password (for the <b><em>my/gadgets</em></b> group)
|
98
|
+
#
|
99
|
+
# - <tt>opensecret unlock my/gadgets</tt> and respond to the password prompt (or)
|
100
|
+
# - <tt>opensecret unlock my/gadgets --with="hUM4n-0pen$3cr3t"</tt>
|
101
|
+
#
|
102
|
+
# If providing the password on the command line, one must be confident that the shell's
|
103
|
+
# command history cannot be exploited to capture it.
|
104
|
+
#
|
105
|
+
# @param outer_path [String] the path to the (previously) locked secrets in frozen storage.
|
106
|
+
def unlock outer_path
|
107
|
+
|
108
|
+
unlock_uc = OpenSecret::Unlock.new
|
109
|
+
unlock_uc.outer_path = outer_path
|
110
|
+
unlock_uc.master_p4ss = options[:with] if options[:with]
|
111
|
+
unlock_uc.flow_of_events
|
112
|
+
|
113
|
+
end
|
83
114
|
|
84
115
|
# Description of the put secret command.
|
85
116
|
desc "put <secret_id> <secret_value>", "put secret like login/username into opened context."
|
data/lib/plugins/cipher.rb
CHANGED
@@ -28,6 +28,50 @@ module OpenSecret
|
|
28
28
|
# with a powerful symmetric encryption algorithm which could be any one of the
|
29
29
|
# leading ciphers such as TwoFish or the Advanced Encryption Standard (AES).
|
30
30
|
#
|
31
|
+
# == Ciphers at 3 Levels
|
32
|
+
#
|
33
|
+
# Ciphers are implemented at three distinct levels.
|
34
|
+
#
|
35
|
+
# <b>Low Level Ciphers</b>
|
36
|
+
#
|
37
|
+
# Low level ciphers are given text to encrypt and an instantiated dictionary
|
38
|
+
# in which to place the encryption parameters such as keys and initialization
|
39
|
+
# vectors (iv)s.
|
40
|
+
#
|
41
|
+
# Some more specific ciphers can handle authorization data for example the
|
42
|
+
# Galois Counter Mode (GCM) cipher.
|
43
|
+
#
|
44
|
+
# Low level ciphers know nothing about text IO nor reading and writing to
|
45
|
+
# persistence structures like files, queues and databases.
|
46
|
+
#
|
47
|
+
# <b>Mid Level Ciphers</b>
|
48
|
+
#
|
49
|
+
# Mid level ciphers talk to the low level ciphers and bring in input and output
|
50
|
+
# textual formats like OpenSecret's two-part block structures.
|
51
|
+
#
|
52
|
+
# Mid level ciphers still know nothing of persistence structures like files,
|
53
|
+
# queues and databases.
|
54
|
+
#
|
55
|
+
# <b>Use Case Level Ciphers</b>
|
56
|
+
#
|
57
|
+
# The ciphers operating at the use case level talk to mid level ciphers. They
|
58
|
+
# interact with the <b>opensecret store API</b> which brings persistence
|
59
|
+
# functions such as <b>read/write</b> as well as remoting functions such as
|
60
|
+
# <b>push/pull</b>.
|
61
|
+
#
|
62
|
+
# Use Case level ciphers interact with the latest crypt technologies due to
|
63
|
+
# interface separation. Also they talk classes implementing persistence stores
|
64
|
+
# allowing assets liek Git, S3, DropBox, simple files, SSH filesystems, Samba
|
65
|
+
# to hold locked key and material crypts.
|
66
|
+
#
|
67
|
+
# Databases stores will be introduced soon allowing opensecret to plug in and
|
68
|
+
# exploit database managers like Mongo, Hadoop, MySQL, Maria, and PostgreSQL.
|
69
|
+
#
|
70
|
+
# Plugging into DevOps orchestration platforms like Terraform, Ansible, Chef
|
71
|
+
# and Puppet will soon be available. Add this with integrations to other credential
|
72
|
+
# managers like HashiCorp's Vault, Credstash, Amazon KMS, Git Secrets, PGP,
|
73
|
+
# LastPass, KeePass and KeePassX.
|
74
|
+
#
|
31
75
|
# == How to Implement a Cipher
|
32
76
|
#
|
33
77
|
# Extend this base class to inherit lots of +unexciting+ functionality
|
@@ -40,7 +84,6 @@ module OpenSecret
|
|
40
84
|
# - handles +exceptions+ and +malicious input detection+ and incubation
|
41
85
|
# - +_performs the asymmetric encryption_+ of the cipher's symmetrically encrypted output
|
42
86
|
#
|
43
|
-
#
|
44
87
|
# == What Behaviour Must Ciphers Implement
|
45
88
|
#
|
46
89
|
# Ciphers bring the cryptographic mathematics and implementation algorithms
|
@@ -57,80 +100,10 @@ module OpenSecret
|
|
57
100
|
# do the nitty gritty of file-handling plus managing stores and paths.
|
58
101
|
class Cipher
|
59
102
|
|
60
|
-
#
|
61
|
-
#
|
62
|
-
# as a workaround. opensecret does it diferently.
|
63
|
-
#
|
64
|
-
# == Why isn't Space Padding Used?
|
65
|
-
#
|
66
|
-
# If opensecret padded plaintext (ending in one or more spaces) with
|
67
|
-
# spaces, the decrypt phase (after right stripping spaces) would return
|
68
|
-
# plain text string +shorter than the original+.
|
69
|
-
#
|
70
|
-
# == So How is Padding Done?
|
71
|
-
#
|
72
|
-
# Instead of single space padding - opensecret uses an unlikely 7 character
|
73
|
-
# padder which is repeated until the multiple is reached.
|
74
|
-
#
|
75
|
-
# <tt><-|@|-></tt>
|
76
|
-
#
|
77
|
-
# == So How is Padding Done?
|
78
|
-
#
|
79
|
-
# The +padder length must be a prime number+ or infinite loops could occur.
|
80
|
-
#
|
81
|
-
# If the padder string is likely to occur in the plain text, another
|
82
|
-
# padder (or strategy) should and could be employed.
|
83
|
-
#
|
84
|
-
TEXT_PADDER = "<-|@|->"
|
85
|
-
|
86
|
-
|
87
|
-
# An unusual string that glues together an encryption dictionary and
|
88
|
-
# a chunk of base64 encoded and encrypted ciphertext.
|
89
|
-
# The string must be unusual enough to ensure it does not occur within
|
90
|
-
# the dictionary metadata keys or values.
|
91
|
-
INNER_GLUE_STRING = "\n<-|@| < || opensecret inner crypt material axis || > |@|->\n\n"
|
92
|
-
|
93
|
-
|
94
|
-
# An unusual string that glues together the asymmetrically encrypted outer
|
95
|
-
# encryption key with the outer crypted text.
|
96
|
-
OUTER_GLUE_STRING = "\n<-|@| < || opensecret outer crypt material axis || > |@|->\n\n"
|
97
|
-
|
98
|
-
|
99
|
-
# Text header for key-value pairs hash map that will be serialized.
|
100
|
-
DICTIONARY = "dictionary"
|
101
|
-
|
102
|
-
# Name for the class of cipher employed.
|
103
|
-
DICT_CIPHER_NAME = "cipher.class"
|
104
|
-
|
105
|
-
# Name for the {Base64} encoded symmetric (lock/unlock) crypt key.
|
106
|
-
DICT_CRYPT_KEY = "encryption.key"
|
107
|
-
|
108
|
-
# Name for the {Base64} encoded plain text digest.
|
109
|
-
DICT_PLAINTEXT_DIGEST = "plaintext.digest"
|
110
|
-
|
111
|
-
# Name for the {Base64} encoded crypt material digest.
|
112
|
-
DICT_MATERIAL_DIGEST = "cipher.digest"
|
113
|
-
|
114
|
-
# Name for the plain text initialization vector (iv).
|
115
|
-
DICT_INIT_VECTOR = "crypt.init.vector"
|
116
|
-
|
117
|
-
# Name for the {Base64} encoded crypted cipher text.
|
118
|
-
DICT_CIPHER_TEXT = "cipher.text"
|
119
|
-
|
120
|
-
|
121
|
-
# The cipher constructor instantiates the encryption dictionary which
|
122
|
-
# will be collaboratively added to by the parent and child ciphers.
|
123
|
-
def initialize
|
124
|
-
|
125
|
-
@dictionary = {}
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
# Ciphers use +symmetric algorithms+ to encrypt the given text, which
|
131
|
-
# is then wrapped up along with the encryption key and other +metadata+
|
103
|
+
# Ciphers use <b>symmetric algorithms</b> to encrypt the given text, which
|
104
|
+
# is then wrapped up along with the encryption key and other <b>metadata</b>
|
132
105
|
# pertinent to the algorithm, they then encrypt this bundle with the
|
133
|
-
#
|
106
|
+
# <b>public key</b> provided and return the text that can safely be stored in
|
134
107
|
# a text file.
|
135
108
|
#
|
136
109
|
# Ciphers should never interact with the filesystem which makes them
|
@@ -142,158 +115,84 @@ module OpenSecret
|
|
142
115
|
# Every component in the pipeline bears the responsibility for nullifying
|
143
116
|
# and rejecting malicious content.
|
144
117
|
#
|
145
|
-
# @param
|
146
|
-
# public key.
|
147
|
-
#
|
118
|
+
# @param public_key [OpenSSL::PKey::RSA]
|
119
|
+
# an {OpenSSL::PKey::RSA} public key. The unique selling point of
|
120
|
+
# asymmetric encryption is it can be done without recourse to the heavily
|
121
|
+
# protected private key. Thus the encryption process can continue with
|
122
|
+
# just a public key as long as its authenticity is assured.
|
148
123
|
#
|
149
|
-
# @param payload_text [String]
|
124
|
+
# @param payload_text [String]
|
125
|
+
# plaintext (or base64 encoded) text to encrypt
|
150
126
|
#
|
151
127
|
# @return [String] doubly (symmetric and asymmetric) encrypted cipher text
|
152
|
-
def encrypt_it
|
153
|
-
|
154
|
-
crypted_payload = do_symmetric_encryption payload_text
|
128
|
+
def self.encrypt_it public_key, payload_text
|
155
129
|
|
156
|
-
|
130
|
+
crypt_data = {}
|
131
|
+
crypted_payload = Base64.encode64( Aes256.do_encrypt( crypt_data, payload_text ) )
|
132
|
+
unified_material = CryptIO.inner_crypt_serialize crypt_data, crypted_payload
|
157
133
|
|
158
|
-
unified_material = unify_hash_and_text crypted_payload
|
159
134
|
outer_crypt_key = OpenSecret::Engineer.strong_key( 128 )
|
160
|
-
crypted_cryptkey =
|
161
|
-
crypted_material = Base64.encode64(Blowfish.new.encryptor unified_material, outer_crypt_key)
|
162
|
-
locked_ciphertxt = unify_text_and_text crypted_cryptkey, crypted_material
|
163
|
-
|
164
|
-
return locked_ciphertxt
|
135
|
+
crypted_cryptkey = Base64.encode64( public_key.public_encrypt( outer_crypt_key ) )
|
165
136
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
# This method takes the textual public key lacking the private key portion
|
170
|
-
# as it is not needed, and encrypts the secret text with it.
|
171
|
-
# It returns a Base64 encoded version of they encrypted text.
|
172
|
-
#
|
173
|
-
# The public key text must be compatible with {OpenSSL::PKey::RSA} as the
|
174
|
-
# class will be instantiated using the public key text.
|
175
|
-
#
|
176
|
-
# @param public_key_text [String] the textual portion of an RSA public key
|
177
|
-
# @param secret_text [String] secret text to encrypt with the public key
|
178
|
-
#
|
179
|
-
# @return [String] a Base64 encoded version of the encrypted secret text
|
180
|
-
def do_asymmetric_encryption public_key_text, secret_text
|
137
|
+
crypted_material = Base64.encode64(Blowfish.new.encryptor unified_material, outer_crypt_key)
|
181
138
|
|
182
|
-
|
183
|
-
Base64.encode64( asymmetric_encrypt_key.public_encrypt( secret_text[0..1012] ) )
|
139
|
+
return CryptIO.outer_crypt_serialize( crypted_cryptkey, crypted_material )
|
184
140
|
|
185
141
|
end
|
186
142
|
|
187
143
|
|
188
|
-
#
|
189
|
-
#
|
190
|
-
#
|
144
|
+
# This method takes and <b><em>opensecret formatted</em></b> cipher-text block
|
145
|
+
# generated by {self.encrypt_it} and returns the original message that has effectively
|
146
|
+
# been doubly encrypted using a symmetric and asymmetric cipher. This type of
|
147
|
+
# encryption is standard best practice when serializing secrets.
|
191
148
|
#
|
192
|
-
#
|
193
|
-
#
|
194
|
-
#
|
195
|
-
# original plain text (which half the time is actually a private key).
|
149
|
+
# opensecret cipher-text blocks <b><em>look like a two(2) part bundle</em></b>
|
150
|
+
# but they are <b><em>actually a three(3) part bundle</em></b> because the second
|
151
|
+
# part is in itself an amalgam of two distinct objects, serialized as text blocks.
|
196
152
|
#
|
197
|
-
#
|
198
|
-
# reusable in API and remote store scenarios.
|
153
|
+
# <b>The 3 OpenSecret Blocks</b>
|
199
154
|
#
|
200
|
-
#
|
201
|
-
#
|
155
|
+
# Even though the incoming text <b><em>appears to contain two (2) blocks</em></b>,
|
156
|
+
# it <b><em>actually contains three (3)</em></b>.
|
202
157
|
#
|
203
|
-
#
|
204
|
-
|
205
|
-
|
206
|
-
paraphernalia = do_asymmetric_decryption private_key, cipher_text
|
207
|
-
return do_symmetric_decryption get_dictionary(paraphernalia), get_crypt(paraphernalia)
|
208
|
-
|
209
|
-
end
|
210
|
-
|
211
|
-
|
212
|
-
# Serialize and then unite a hash map and a textual chunk using
|
213
|
-
# a known but unusual separator string in a manner that protects
|
214
|
-
# the content integrity during the serialize and extraction phases.
|
215
|
-
#
|
216
|
-
# == Why an Unusual Separator String
|
158
|
+
# - a massive symmetric encryption key (locked by an asymmetric keypair)
|
159
|
+
# - a dictionary denoting the algorithm and parameters used to encrypt the 3rd block
|
160
|
+
# - the true message whose encryption is parametized by the dictionary (in 2nd block)
|
217
161
|
#
|
218
|
-
# The
|
219
|
-
#
|
220
|
-
#
|
221
|
-
# phase may not accurately return the same two entities we are employed
|
222
|
-
# to unite.
|
162
|
+
# The second and third block are only revealed by asymmetrically decrypting
|
163
|
+
# the key in the first block and using it to symmetrically decrypt what appears
|
164
|
+
# to be a unified second block.
|
223
165
|
#
|
224
|
-
#
|
166
|
+
# @param private_key [OpenSSL::PKey::RSA]
|
167
|
+
# the <b>asymmetric private key</b> whose corresponding public key was
|
168
|
+
# employed during the encryption of a super-strong 128 character symmetric
|
169
|
+
# key embalmed by the first ciphertext block.
|
225
170
|
#
|
226
|
-
#
|
227
|
-
#
|
228
|
-
#
|
171
|
+
# @param os_block_text [String]
|
172
|
+
# the locked cipher text is the opensecret formatted block which comes
|
173
|
+
# in two main chunks. First is the <b>long strong</b> symmetric encryption
|
174
|
+
# key crypted with the public key portion of the private key in the first
|
175
|
+
# parameter.
|
229
176
|
#
|
230
|
-
#
|
231
|
-
#
|
232
|
-
# - booleans
|
177
|
+
# The second chunk is the symmetrically crypted text that was locked with
|
178
|
+
# the encryption key revealed in the first chunk.
|
233
179
|
#
|
234
|
-
#
|
235
|
-
#
|
236
|
-
#
|
237
|
-
#
|
238
|
-
|
239
|
-
# @return [String] serialized and glued together result of map plus text
|
240
|
-
def unify_hash_and_text text_chunk
|
241
|
-
|
242
|
-
nil_or_empty_hash = @dictionary.nil? || @dictionary.empty?
|
243
|
-
raise ArgumentError, "Cannot unify nil or empty metadata." if nil_or_empty_hash
|
244
|
-
|
245
|
-
ini_map = IniFile.new
|
246
|
-
ini_map[ DICTIONARY ] = @dictionary
|
247
|
-
|
248
|
-
return ini_map.to_s + INNER_GLUE_STRING + text_chunk
|
249
|
-
|
250
|
-
end
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
# Using an outer divider (glue) - attach the asymmetrically encrypted outer
|
255
|
-
# encryption key with the outer encrypted text.
|
256
|
-
#
|
257
|
-
# @param crypt_material_x [String] asymmetrically encrypted (encoded) outer encryption key
|
258
|
-
# @param crypt_material_y [String] symmetrically encrypted inner metadata and payload crypt
|
259
|
-
#
|
260
|
-
# @return [String] concatenated result of the two crypt materials and divider string
|
261
|
-
def unify_text_and_text crypt_material_x, crypt_material_y
|
262
|
-
|
263
|
-
return crypt_material_x + OUTER_GLUE_STRING + crypt_material_y
|
264
|
-
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
def encrypt_usecase public_key, secret_path, stores, plain_text
|
270
|
-
|
271
|
-
key_pair = KeyPair.new
|
272
|
-
|
273
|
-
secret_bundle_crypt = encrypt_it key_pair.public_key, plain_text
|
274
|
-
stores[1].write_path( secret_path, secret_bundle_crypt )
|
275
|
-
|
276
|
-
secret_key_crypt = encrypt_it public_key, key_pair.private_key
|
277
|
-
stores[0].write_path( secret_path, secret_key_crypt )
|
278
|
-
|
279
|
-
end
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
def decrypt_usecase private_key, public_key, secret_path, stores
|
284
|
-
|
285
|
-
inner_private_key = decrypt_it( private_key, stores[0].read_path(secret_path) )
|
286
|
-
secret_text = decrypt_it( inner_private_key, stores[1].read_path(secret_path) )
|
287
|
-
|
288
|
-
encrypt_usecase public_key, secret_path, stores, secret_text
|
289
|
-
return secret_text
|
290
|
-
|
291
|
-
end
|
180
|
+
# @return [String]
|
181
|
+
# the doubly encrypted plain text that is locked by a symmetric key and
|
182
|
+
# that symmetric key itself locked using the public key portion of the
|
183
|
+
# private key whose crypted form is presented in the first parameter.
|
184
|
+
def self.decrypt_it private_key, os_block_text
|
292
185
|
|
186
|
+
first_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, true )
|
187
|
+
trail_block = Base64.decode64( CryptIO.outer_crypt_deserialize os_block_text, false )
|
293
188
|
|
189
|
+
decrypt_key = private_key.private_decrypt first_block
|
190
|
+
inner_block = Blowfish.new.decryptor( trail_block, decrypt_key )
|
294
191
|
|
295
|
-
|
192
|
+
crypt_props = Hash.new
|
193
|
+
cipher_text = CryptIO.inner_crypt_deserialize( crypt_props, inner_block )
|
296
194
|
|
195
|
+
return Aes256.do_decrypt( crypt_props, cipher_text )
|
297
196
|
|
298
197
|
end
|
299
198
|
|
@@ -13,9 +13,9 @@ module OpenSecret
|
|
13
13
|
# dictionary which will be stored along with the ciphertext itself.
|
14
14
|
# The dictionary includes
|
15
15
|
#
|
16
|
-
# - <
|
17
|
-
# - <
|
18
|
-
# - <
|
16
|
+
# - <b>symmetric.cipher</b> - the algorithm used to encrypt and decrypt
|
17
|
+
# - <b>encryption.key</b> - hex encoded key for encrypting and decrypting
|
18
|
+
# - <b>initialize.vector</b> - the initialization vector known as a IV (four)
|
19
19
|
#
|
20
20
|
# == Aes256 Implemented Methods
|
21
21
|
#
|
@@ -25,137 +25,121 @@ module OpenSecret
|
|
25
25
|
#
|
26
26
|
# This class implements the below methods
|
27
27
|
#
|
28
|
-
# - <
|
29
|
-
# - <
|
28
|
+
# - <b>do_symmetric_encryption(plain_text)</b> - resulting in ciphertext
|
29
|
+
# - <b>do_symmetric_decryption(ciphertext, encryption_dictionary)</b> » plaintext
|
30
30
|
#
|
31
|
-
# and it also sets the <
|
31
|
+
# and it also sets the <b>@dictionary</b> hash (map) of pertinent
|
32
32
|
# key/value pairs including the +encryption algorithm+ and +encryption key+.
|
33
33
|
#
|
34
34
|
# That's It. Cipher children can rely on the {OpenSecret::Cipher} parent to
|
35
35
|
# do the nitty gritty of file-handling plus managing stores and paths.
|
36
36
|
|
37
|
-
class Aes256
|
38
|
-
|
39
|
-
@@initialize_vector_keyname = "initialize.vector"
|
37
|
+
class Aes256
|
40
38
|
|
41
39
|
# Use the AES 256 bit block cipher and a robust strong random key plus
|
42
40
|
# initialization vector (IV) to symmetrically encrypt the plain text.
|
43
41
|
#
|
44
|
-
#
|
42
|
+
# <b>Cryptographic Properties</b>
|
43
|
+
#
|
44
|
+
# This encrypt event populates key/value pairs to the hash (dictionary) instance
|
45
|
+
# given in the parameter.
|
46
|
+
#
|
47
|
+
# A crypt properties dictionary acts as <b>output from every encryption event</b>
|
48
|
+
# and <b>input to every decryption event</b>. The most common properties include
|
45
49
|
#
|
46
|
-
# -
|
47
|
-
# -
|
48
|
-
# -
|
50
|
+
# - the symmetric key used for the encryption and decryption
|
51
|
+
# - the iv (initialization vector) that adds another dimension of strength
|
52
|
+
# - authorization data that thwarts switch attacks by tying context to content
|
53
|
+
# - the cipher algorithm, its implementation and its encryption strength
|
54
|
+
# - the digest of the original message for validation purposes
|
55
|
+
#
|
56
|
+
# @param e_properties [Hash]
|
57
|
+
# instantiated hash map in which the encrryption properties will
|
58
|
+
# be stuffed.
|
49
59
|
#
|
50
60
|
# @param plain_text [String] the plain (or base64 encoded) text to encrypt
|
51
61
|
# @return [String] the symmetrically encrypted cipher text
|
52
|
-
def
|
62
|
+
def self.do_encrypt e_properties, plain_text
|
53
63
|
|
54
64
|
crypt_cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
55
65
|
crypt_cipher.encrypt
|
66
|
+
plain_text_digest = Digest::SHA256.digest plain_text
|
56
67
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
cipher_text = crypt_cipher.update( plain_text ) + crypt_cipher.final
|
68
|
+
e_properties[CryptIO::DICT_CIPHER_NAME] = crypt_cipher.class.name
|
69
|
+
e_properties[CryptIO::DICT_CRYPT_KEY] = Base64.urlsafe_encode64 crypt_cipher.random_key
|
70
|
+
e_properties[CryptIO::DICT_CRYPT_IV] = Base64.urlsafe_encode64 crypt_cipher.random_iv
|
71
|
+
e_properties[CryptIO::DICT_TEXT_DIGEST] = Base64.urlsafe_encode64 plain_text_digest
|
63
72
|
|
64
|
-
|
65
|
-
@dictionary[DICT_MATERIAL_DIGEST] = Base64.urlsafe_encode64(Digest::SHA256.digest(cipher_text))
|
66
|
-
|
67
|
-
return cipher_text
|
73
|
+
return crypt_cipher.update( plain_text ) + crypt_cipher.final
|
68
74
|
|
69
75
|
end
|
70
76
|
|
71
77
|
|
72
|
-
# Use the AES 256 bit block cipher together with the encryption key
|
73
|
-
#
|
74
|
-
# to symmetrically decrypt the
|
78
|
+
# Use the AES 256 bit block cipher together with the encryption key,
|
79
|
+
# initialization vector (iv) and other data found within the decryption
|
80
|
+
# properties dictionary to symmetrically decrypt the cipher text.
|
81
|
+
#
|
82
|
+
# This encrypt event in {self.do_encrypt} populated the property dictionary
|
83
|
+
# that was presumably serialized, stored, retrieved then deserialized and
|
84
|
+
# (at last) presented in the first parameter.
|
85
|
+
#
|
86
|
+
# <b>Cryptographic Properties</b>
|
87
|
+
#
|
88
|
+
# A crypt properties dictionary is the <b>output from every encryption event</b>
|
89
|
+
# and <b>input to every decryption event</b>. The most common properties include
|
75
90
|
#
|
76
|
-
#
|
91
|
+
# - the symmetric key used for the encryption and decryption
|
92
|
+
# - the iv (initialization vector) that adds another dimension of strength
|
93
|
+
# - authorization data that thwarts switch attacks by tying context to content
|
94
|
+
# - the cipher algorithm, its implementation and its encryption strength
|
95
|
+
# - the digest of the original message for validation purposes
|
77
96
|
#
|
78
|
-
#
|
79
|
-
#
|
97
|
+
# @param d_properties [Hash]
|
98
|
+
# the crypt properties dictionary is the <b>output from every encryption event</b>
|
99
|
+
# and (as in this case) <b>input to every decryption event</b>.
|
80
100
|
#
|
81
|
-
#
|
82
|
-
#
|
101
|
+
# @param cipher_text [String]
|
102
|
+
# the (already decoded) cipher text for decryption by this method using the
|
103
|
+
# encryption properties setup during the past encrypt event.
|
83
104
|
#
|
84
|
-
# @
|
85
|
-
#
|
86
|
-
|
105
|
+
# @return [String]
|
106
|
+
# the plain text message originally given to be encrypted. If the message digest
|
107
|
+
# is provided within the decryption properties dictionary a sanity check will
|
108
|
+
# occur.
|
109
|
+
#
|
110
|
+
# @raise [RuntimeError]
|
111
|
+
# if decryption fails or the recalculated message digest fails an equivalence test.
|
112
|
+
def self.do_decrypt d_properties, cipher_text
|
113
|
+
|
114
|
+
decode_cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
115
|
+
decode_cipher.decrypt
|
116
|
+
|
117
|
+
decode_cipher.key = Base64.urlsafe_decode64( d_properties[CryptIO::DICT_CRYPT_KEY] )
|
118
|
+
decode_cipher.iv = Base64.urlsafe_decode64( d_properties[CryptIO::DICT_CRYPT_IV] )
|
87
119
|
|
88
|
-
|
120
|
+
plain_text = decode_cipher.update( cipher_text ) + decode_cipher.final
|
121
|
+
assert_digest_equivalence( d_properties[CryptIO::DICT_TEXT_DIGEST], plain_text )
|
122
|
+
|
123
|
+
return plain_text
|
89
124
|
|
90
125
|
end
|
91
126
|
|
92
127
|
|
128
|
+
private
|
129
|
+
|
93
130
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
line5 = "5>> secret 5 with hyphens - and underscore __ and plus ++ and equal == and sqBs [[]].\n"
|
107
|
-
line6 = "6>> secret 6 with double quote \"from here to here\" and \' single quotes\'.\n"
|
108
|
-
line7 = "7>> secret 7 with periods .... and hashes #####\n"
|
109
|
-
|
110
|
-
crypt_text = ""
|
111
|
-
crypt_text += encode_cipher.update line1
|
112
|
-
crypt_text += encode_cipher.update line2
|
113
|
-
crypt_text += encode_cipher.update line3
|
114
|
-
crypt_text += encode_cipher.update line4
|
115
|
-
crypt_text += encode_cipher.update line5
|
116
|
-
crypt_text += encode_cipher.update line6
|
117
|
-
crypt_text += encode_cipher.update line7
|
118
|
-
crypt_text += encode_cipher.final
|
119
|
-
coded_crypt_text = Base64.urlsafe_encode64(crypt_text)
|
120
|
-
|
121
|
-
puts ""
|
122
|
-
puts "The key is #{hex_key}"
|
123
|
-
puts "The IV is #{hex_iv}"
|
124
|
-
puts "========================"
|
125
|
-
puts "The Cipher Text is Below"
|
126
|
-
puts "========================"
|
127
|
-
puts coded_crypt_text
|
128
|
-
puts "========================"
|
129
|
-
puts crypt_text
|
130
|
-
puts "========================"
|
131
|
-
puts "========================"
|
132
|
-
puts "========================"
|
133
|
-
puts line1 + line2 + line3 + line4 + line5 + line6 + line7
|
134
|
-
puts "========================"
|
135
|
-
puts "========================"
|
136
|
-
puts "========================"
|
137
|
-
puts ""
|
138
|
-
puts ""
|
139
|
-
|
140
|
-
unencoded_crypt_text = Base64.urlsafe_decode64(coded_crypt_text)
|
141
|
-
decode_cipher = OpenSSL::Cipher.new('aes-256-cbc')
|
142
|
-
|
143
|
-
decode_cipher.decrypt
|
144
|
-
decode_cipher.key = [hex_key].pack("H*")
|
145
|
-
decode_cipher.iv = [hex_iv].pack("H*")
|
146
|
-
first_part = decode_cipher.update( Base64.urlsafe_decode64(coded_crypt_text) )
|
147
|
-
second_part = ""
|
148
|
-
second_part << decode_cipher.final
|
149
|
-
|
150
|
-
puts "========================"
|
151
|
-
puts "Decrypted Text is Below"
|
152
|
-
puts "========================"
|
153
|
-
puts first_part
|
154
|
-
puts "========================"
|
155
|
-
puts second_part
|
156
|
-
puts "========================"
|
157
|
-
puts ""
|
158
|
-
=end
|
131
|
+
def self.assert_digest_equivalence( digest_b4_encryption, plain_text_message )
|
132
|
+
|
133
|
+
plain_text_digest = Base64.urlsafe_encode64( Digest::SHA256.digest( plain_text_message ) )
|
134
|
+
return if digest_b4_encryption.eql? plain_text_digest
|
135
|
+
|
136
|
+
msg1 = "\nEquivalence check of original and decrypted plain text digests failed.\n"
|
137
|
+
msg2 = "Digest before encryption => #{digest_b4_encryption}\n"
|
138
|
+
msg3 = "Digest after decryption => #{plain_text_digest}\n"
|
139
|
+
error_message = msg1 + msg2 + msg3
|
140
|
+
raise RuntimeError, error_message
|
141
|
+
|
142
|
+
end
|
159
143
|
|
160
144
|
|
161
145
|
end
|