Skip to content

Commit d0388b0

Browse files
authored
Add ItsDangerous request parameters encryption (#203)
* Add ItsDangerous request parameters encryption * fix typo * update AES decryption returned as a string
1 parent d9ffe20 commit d0388b0

File tree

5 files changed

+60
-15
lines changed

5 files changed

+60
-15
lines changed

backend/app/common/enums.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,8 @@ class OperaLogCipherType(IntEnum):
6969

7070
aes = 0
7171
md5 = 1
72-
plan = 2
72+
itsdangerous = 2
73+
plan = 3
7374

7475

7576
class StatusType(IntEnum):

backend/app/core/conf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ def validator_api_url(cls, values):
141141
OPENAPI_URL,
142142
f'{API_V1_STR}/auth/swagger_login',
143143
]
144-
OPERA_LOG_ENCRYPT: int = 1 # 请求入参加密, 0: AES (高性能损耗), 1: md5, 2: 不加密, other: 替换为 ******
144+
OPERA_LOG_ENCRYPT: int = 1 # 0: AES (性能损耗); 1: md5; 2: ItsDangerous; 3: 不加密, others: 替换为 ******
145145
OPERA_LOG_ENCRYPT_INCLUDE: list[str] = ['password', 'old_password', 'new_password', 'confirm_password']
146146

147147
class Config:

backend/app/middleware/opera_log_middleware.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from backend.app.core.conf import settings
1414
from backend.app.schemas.opera_log import CreateOperaLog
1515
from backend.app.services.opera_log_service import OperaLogService
16-
from backend.app.utils.encrypt import AESCipher, Md5Cipher
16+
from backend.app.utils.encrypt import AESCipher, Md5Cipher, ItsDCipher
1717
from backend.app.utils.request_parse import parse_user_agent_info, parse_ip_info
1818
from backend.app.utils.timezone import timezone_utils
1919

@@ -176,15 +176,15 @@ def desensitization(args: dict):
176176
case OperaLogCipherType.aes:
177177
for key in args.keys():
178178
if key in settings.OPERA_LOG_ENCRYPT_INCLUDE:
179-
args[key] = (
180-
AESCipher(settings.OPERA_LOG_ENCRYPT_SECRET_KEY).encrypt(
181-
bytes(args[key], encoding='utf-8')
182-
)
183-
).hex()
179+
args[key] = (AESCipher(settings.OPERA_LOG_ENCRYPT_SECRET_KEY).encrypt(args[key])).hex()
184180
case OperaLogCipherType.md5:
185181
for key in args.keys():
186182
if key in settings.OPERA_LOG_ENCRYPT_INCLUDE:
187183
args[key] = Md5Cipher.encrypt(args[key])
184+
case OperaLogCipherType.itsdangerous:
185+
for key in args.keys():
186+
if key in settings.OPERA_LOG_ENCRYPT_INCLUDE:
187+
args[key] = ItsDCipher(settings.OPERA_LOG_ENCRYPT_SECRET_KEY).encrypt(args[key])
188188
case OperaLogCipherType.plan:
189189
pass
190190
case _:

backend/app/utils/encrypt.py

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
#!/usr/bin/env python3
22
# -*- coding: utf-8 -*-
33
import os
4+
from typing import Any
45

56
from cryptography.hazmat.backends.openssl import backend
67
from cryptography.hazmat.primitives import padding
78
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
9+
from itsdangerous import URLSafeSerializer
10+
11+
from backend.app.common.log import log
812

913

1014
class AESCipher:
@@ -14,13 +18,15 @@ def __init__(self, key: bytes | str):
1418
"""
1519
self.key = key if isinstance(key, bytes) else bytes.fromhex(key)
1620

17-
def encrypt(self, plaintext: bytes) -> bytes:
21+
def encrypt(self, plaintext: bytes | str) -> bytes:
1822
"""
1923
AES 加密
2024
2125
:param plaintext: 加密前的明文
2226
:return:
2327
"""
28+
if not isinstance(plaintext, bytes):
29+
plaintext = str(plaintext).encode('utf-8')
2430
iv = os.urandom(16)
2531
cipher = Cipher(algorithms.AES(self.key), modes.CBC(iv), backend=backend)
2632
encryptor = cipher.encryptor()
@@ -29,7 +35,7 @@ def encrypt(self, plaintext: bytes) -> bytes:
2935
ciphertext = encryptor.update(padded_plaintext) + encryptor.finalize()
3036
return iv + ciphertext
3137

32-
def decrypt(self, ciphertext: bytes | str) -> bytes:
38+
def decrypt(self, ciphertext: bytes | str) -> str:
3339
"""
3440
AES 解密
3541
@@ -44,7 +50,7 @@ def decrypt(self, ciphertext: bytes | str) -> bytes:
4450
unpadder = padding.PKCS7(cipher.algorithm.block_size).unpadder() # type: ignore
4551
padded_plaintext = decryptor.update(ciphertext) + decryptor.finalize()
4652
plaintext = unpadder.update(padded_plaintext) + unpadder.finalize()
47-
return plaintext
53+
return plaintext.decode('utf-8')
4854

4955

5056
class Md5Cipher:
@@ -59,8 +65,45 @@ def encrypt(plaintext: bytes | str) -> str:
5965
import hashlib
6066

6167
md5 = hashlib.md5()
62-
if isinstance(plaintext, str):
63-
md5.update(plaintext.encode('utf-8'))
64-
else:
65-
md5.update(plaintext)
68+
if not isinstance(plaintext, bytes):
69+
plaintext = str(plaintext).encode('utf-8')
70+
md5.update(plaintext)
6671
return md5.hexdigest()
72+
73+
74+
class ItsDCipher:
75+
def __init__(self, key: bytes | str):
76+
"""
77+
:param key: 密钥,16/24/32 bytes 或 16 进制字符串
78+
"""
79+
self.key = key if isinstance(key, bytes) else bytes.fromhex(key)
80+
81+
def encrypt(self, plaintext: Any) -> str:
82+
"""
83+
ItsDangerous 加密 (可能失败,如果 plaintext 无法序列化,则会加密为 MD5)
84+
85+
:param plaintext: 加密前的明文
86+
:return:
87+
"""
88+
serializer = URLSafeSerializer(self.key)
89+
try:
90+
ciphertext = serializer.dumps(plaintext)
91+
except Exception as e:
92+
log.error(f'ItsDangerous encrypt failed: {e}')
93+
ciphertext = Md5Cipher.encrypt(plaintext)
94+
return ciphertext
95+
96+
def decrypt(self, ciphertext: str) -> Any:
97+
"""
98+
ItsDangerous 解密 (可能失败,如果 ciphertext 无法反序列化,则解密失败, 返回原始密文)
99+
100+
:param ciphertext: 解密前的密文
101+
:return:
102+
"""
103+
serializer = URLSafeSerializer(self.key)
104+
try:
105+
plaintext = serializer.loads(ciphertext)
106+
except Exception as e:
107+
log.error(f'ItsDangerous decrypt failed: {e}')
108+
plaintext = ciphertext
109+
return plaintext

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ fastapi-limiter==0.1.5
1616
fastapi-pagination==0.12.1
1717
gunicorn==20.1.0
1818
httpx==0.23.0
19+
itsdangerous==2.1.2
1920
loguru==0.6.0
2021
passlib==1.7.4
2122
path==15.1.2

0 commit comments

Comments
 (0)