Skip to content

Commit 5ca7ab8

Browse files
authored
Merge pull request luojilab#16 from luojilab/develop
feat: add CBC mode
2 parents 63515bd + 868dc01 commit 5ca7ab8

File tree

6 files changed

+99
-34
lines changed

6 files changed

+99
-34
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ build
88
client/out
99
client/client.iml
1010
client/java/META-INF
11+
__pycache__

README.md

+18-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ A Django model field that encrypt your data when save to and decrypt when get fr
1111
* Use settings.SECRET_KEY as secret key default or anyelse which length >= 32
1212
* Support CharField、TextField、IntegerField、EmailField
1313
* Support Django ORM's `get()``filter()` query method
14-
* Use AES-256-ECB algorithm
14+
* Support AES-256-ECB/AES-256-CBC
1515
* Support PostgreSQL and MySQL database
1616
* Support Django model field `db_index` and `unique` attributes
1717

@@ -52,10 +52,25 @@ c.decrypt('-bYijegsEDrmS1s7ilnspA==') # some_address
5252

5353
### Settings
5454

55+
#### MIRAGE_SECRET_KEY
56+
5557
You can use the `settings.SECRET_KEY` as default key, if you want custom another key for mirage, set the `MIRAGE_SECRET_KEY` in settings.
5658

5759
Mirage will get the `settings.MIRAGE_SECRET_KEY` first, if not set, mirage will get the `settings.SECRET_KEY`.
5860

61+
#### MIRAGE_CIPHER_MODE
62+
63+
`MIRAGE_CIPHER_MODE` is optional, choices are:
64+
65+
- `ECB`
66+
- `CBC`
67+
68+
If you don't set, default is `ECB`.
69+
70+
#### MIRAGE_CIPHER_IV
71+
72+
`MIRAGE_CIPHER_IV` is optional, if you don't set, it will use a default: "1234567890abcdef", it's length must be 16.
73+
5974

6075
## Model Fields
6176

@@ -113,6 +128,8 @@ from mirage import exceptions
113128

114129
## Performance
115130

131+
### With ECB mode
132+
116133
Migrate data: 6000,000 columns takes 40 minutes, Average 1 column/2.5ms
117134

118135
Only encrypt/decrypt: Average 1 value/ms

mirage/crypto.py

+58-23
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,79 @@
55
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
66

77
from django.conf import settings
8-
from django.utils.encoding import force_bytes, force_text
8+
from django.utils.encoding import force_bytes, force_str
99

1010

11+
class BaseCipher:
12+
def __init__(self, key, iv) -> None:
13+
self.key = key
14+
self.iv = iv
15+
16+
def encrypt(self, text) -> str:
17+
return text
18+
19+
def decrypt(self, encrypted) -> str:
20+
return encrypted
21+
22+
23+
class ECBCipher(BaseCipher):
24+
25+
def encrypt(self, text):
26+
encryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).encryptor()
27+
padder = padding.PKCS7(algorithms.AES(self.key).block_size).padder()
28+
padded_data = padder.update(force_bytes(text)) + padder.finalize()
29+
encrypted_text = encryptor.update(padded_data) + encryptor.finalize()
30+
return force_str(base64.urlsafe_b64encode(encrypted_text))
31+
32+
def decrypt(self, encrypted):
33+
decryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).decryptor()
34+
padder = padding.PKCS7(algorithms.AES(self.key).block_size).unpadder()
35+
decrypted_text = decryptor.update(base64.urlsafe_b64decode(encrypted))
36+
unpadded_text = padder.update(decrypted_text) + padder.finalize()
37+
return force_str(unpadded_text)
38+
39+
class CBCCipher(BaseCipher):
40+
41+
def encrypt(self, text) -> str:
42+
encryptor = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), default_backend()).encryptor()
43+
padder = padding.PKCS7(algorithms.AES(self.key).block_size).padder()
44+
padded_data = padder.update(force_bytes(text)) + padder.finalize()
45+
encrypted_text = encryptor.update(padded_data) + encryptor.finalize()
46+
return force_str(base64.urlsafe_b64encode(encrypted_text))
47+
48+
def decrypt(self, encrypted) -> str:
49+
decryptor = Cipher(algorithms.AES(self.key), modes.CBC(self.iv), default_backend()).decryptor()
50+
padder = padding.PKCS7(algorithms.AES(self.key).block_size).unpadder()
51+
decrypted_text = decryptor.update(base64.urlsafe_b64decode(encrypted))
52+
unpadded_text = padder.update(decrypted_text) + padder.finalize()
53+
return force_str(unpadded_text)
54+
1155
class Crypto:
1256

