-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathencrypted_session.py
More file actions
153 lines (121 loc) · 5.45 KB
/
encrypted_session.py
File metadata and controls
153 lines (121 loc) · 5.45 KB
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
from werkzeug.datastructures import CallbackDict
from flask.sessions import SessionInterface, SessionMixin
from Crypto.Cipher import AES
import json
from json import JSONEncoder, JSONDecoder
import base64
import zlib
class EncryptedSession(CallbackDict, SessionMixin):
def __init__(self, initial=None):
def on_update(self):
self.modified = True
CallbackDict.__init__(self, initial, on_update)
self.modified = False
class EncryptedSessionInterface(SessionInterface):
session_class = EncryptedSession
compress_threshold = 1024
def open_session(self, app, request):
'''
@param py: Flask py
@param request: Flask HTTP Request
@summary: Sets the current session from the request's session cooke. This overrides the default
Flask implementation, adding AES decryption of the client-side session cookie.
'''
# Get the session cookie
session_cookie = request.cookies.get(app.session_cookie_name)
if not session_cookie:
return self.session_class()
# Get the crypto key
crypto_key = app.config['SESSION_CRYPTO_KEY'] if 'SESSION_CRYPTO_KEY' in app.config else app.crypto_key
# Split the session cookie : <z|u>.<base64 cipher text>.<base64 mac>.<base64 nonce>
itup = session_cookie.split(".")
if (len (itup) is not 4):
return self.session_class() # Session cookie not in the right format
try:
# Compressed data?
if (itup[0] == 'z'): # session cookie for compressed data starts with "z."
is_compressed = True
else:
is_compressed = False
# Decode the cookie parts from base64
ciphertext = base64.b64decode (bytes(itup[1], 'utf-8'))
mac = base64.b64decode (bytes(itup[2], 'utf-8'))
nonce = base64.b64decode (bytes(itup[3], 'utf-8'))
# Decrypt
cipher = AES.new (crypto_key, AES.MODE_EAX, nonce)
data = cipher.decrypt_and_verify (ciphertext, mac)
# Convert back to a dict and pass that onto the session
if is_compressed:
data = zlib.decompress (data)
session_dict = json.loads (str (data, 'utf-8'), cls=BinaryAwareJSONDecoder)
return self.session_class (session_dict)
except ValueError:
return self.session_class()
def save_session(self, app, session, response):
'''
@param py: Flask py
@param session: Flask / Werkzeug Session
@param response: Flask HTTP Response
@summary: Saves the current session. This overrides the default Flask implementation, adding
AES encryption of the client-side session cookie.
'''
domain = self.get_cookie_domain(app)
if not session:
if session.modified:
response.delete_cookie (app.session_cookie_name, domain=domain)
return
expires = self.get_expiration_time(app, session)
# Decide whether to compress
bdict = bytes (json.dumps (dict (session), cls=BinaryAwareJSONEncoder), 'utf-8')
if (len (bdict) > self.compress_threshold):
prefix = "z" # session cookie for compressed data starts with "z."
bdict = zlib.compress (bdict)
else:
prefix = "u" # session cookie for uncompressed data starts with "u."
# Get the crypto key
crypto_key = app.config['SESSION_CRYPTO_KEY'] if 'SESSION_CRYPTO_KEY' in app.config else app.crypto_key
# Encrypt using AES in EAX mode
cipher = AES.new (crypto_key, AES.MODE_EAX)
ciphertext, mac = cipher.encrypt_and_digest (bdict)
nonce = cipher.nonce
# Convert the ciphertext, mac, and nonce to base64
b64_ciphertext = base64.b64encode (ciphertext)
b64_mac = base64.b64encode (mac)
b64_nonce = base64.b64encode (nonce)
# Create the session cookie as <u|z>.<base64 cipher text>.<base64 mac>.<base64 nonce>
tup = [prefix, b64_ciphertext.decode(), b64_mac.decode(), b64_nonce.decode()]
session_cookie = ".".join(tup)
# Set the session cookie
response.set_cookie(app.session_cookie_name, session_cookie,
expires=expires, httponly=True,
domain=domain)
class BinaryAwareJSONEncoder(JSONEncoder):
"""
Converts a python object, where binary data is converted into an object
that can be decoded using the BinaryAwareJSONDecoder.
"""
def default(self, obj):
if isinstance(obj, bytes):
return {
'__type__' : 'bytes',
'b' : base64.b64encode (obj).decode ()
}
else:
return JSONEncoder.default(self, obj)
class BinaryAwareJSONDecoder(JSONDecoder):
"""
Converts a json string, where binary data was converted into objects form
using the BinaryAwareJSONEncoder, back into a python object.
"""
def __init__(self):
JSONDecoder.__init__(self, object_hook=self.dict_to_object)
def dict_to_object(self, d):
if '__type__' not in d:
return d
typ = d.pop('__type__')
if typ == 'bytes':
return base64.b64decode (bytes (d['b'], 'utf-8'))
else:
# Oops... better put this back together.
d['__type__'] = typ
return d