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 +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
|