13-
def __init__(self, key=None):
57+
def __init__(self, key=None, mode=None, iv=None):
1458
if key is None:
1559
key = getattr(settings, "MIRAGE_SECRET_KEY", None) or getattr(settings, "SECRET_KEY")
1660
assert len(key) >= 32, "mirage key length must more than 32!"
17-
self.key = base64.urlsafe_b64encode(force_bytes(key))[:32]
61+
key = base64.urlsafe_b64encode(force_bytes(key))[:32]
62+
if mode is None:
63+
mode = getattr(settings, "MIRAGE_CIPHER_MODE", "ECB")
64+
if iv is None:
65+
iv=getattr(settings, "MIRAGE_CIPHER_IV", "1234567890abcdef")
66+
self.cipher = eval(f"{mode}Cipher")(key=key, iv=force_bytes(iv))
1867

1968
def encrypt(self, text):
2069
if text is None:
2170
return None
2271
try:
23-
self.try_decrypt(text)
72+
self.cipher.decrypt(text)
2473
return text
2574
except Exception:
26-
return self.try_encrypt(text)
27-
28-
def try_encrypt(self, text):
29-
encryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).encryptor()
30-
padder = padding.PKCS7(algorithms.AES(self.key).block_size).padder()
31-
padded_data = padder.update(force_bytes(text)) + padder.finalize()
32-
encrypted_text = encryptor.update(padded_data) + encryptor.finalize()
33-
return force_text(base64.urlsafe_b64encode(encrypted_text))
34-
35-
def try_decrypt(self, encrypted_text):
36-
decryptor = Cipher(algorithms.AES(self.key), modes.ECB(), default_backend()).decryptor()
37-
padder = padding.PKCS7(algorithms.AES(self.key).block_size).unpadder()
38-
decrypted_text = decryptor.update(base64.urlsafe_b64decode(encrypted_text))
39-
unpadded_text = padder.update(decrypted_text) + padder.finalize()
40-
return force_text(unpadded_text)
75+
return self.cipher.encrypt(text)
4176

42-
def decrypt(self, encrypted_text):
43-
if encrypted_text is None:
77+
def decrypt(self, encrypted):
78+
if encrypted is None:
4479
return None
4580
try:
46-
return self.try_decrypt(encrypted_text)
81+
return self.cipher.decrypt(encrypted)
4782
except Exception:
48-
return encrypted_text
83+
return encrypted

setup.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
88
setup(
99
name='django-mirage-field',
10-
version='1.1.8',
10+
version='2.0.0.beta1',
1111
install_requires=[
1212
"cryptography",
1313
"tqdm",

tests/settings/base.py

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
SECRET_KEY = "ZGphbmdvLW1pcmFnZS1maWVsZCBoYXZlIGZ1bg=="
33

44
MIRAGE_SECRET_KEY = "MIRAGE_SECRET_KEYMIRAGE_SECRET_KEY"
5+
MIRAGE_CIPHER_MODE = "CBC"
6+
MIRAGE_CIPHER_IV = "1234567890abcdef"
57

68
INSTALLED_APPS = [
79
'django.contrib.auth',

tests/test_crypto.py

+19-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
from django.test import TestCase
2-
from django.conf import settings
32
from mirage.crypto import Crypto
43

54

6-
class TestCrypto(TestCase):
5+
class TestCryptoECB(TestCase):
76

87
def setUp(self):
9-
self.crypto = Crypto()
8+
self.crypto = Crypto(mode='ECB')
109
self.value = 'hello,text'
11-
if getattr(settings, "MIRAGE_SECRET_KEY", None):
12-
self.encrypted = "4DIIbNsZPqO1DuXX1GjpkQ=="
13-
else:
14-
self.encrypted = 'pyy1FL2ftjBjUrJlGjgl3g=='
10+
self.encrypted = "4DIIbNsZPqO1DuXX1GjpkQ=="
1511

16-
def test_encrypt(self):
12+
def test_ecb_encrypt(self):
1713
self.assertEqual(self.crypto.encrypt(self.value), self.encrypted)
1814

19-
def test_decrypt(self):
15+
def test_ecb_decrypt(self):
16+
self.assertEqual(self.crypto.decrypt(self.encrypted), self.value)
17+
18+
19+
class TestCryptoCBC(TestCase):
20+
21+
def setUp(self) -> None:
22+
self.crypto = Crypto(mode='CBC')
23+
self.value = 'hello,text'
24+
self.encrypted = "E_RFOSafjW-FQ-PDkXkv5g=="
25+
26+
def test_cbc_encrypt(self):
27+
self.assertEqual(self.crypto.encrypt(self.value), self.encrypted)
28+
29+
def test_cbc_decrypt(self):
2030
self.assertEqual(self.crypto.decrypt(self.encrypted), self.value)

0 commit comments

Comments
 (0)