diff --git a/app/helper/cookiecloud.py b/app/helper/cookiecloud.py index b98b51f9..f8e4373e 100644 --- a/app/helper/cookiecloud.py +++ b/app/helper/cookiecloud.py @@ -1,7 +1,11 @@ +import json + from typing import Tuple, Optional +from hashlib import md5 from app.utils.http import RequestUtils from app.utils.string import StringUtils +from app.utils.common import decrypt class CookieCloudHelper: @@ -22,11 +26,25 @@ class CookieCloudHelper: if not self._server or not self._key or not self._password: return None, "CookieCloud参数不正确" req_url = "%s/get/%s" % (self._server, str(self._key).strip()) - ret = self._req.post_res(url=req_url, json={"password": str(self._password).strip()}) + ret = self._req.get_res(url=req_url) if ret and ret.status_code == 200: result = ret.json() if not result: - return {}, "未下载到数据" + return {}, "未下载到数据" + encrypted = result.get("encrypted") + if not encrypted: + return {}, "未获取到cookie密文" + else: + crypt_key = self.get_crypt_key() + try: + decrypted_data = decrypt(encrypted, crypt_key).decode('utf-8') + result = json.loads(decrypted_data) + except Exception as e: + return {}, "cookie解密失败" + str(e) + + if not result: + return {}, "cookie解密为空" + if result.get("cookie_data"): contents = result.get("cookie_data") else: @@ -66,3 +84,11 @@ class CookieCloudHelper: return None, f"同步CookieCloud失败,错误码:{ret.status_code}" else: return None, "CookieCloud请求失败,请检查服务器地址、用户KEY及加密密码是否正确" + + def get_crypt_key(self) -> bytes: + """ + 使用UUID和密码生成CookieCloud的加解密密钥 + """ + md5_generator = md5() + md5_generator.update((str(self._key).strip() + '-' + str(self._password).strip()).encode('utf-8')) + return (md5_generator.hexdigest()[:16]).encode('utf-8') diff --git a/app/utils/common.py b/app/utils/common.py index 606fef6d..10f5298b 100644 --- a/app/utils/common.py +++ b/app/utils/common.py @@ -1,5 +1,10 @@ import time +import base64 + from typing import Any +from Crypto import Random +from Crypto.Cipher import AES +from hashlib import md5 def retry(ExceptionToCheck: Any, @@ -32,3 +37,48 @@ def retry(ExceptionToCheck: Any, return f_retry return deco_retry + + +def bytes_to_key(data: bytes, salt: bytes, output=48) -> bytes: + # extended from https://gist.github.com/gsakkis/4546068 + assert len(salt) == 8, len(salt) + data += salt + key = md5(data).digest() + final_key = key + while len(final_key) < output: + key = md5(key + data).digest() + final_key += key + return final_key[:output] + + +def encrypt(message: bytes, passphrase: bytes) -> bytes: + """ + CryptoJS 加密原文 + + This is a modified copy of https://stackoverflow.com/questions/36762098/how-to-decrypt-password-from-javascript-cryptojs-aes-encryptpassword-passphras + """ + salt = Random.new().read(8) + key_iv = bytes_to_key(passphrase, salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + length = 16 - (len(message) % 16) + data = message + (chr(length) * length).encode() + return base64.b64encode(b"Salted__" + salt + aes.encrypt(data)) + + +def decrypt(encrypted: str | bytes, passphrase: bytes) -> bytes: + """ + CryptoJS 解密密文 + + 来源同encrypt + """ + encrypted = base64.b64decode(encrypted) + assert encrypted[0:8] == b"Salted__" + salt = encrypted[8:16] + key_iv = bytes_to_key(passphrase, salt, 32 + 16) + key = key_iv[:32] + iv = key_iv[32:] + aes = AES.new(key, AES.MODE_CBC, iv) + data = aes.decrypt(encrypted[16:]) + return data[:-(data[-1] if type(data[-1]) == int else ord(data[-1]))]