-
Notifications
You must be signed in to change notification settings - Fork 308
implement symmetric encryption #3998
Changes from 1 commit
01b4ec3
2a6bbd4
6edce65
da4cf0f
e0dc3c4
049bd48
34ba9cd
5ce0ff8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
from __future__ import absolute_import, division, print_function, unicode_literals | ||
|
||
import hashlib | ||
import json | ||
import random | ||
import string | ||
import time | ||
|
||
from cryptography.fernet import Fernet, MultiFernet | ||
|
||
|
||
# utils | ||
# ===== | ||
|
@@ -61,3 +64,39 @@ def constant_time_compare(val1, val2): | |
for x, y in zip(val1, val2): | ||
result |= ord(x) ^ ord(y) | ||
return result == 0 | ||
|
||
|
||
# Encrypting Packer | ||
# ================= | ||
|
||
class EncryptingPacker(object): | ||
"""Implement conversion of Python objects to/from encrypted bytestrings. | ||
|
||
:param bytes key: a Fernet key as ``bytes``, for encryption and decryption via | ||
``cryptography.fernet.MultiFernet`` | ||
:param list old_keys: additional Fernet keys as ``bytes`` for decryption via | ||
``cryptography.fernet.MultiFernet`` | ||
|
||
""" | ||
|
||
def __init__(self, key, *old_keys): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't this be simplified to just take There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, maybe this was done to emphasize the answer to https://github.com/gratipay/gratipay.com/pull/3998/files#r62548400? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, that's more or less what I was thinking. I wanted to differentiate between the first key, which is the one used for encryption (and decryption), and the old keys, which can only be used for decryption. |
||
keys = [key] + list(old_keys) | ||
self.fernet = MultiFernet([Fernet(k) for k in keys]) | ||
|
||
def pack(self, obj): | ||
"""Given a JSON-serializable object, return an encrypted byte string. | ||
""" | ||
obj = json.dumps(obj) # serialize to unicode | ||
obj = obj.encode('utf8') # convert to bytes | ||
obj = self.fernet.encrypt(obj) # encrypt | ||
return obj | ||
|
||
def unpack(self, obj): | ||
"""Given an encrypted byte string, return a Python object. | ||
""" | ||
if not type(obj) is bytes: | ||
raise TypeError("need bytes, got {}".format(type(obj))) | ||
obj = self.fernet.decrypt(obj) # decrypt | ||
obj = obj.decode('utf8') # convert to unicode | ||
obj = json.loads(obj) # deserialize from unicode | ||
return obj |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,9 @@ | |
|
||
from aspen import Response | ||
from aspen.http.request import Request | ||
from base64 import urlsafe_b64decode | ||
from gratipay import security | ||
from gratipay.models.participant import Participant | ||
from gratipay.testing import Harness | ||
from pytest import raises | ||
|
||
|
@@ -42,3 +44,19 @@ def test_ahtr_sets_x_content_type_options(self): | |
def test_ahtr_sets_x_xss_protection(self): | ||
headers = self.client.GET('/about/').headers | ||
assert headers['X-XSS-Protection'] == '1; mode=block' | ||
|
||
|
||
# ep - EncryptingPacker | ||
|
||
def test_ep_packs_encryptingly(self): | ||
packed = Participant.encrypting_packer.pack({"foo": "bar"}) | ||
assert urlsafe_b64decode(packed)[0] == b'\x80' # Frenet version | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Frenet → Fernet |
||
|
||
def test_ep_unpacks_decryptingly(self): | ||
packed = b'gAAAAABXJMbdriJ984uMCMKfQ5p2UUNHB1vG43K_uJyzUffbu2Uwy0d71kAnqOKJ7Ww_FEQz9Dliw8'\ | ||
b'7UpM5TdyoJsll5nMAicg==' | ||
assert Participant.encrypting_packer.unpack(packed) == {"foo": "bar"} | ||
|
||
def test_ep_demands_bytes(self): | ||
raises(TypeError, Participant.encrypting_packer.unpack, buffer('buffer')) | ||
raises(TypeError, Participant.encrypting_packer.unpack, 'unicode') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe leave a comment here saying that extra keys should be separated by a space?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, in a space separated list - which is the latest key (the one with which new items will be encrypted)? The first or the last?