opensecret 0.0.960 → 0.0.962

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b95962b0946200185b87257ef12fbfab9e689ecd
4
- data.tar.gz: 40eb3bb6b0a401192e33a92b23053d35755f7c51
3
+ metadata.gz: 06aed72d992d1b8f9ae2533681c47e20d9974e05
4
+ data.tar.gz: 04b41b862706f7f98d95f78bdbc18405c0e5b80a
5
5
  SHA512:
6
- metadata.gz: 8e6c1c61a35e114c6e20e5d8192c3d1a2b996fb6bb65ebd58858f268d2839d5d629824883b2efc8ef8e50faf064f13efcf77e850986313baa168fb0ea06caf10
7
- data.tar.gz: 8f511f4f83d18d85a3891ef504bef7b60a8f8bcb59e427d86f24acf928315d765132be999a0aa63a2aec4cd818e7b96ab61df59346fc6753c94c30fa4bc14b66
6
+ metadata.gz: 993f8128c462a648fb3599326cac9ee378a17d8bbaea620aee11223a6b10584205df2c2c68b2856223befccb2a0e02207188ddbd80dfe3e0bd615c6f1d95a66f
7
+ data.tar.gz: 6684892e42493e09e13549591ef8b048ad95d0b586c4a8f1226e4d1f063fc42401241326a814c35c78699aa2a2d441fceb9f69ea9ce050c909eed090258168dd
@@ -19,7 +19,7 @@ class Trial
19
19
 
20
20
  end
21
21
 
22
- Trial.ciphername
22
+ ####### ======> Trial.ciphername
23
23
 
24
24
 
25
25
  def self.certify
data/lib/opensecret.rb CHANGED
@@ -65,21 +65,52 @@ class CliInterpreter < Thor
65
65
  end
66
66
 
67
67
 
68
- # Description of the open "wake-up" call.
69
- desc "open CONTEXT_PATH", "CONTEXT_PATH context path to the family of secrets to be created."
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 context_path [String] the path to USB key for storing encrypted keys
75
- def open context_path
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.context_path = context_path
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."
@@ -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
- # Many ciphers (like Blowfish) constrains plain text lengths to multiples
61
- # of 8 (or 16) and a common +right pad with spaces+ strategy is employed
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
- # +public key+ provided and return the text that can safely be stored in
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 public_key_text [String] textual portion of an {OpenSSL::PKey::RSA}
146
- # public key. There is no need for an asymmetric encryption agent to know
147
- # the asymmetic private key.
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] plaintext (or base64 encoded) text to encrypt
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 public_key_text, payload_text
153
-
154
- crypted_payload = do_symmetric_encryption payload_text
128
+ def self.encrypt_it public_key, payload_text
155
129
 
156
- ######### puts JSON.pretty_generate(@dictionary)
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 = do_asymmetric_encryption public_key_text, outer_crypt_key
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
- end
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
- asymmetric_encrypt_key = OpenSSL::PKey::RSA.new public_key_text
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
- # The decrypted cipher-text is actually a two part bundle consisting of
189
- # a dictionary and then more cipher-text which is then decrypted using
190
- # the symmetric key held within the dictionary.
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
- # The private key revealed a dictionary holding the symmetric encryption
193
- # key and other details pertinent to the cipher's encrypt/decrypt process.
194
- # This data is used to work on the cipher text, eventually revealing the
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
- # Ciphers should never interact with the filesystem which makes them
198
- # reusable in API and remote store scenarios.
153
+ # <b>The 3 OpenSecret Blocks</b>
199
154
  #
