Merge pull request #1357 from fyfe/feature/refactor-cryptutils

refactor cryptutils
This commit is contained in:
Antoine Nguyen
2018-01-18 09:02:37 +01:00
committed by GitHub
2 changed files with 71 additions and 42 deletions
+33 -42
View File
@@ -1,58 +1,49 @@
# coding: utf-8
# -*- coding: utf-8 -*-
"""Crypto related utilities."""
from __future__ import unicode_literals
import base64
import random
import string
from django.utils.encoding import force_bytes, force_text
from django.conf import settings
from django.utils.crypto import get_random_string
from django.utils.encoding import smart_bytes, smart_text
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
from modoboa.parameters import tools as param_tools
from cryptography.fernet import Fernet
def random_key(l=16):
"""Generate a random key.
:param integer l: the key's length
:return: a string
"""
punctuation = """!#$%&'()*+,-./:;<=>?@[]^_`{|}~"""
population = string.digits + string.ascii_letters + punctuation
while True:
key = "".join(random.sample(population * l, l))
if len(key) == l:
return key
def random_key(length=16):
"""Generate a new key used to encrypt user passwords in session storage."""
chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)'
return get_random_string(length, chars)
def encrypt(clear):
key = force_bytes(
param_tools.get_global_parameter("secret_key", app="core"))
backend = default_backend()
cipher = Cipher(algorithms.AES(key), modes.ECB(), backend=backend)
encryptor = cipher.encryptor()
block_size = algorithms.AES.block_size
clear = force_bytes(clear)
if len(clear) % block_size:
clear += b" " * (block_size - len(clear) % block_size)
ct = encryptor.update(clear) + encryptor.finalize()
return force_text(base64.b64encode(ct))
def _get_fernet():
"""Create a Fernet instance."""
secret_key = base64.urlsafe_b64encode(smart_bytes(settings.SECRET_KEY[:32]))
return Fernet(secret_key)
def decrypt(ct):
backend = default_backend()
key = param_tools.get_global_parameter("secret_key", app="core")
cipher = Cipher(
algorithms.AES(force_bytes(key)), modes.ECB(), backend=backend)
ct = base64.b64decode(force_bytes(ct))
decryptor = cipher.decryptor()
clear = decryptor.update(ct) + decryptor.finalize()
return force_text(clear.rstrip(b" "))
def encrypt(clear_value):
"""Encrypt a value using secret_key."""
return smart_text(_get_fernet().encrypt(smart_bytes(clear_value)))
def decrypt(encrypted_value):
"""Decrypt a value using secret_key."""
return smart_text(_get_fernet().decrypt(smart_bytes(encrypted_value)))
def get_password(request):
return decrypt(request.session["password"])
"""
Retrieve and decrypt the users password from session storage.
This is used by modoboa-webmail to allow the user to send/receive e-mails
without having to ask the user for a password on each connection to the
postfix/dovecot server.
"""
try:
return decrypt(request.session["password"])
except KeyError:
return None
+38
View File
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
"""Tests for crypto utility functions."""
from __future__ import unicode_literals
from django.test import SimpleTestCase
from django.test.utils import override_settings
from modoboa.lib import cryptutils
@override_settings(SECRET_KEY="!8o(-dbbl3e+*bh7nx-^xysdt)1gso*%@4ze4-9_9o+i&amp"
";t--u_")
class CryptUtilsTest(SimpleTestCase):
"""Tests for modoboa.lib.cryptutils"""
def test_valid_secret_key_generated(self):
"""Generate a 32 byte key"""
secret_key = cryptutils.random_key(32)
self.assertEqual(len(secret_key), 32)
def test_encrypt(self):
"""Encrypt a string"""
value = "test"
expected_output = "test"
output = cryptutils.encrypt(value)
output = cryptutils.decrypt(output)
self.assertEqual(output, expected_output)
def test_decrypt(self):
"""Decrypt a string"""
value = "gAAAAABaXSWk4Via-aYP7diek9MmfknUiEY8szrgxIytdXrbfc"\
"YlbOYiNG01zqkn3a06P1xPXe5XD2SP4UrIvCWzhLs-FO19Cw=="
expected_output = "test"
output = cryptutils.decrypt(value)
self.assertEqual(output, expected_output)