This repository has been archived by the owner on Jan 4, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
/
EncryptedYAML.py
401 lines (302 loc) · 11.8 KB
/
EncryptedYAML.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
#-----------------------------------------------------------
# Filename : EncryptedYaml.py
# Description : Simple wrapper around the PyYaml module that
# provides support for encrypting the YAML with blowfish
# Created By : Rich Smith
# Date Created : 05-Jan-2014 13:30
#
# License : LGPL
#
# (c) Copyright 2014, Rich Smith all rights reserved.
#-----------------------------------------------------------
#
# Useful for there are passwords / API keys in the YAML that you would like
# to offer some data at rest protection to.
#
# This *DOES NOT* offer any level of in-memory protection of the objects during
# or after encryption / decryption.
__author__ = "Rich Smith"
__version__ = "0.1"
import sys
import types
try:
import yaml
except:
print "[-] The yaml module is needed to use encrypted yaml.\nGo to http://pyyaml.org or `pip install PyYaml`"
raise
## Pure Python Blowfish implementation by Michael Gilfix <[email protected]>
from blowfish import Blowfish
ENCRYPTED_YAML_HEADER = "#EncryptedYaml_BlowFish\n\n"
class BadKeyException(Exception):
pass
class EncryptedYamlException(Exception):
pass
def is_data_encrypted(data_to_test):
"""
Test whether a passed string is encrypted by looking for the magic header.
Return True if the supplied file object is found to have the ENCRYPTED_YAML_HEADER
False if not.
"""
##Look for our magic header in the first line
if ENCRYPTED_YAML_HEADER in data_to_test:
encrypted = True
else:
encrypted = False
return encrypted
def _encrypt_yaml(yaml_stream, key):
"""
Function that does the encryption of the YAML document with the specified key.
The stream is always a string object.
Return the encrypted version of the string with the ENCRYPTED_YAML_HEADER prepended.
"""
##Blow the fish
try:
bfish = Blowfish(key)
bfish.initCTR()
crypto_yaml = bfish.encryptCTR(yaml_stream)
##Add in our header to indicate we're encrypted
crypto_yaml = "%s%s"%(ENCRYPTED_YAML_HEADER, crypto_yaml)
except Exception, err:
raise EncryptedYamlException("Problem encrypting the YAML file - %s"%(err))
return crypto_yaml
def _decrypt_yaml(e_yaml_stream, key):
"""
Function that does the decryption of the YAML document with the specified key.
The stream can be any of the types of streams support by the PyYaml module (strings,
unicode strings, or file objects)
"""
##We have to read the file so ensure we can reset it to where it was when passed
if type(e_yaml_stream) == types.FileType:
curr_poss = e_yaml_stream.tell()
e_yaml_data = e_yaml_stream.read()
else:
e_yaml_data = e_yaml_stream
##Skip first line as it's the magic header
pos = e_yaml_data.find("\n\n")
if pos != -1:
e_yaml_data = e_yaml_data[pos+2:]
##Decrypt stream
try:
bfish = Blowfish(key)
bfish.initCTR()
yaml_data = bfish.decryptCTR(e_yaml_data)
except Exception, err:
raise EncryptedYamlException("Problem decrypting the YAML file - %s"%(err))
##Reset read position if stream is a file object
if type(e_yaml_stream) == types.FileType:
e_yaml_stream.seek(curr_poss)
return yaml_data
##Overload the load/dump functions from the yaml module so that the encryption/decryption can take place
## if needed and rely on the PyYaml module to do the YAML side of things
def load(stream, Loader = yaml.loader.Loader, key = None):
"""
Parse the first YAML document in a stream and produce the corresponding Python object.
If a key is specified decrypt the YAML document prior to parsing.
"""
##If a key is provided we need to attempt decryption of the stream
if key:
##Is encrypted, decrypt stream with supplied key and then pass on
stream = _decrypt_yaml(stream, key)
##Hand off decrypted stream to PyYaml module
return yaml.load(stream, Loader)
def load_all(stream, Loader = yaml.loader.Loader, key = None):
"""
Parse all YAML documents in a stream and produce corresponding Python objects.
If a key is specified decrypt the YAML document prior to parsing.
"""
##If a key is provided we need to attempt decryption of the stream
if key:
##Is encrypted, decrypt stream with supplied key and then pass on
stream = _decrypt_yaml(stream, key)
##Hand off decrypted stream to PyYaml module
return yaml.load_all(stream, Loader)
def safe_load(stream, key = None):
"""
Parse the first YAML document in a stream and produce the corresponding Python object.
If a key is specified decrypt the YAML document prior to parsing.
Resolve only basic YAML tags.
"""
return load(stream, yaml.loader.SafeLoader, key)
def safe_load_all(stream, key = None):
"""
Parse all YAML documents in a stream and produce corresponding Python objects.
If a key is specified decrypt the YAML document prior to parsing.
Resolve only basic YAML tags.
"""
return load_all(stream, yaml.loader.SafeLoader, key)
def dump_all(documents, stream = None, Dumper = yaml.dumper.Dumper, key = None, **kwargs):
"""
Serialize a sequence of Python objects into a YAML stream & if a key is specified encrypt the resulting stream.
If a stream was passed write the resulting yaml object to that stream, if stream is None, return the produced string
instead.
"""
all_yaml = ""
for data in documents:
##If a key value has been passed do not pass the stream argument
## so as the unencrypted data does not get written to a file else just
## do what has been requested
if not key:
return yaml.dump(data, stream = stream, Dumper = Dumper, **kwargs)
##Do not pass the stream argument to force the return of a string repr of the yaml rather than a file write
yaml_obj = yaml.dump(data, Dumper = Dumper, **kwargs)
##Do the encryption
crypto_yaml = _encrypt_yaml(yaml_obj, key)
##If a stream was passed write the encrypted data to that stream
if stream:
try:
stream.write(crypto_yaml)
except Exception, err:
raise EncryptedYamlException("Problem writing encrypted YAML to specified stream - %s"%(err))
##If no stream specified just return the header + crypted bytes
else:
all_yaml += crypto_yaml
##Either return a string representation or None if a stream was passed
if stream:
return None
else:
return all_yaml
def dump(data, stream = None, Dumper = yaml.dumper.Dumper, key = None, **kwargs):
"""
Serialize a Python object into a YAML stream & if a key is specified encrypt it.
If stream is None, return the produced string instead.
"""
return dump_all([data], stream, Dumper = Dumper, key = key, **kwargs )
def safe_dump(data, stream = None, key = None, **kwargs):
"""
Serialize a sequence of Python objects into a YAML stream & if a key is specified encrypt it.
Produce only basic YAML tags.
If stream is None, return the produced string instead.
"""
return dump_all([data], stream = stream, key = key, Dumper = yaml.dumper.SafeDumper, **kwargs)
def safe_dump_all(documents, stream = None, key = None, **kwargs):
"""
Serialize a Python object into a YAML stream & if a key is specified encrypt it.
Produce only basic YAML tags.
If stream is None, return the produced string instead.
"""
return dump_all(documents, stream, key, Dumper = yaml.dumper.SafeDumper, **kwargs)
def __test():
"""
Perform some tests
"""
print "[!] Testing EncryptedYaml..."
import tempfile
KEY = "TESTTEST"
test_yaml = """
foo: bar
bar: 2
"""
test_obj = {"foo":"bar", "bar":2}
##Test YAML dumps
ret = dump(test_obj)
print "[+] String Dump: ", ret
tmp_fd, tmp_fn = tempfile.mkstemp(dir="/tmp")
tmp_fo = open(tmp_fn, "r+b")
ret = dump(test_obj, stream = tmp_fo)
tmp_fo.close()
print "[+] File Dump written to: ", tmp_fn
c_ret = dump(test_obj, key = KEY)
print "[+] Encrypted String Dump: ", c_ret
e_tmp_fd, e_tmp_fn = tempfile.mkstemp(dir="/tmp")
e_tmp_fo = open(e_tmp_fn, "r+b")
ret = dump(test_obj, stream = e_tmp_fo, key = KEY)
e_tmp_fo.close()
print "[+] Encrypted File Dump written to: ", e_tmp_fn
##Test YAML loads
ret = load(test_yaml)
print "[+] String Load: ",ret
##test load yaml file
tmp_fo = open(tmp_fn, "r+b")
ret = load(tmp_fo)
tmp_fo.close()
print "[+] File Load: ",ret
##test load encrypted yaml string
e_tmp_fo = open(e_tmp_fn, "r+b")
ret = load(e_tmp_fo.read(), key = KEY)
print "[+] Encrypted String Load:",ret
##test load encrypted yaml file
e_tmp_fo.seek(0)
ret = load(e_tmp_fo, key = KEY)
e_tmp_fo.close()
print "[+] Encrypted File Load:",ret
print "[!] Done."
def __clear2encrypted(input_f, output_f):
"""
Take a clear YAML file, parse it, encrypt it and dump crypted data to specified file
"""
##Open cleartext yaml file
try:
config_f_obj = open(input_f, "rb")
except Exception, err:
print "[-] Error opening specified file '%s' - "%(input_f, err)
return False
key = getpass.getpass("Please enter a key for the encryption (8 chars or longer): ")
##Parse the data to a yaml object
try:
y_data = yaml.load(config_f_obj)
except Exception, err:
print "[-] Problem parsing specified YAML file - %s"%(err)
config_f_obj.close()
return False
config_f_obj.close()
##Now pass that yaml object to the crypto dumper and have it encrypted and written to disk
try:
output_f_obj = open(output_f, "wb")
ret = dump(y_data, stream = output_f_obj, key = key)
except Exception, err:
print "[-] Error opening/encrypting specified file '%s' - %s"%(output_f, err)
return False
output_f_obj.close()
print "[+] Encrypted YAML file written to: %s"%(output_f)
return True
def __encrypted2clear(input_f, output_f):
"""
Take an encrypted YAML file, decrypt it, parse it and dump clear data to specified file
"""
try:
encrypted_f_obj = open(input_f, "rb")
except Exception, err:
print "[-] Error opening specified file '%s' - "%(input_f, err)
return False
key = getpass.getpass("Please enter a key for the decryption (8 chars or longer): ")
try:
d_data = load(encrypted_f_obj, key = key)
print type(d_data)
except Exception, err:
print "[-] Problem decrypting YAML stream (check the supplied key is correct) - %s"%(err)
encrypted_f_obj.close()
return False
encrypted_f_obj.close()
try:
output_f_obj = open(output_f, "wb")
ret = yaml.dump(d_data, stream = output_f_obj, default_flow_style=False)
except Exception, err:
print "[-] Error opening/dumping specified file '%s' - %s"%(output_f, err)
return False
output_f_obj.close()
print "[+] Decrypted YAML file written to: %s"%(output_f)
return True
if __name__ == "__main__":
"""
Super basic encrypt / decrypt / test functionality
"""
import getpass
def usage():
print "Usage: %s [option] [arg]"%(sys.argv[0])
print "Options:"
print "\te <yaml filename> <encrypted yaml filename> - encrypt yaml config file (will prompt for key)"
print "\td <encrypted yaml filename> <yaml file> - decrypt yaml config file"
print "\tt - Run some tests"
if len(sys.argv) < 2:
usage()
sys.exit(-1)
elif sys.argv[1] == "t":
__test()
elif sys.argv[1] == "e":
__clear2encrypted(sys.argv[2], sys.argv[3])
elif sys.argv[1] == "d":
__encrypted2clear(sys.argv[2], sys.argv[3])
else:
usage()
sys.exit(-1)
sys.exit(0)