200
- # @param private_key [String] reveals the dictionary and more ciphertext
201
- # @param cipher_text [String] the crypted (base64 encoded) text bundle
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
- # @return [String] the plain or encoded text first pushed into the cipher
204
- def decrypt_it private_key, cipher_text
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 separator string must be unusual to make it unlikely for it
219
- # to occur in any of the map's key value pairs nor indeed the chunk
220
- # of text being glued. Were this to happen, the separate and reconstitute
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
- # == Hash (Map) Contents
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
- # The map contents will effectively be serialized in a simplistic
227
- # manner therefore the keys should all be strings and the value
228
- # types restricted to
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
- # - strings
231
- # - integers and/or floating point numbers
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
- # Binary text is not allowed neither in the map or the text chunk.
235
- # Any binary should be base64 encoded before being passed to us.
236
- #
237
- # @param text_chunk [String] the text chunk to be glued at the bottom
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
- def rekey_usecase old_private_key, new_public_key, secret_path, store
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
- # - <tt>symmetric.cipher</tt> - the algorithm used to encrypt and decrypt
17
- # - <tt>encryption.key</tt> - hex encoded key for encrypting and decrypting
18
- # - <tt>initialize.vector</tt> - the initialization vector known as a IV (four)
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
- # - <tt>do_symmetric_encryption(plain_text)</tt> - resulting in ciphertext
29
- # - <tt>do_symmetric_decryption(ciphertext, encryption_dictionary)</tt> &raquo; plaintext
28
+ # - <b>do_symmetric_encryption(plain_text)</b> - resulting in ciphertext
29
+ # - <b>do_symmetric_decryption(ciphertext, encryption_dictionary)</b> &raquo; plaintext
30
30
  #
31
- # and it also sets the <tt>@dictionary</tt> hash (map) of pertinent
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 < OpenSecret::Cipher
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
- # Add these key/value pairs to @dictionary instance map.
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
- # - <tt>symmetric.cipher</tt> - the algorithm used to encrypt and decrypt
47
- # - <tt>encryption.key</tt> - hex encoded key for encrypting and decrypting
48
- # - <tt>initialize.vector</tt> - the initialization vector known as a IV (four)
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 do_symmetric_encryption plain_text
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
- @dictionary[DICT_CIPHER_NAME] = crypt_cipher.class.name
58
- @dictionary[DICT_CRYPT_KEY] = Base64.urlsafe_encode64 crypt_cipher.random_key
59
- @dictionary[DICT_INIT_VECTOR] = Base64.urlsafe_encode64 crypt_cipher.random_iv
60
- @dictionary[DICT_PLAINTEXT_DIGEST] = Base64.urlsafe_encode64(Digest::SHA256.digest(plain_text))
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
- @dictionary[DICT_CIPHER_TEXT] = Base64.urlsafe_encode64( cipher_text )
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
- # and initialization vector (iv) sitting in the encryption_dictionary,
74
- # to symmetrically decrypt the parameter cipher text.
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
- # == Pre-Condition | Encryption ...
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
- # This method requires the <tt>@dictionary</tt> instance
79
- # variable to have been set and to contain (amongst others)
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
- # - the <tt>encryption.key</tt> - hex encoded key for encrypting and decrypting
82
- # - and <tt>initialize.vector</tt> - the initialization vector known as a IV (four)
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
- # @param cipher_text [String] the base64 encoded cipher text to decrypt
85
- # @return [String] decrypted plain text from symmetric key and cipher text
86
- def do_symmetric_decryption cipher_text
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
- abort "Implement AES 256 decryption in aes-256"
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
- =begin
95
- encode_cipher = OpenSSL::Cipher.new('aes-256-cbc')
96
- encode_cipher.encrypt # We are encrypting
97
- key = encode_cipher.random_key
98
- iv = encode_cipher.random_iv
99
- hex_key = key.unpack("H*").first
100
- hex_iv = iv.unpack("H*").first
101
-
102
- line1 = "1>> This is secret number one over here with at @ and squiggle~ and round brakets().\n"
103
- line2 = "2>> secret number two with colon and semi :; angular <> qmarks ??.\n"
104
- line3 = "3>> secret number 3 fwd slash / and backslash twice \\ and pipe || and excla !!\n"
105
- line4 = "4>> secret 4 with pound ££ dollar $$ percent %% hat ^^ ampr && stars **\n"
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