/
crypto_utils.py
193 lines (142 loc) · 5.74 KB
/
crypto_utils.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
import base64
import os
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from django.conf import settings
AES_BLOCK_SIZE = algorithms.AES.block_size // 8
def _create_cipher(iv, key):
"""Create a cipher for use in symmetric encryption/decryption.
This will use AES encryption in CFB mode (using an 8-bit shift register)
and a random IV.
Args:
iv (bytes):
The random IV to use for the cipher.
key (bytes):
The encryption key to use.
Returns:
cryptography.hazmat.primitives.cipher.Cipher:
The cipher to use for encryption/decryption.
Raises:
ValueError:
The encryption key was not in the right format.
"""
if not isinstance(key, bytes):
raise TypeError('The encryption key must be of type "bytes", not "%s"'
% type(key))
return Cipher(algorithms.AES(key),
modes.CFB8(iv),
default_backend())
def get_default_aes_encryption_key():
"""Return the default AES encryption key for the install.
The default key is the first 16 characters (128 bits) of
:django:setting:`SECRET_KEY`.
Returns:
bytes:
The default encryption key.
"""
return settings.SECRET_KEY[:16].encode('utf-8')
def aes_encrypt(data, key=None):
"""Encrypt data using AES encryption.
This uses AES encryption in CFB mode (using an 8-bit shift register) and a
random IV (which will be prepended to the encrypted value). The encrypted
data will be decryptable using the :py:func:`aes_decrypt` function.
Args:
data (bytes):
The data to encrypt. If a unicode string is passed in, it will be
encoded to UTF-8 first.
key (bytes, optional):
The optional custom encryption key to use. If not supplied, the
default encryption key (from
:py:func:`get_default_aes_encryption_key)` will be used.
Returns:
bytes:
The resulting encrypted value, with the random IV prepended.
Raises:
ValueError:
The encryption key was not in the right format.
"""
if isinstance(data, str):
data = data.encode('utf-8')
iv = os.urandom(AES_BLOCK_SIZE)
cipher = _create_cipher(iv, key or get_default_aes_encryption_key())
encryptor = cipher.encryptor()
return iv + encryptor.update(data) + encryptor.finalize()
def aes_decrypt(data, key=None):
"""Decrypt AES-encrypted data.
This will decrypt an AES-encrypted value in CFB mode (using an 8-bit
shift register). It expects the 16-byte cipher IV to be prepended to the
string.
This is intended as a counterpart for :py:func:`aes_encrypt`.
Args:
data (bytes):
The data to decrypt.
key (bytes, optional):
The optional custom encryption key to use. This must match the key
used for encryption. If not supplied, the default encryption key
(from :py:func:`get_default_aes_encryption_key)` will be used.
Returns:
bytes:
The decrypted value.
Raises:
TypeError:
One or more arguments had an invalid type.
ValueError:
The encryption key was not in the right format.
"""
if not isinstance(data, bytes):
raise TypeError('The data to decrypt must be of type "bytes", not "%s"'
% (type(data)))
cipher = _create_cipher(data[:AES_BLOCK_SIZE],
key or get_default_aes_encryption_key())
decryptor = cipher.decryptor()
return decryptor.update(data[AES_BLOCK_SIZE:]) + decryptor.finalize()
def encrypt_password(password, key=None):
"""Encrypt a password and encode as Base64.
The password will be encrypted using AES encryption in CFB mode (using an
8-bit shift register), and serialized into Base64.
Version Changed:
4.0:
The return type has been changed to :py:class:`unicode`, in order to
improve the expected behavior on Python 3.
Args:
password (unicode or bytes):
The password to encrypt. If a unicode string is passed in, it will
be encoded to UTF-8 first.
key (bytes, optional):
The optional custom encryption key to use. If not supplied, the
default encryption key (from
:py:func:`get_default_aes_encryption_key)` will be used.
Returns:
unicode:
The encrypted password encoded in Base64.
Raises:
ValueError:
The encryption key was not in the right format.
"""
return base64.b64encode(aes_encrypt(password, key=key)).decode('utf-8')
def decrypt_password(encrypted_password, key=None):
"""Decrypt an encrypted password encoded in Base64.
This will decrypt a Base64-encoded encrypted password (from
:py:func:`encrypt_password`) into a usable password string.
Version Changed:
4.0:
The return type has been changed to :py:class:`unicode`, in order to
improve the expected behavior on Python 3.
Args:
encrypted_password (unicode or bytes):
The Base64-encoded encrypted password to decrypt.
key (bytes, optional):
The optional custom encryption key to use. This must match the key
used for encryption. If not supplied, the default encryption key
(from :py:func:`get_default_aes_encryption_key)` will be used.
Returns:
unicode:
The resulting password.
Raises:
ValueError:
The encryption key was not in the right format.
"""
return (
aes_decrypt(base64.b64decode(encrypted_password), key=key)
.decode('utf-8')
)