switched to using pycryptodome as it is already an optional dependency of youtube_dl

This commit is contained in:
Matthew Broadway 2021-06-03 20:08:45 +01:00
parent 8291844201
commit 6d6fe8b3d0
No known key found for this signature in database
GPG Key ID: DDC0B82B6896B381
2 changed files with 40 additions and 29 deletions

View File

@ -22,6 +22,14 @@ class MonkeyPatch:
@unittest.skipIf(not CRYPTO_AVAILABLE, 'cryptography library not available') @unittest.skipIf(not CRYPTO_AVAILABLE, 'cryptography library not available')
class TestCookies(unittest.TestCase): class TestCookies(unittest.TestCase):
def test_chrome_cookie_decryptor_linux_derive_key(self):
key = LinuxChromeCookieDecryptor.derive_key('abc')
assert key == b'7\xa1\xec\xd4m\xfcA\xc7\xb19Z\xd0\x19\xdcM\x17'
def test_chrome_cookie_decryptor_mac_derive_key(self):
key = MacChromeCookieDecryptor.derive_key('abc')
assert key == b'Y\xe2\xc0\xd0P\xf6\xf4\xe1l\xc1\x8cQ\xcb|\xcdY'
def test_chrome_cookie_decryptor_linux_v10(self): def test_chrome_cookie_decryptor_linux_v10(self):
with MonkeyPatch(cookies, '_get_linux_keyring_password', lambda *args, **kwargs: ''): with MonkeyPatch(cookies, '_get_linux_keyring_password', lambda *args, **kwargs: ''):
encrypted_value = b'v10\xccW%\xcd\xe6\xe6\x9fM" \xa7\xb0\xca\xe4\x07\xd6' encrypted_value = b'v10\xccW%\xcd\xe6\xe6\x9fM" \xa7\xb0\xca\xe4\x07\xd6'

View File

@ -11,11 +11,9 @@ from youtube_dl.compat import compat_cookiejar_Cookie, compat_b64decode, Compat_
from youtube_dl.utils import YoutubeDLCookieJar, expand_path from youtube_dl.utils import YoutubeDLCookieJar, expand_path
try: try:
from cryptography.hazmat.primitives.ciphers import Cipher from Crypto.Cipher import AES
from cryptography.hazmat.primitives.ciphers.algorithms import AES from Crypto.Protocol.KDF import PBKDF2
from cryptography.hazmat.primitives.ciphers.modes import CBC, GCM from Crypto.Hash import SHA1
from cryptography.hazmat.primitives.hashes import SHA1
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
CRYPTO_AVAILABLE = True CRYPTO_AVAILABLE = True
except ImportError: except ImportError:
CRYPTO_AVAILABLE = False CRYPTO_AVAILABLE = False
@ -197,7 +195,7 @@ class ChromeCookieDecryptor:
Mac: Mac:
- cookies are either v10 or not v10 - cookies are either v10 or not v10
- v10: AES-CBC encrypted (with more iterations than Linux) with an OS protected key (keyring) - v10: AES-CBC encrypted with an OS protected key (keyring) and more key derivation iterations than linux
- not v10: 'old data' stored as plaintext - not v10: 'old data' stored as plaintext
Windows: Windows:
@ -232,15 +230,15 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
self._v10_key = None self._v10_key = None
self._v11_key = None self._v11_key = None
if CRYPTO_AVAILABLE: if CRYPTO_AVAILABLE:
self._v10_key = self.derive_key('peanuts')
if KEYRING_AVAILABLE:
self._v11_key = self.derive_key(_get_linux_keyring_password(browser_keyring_name))
@staticmethod
def derive_key(password):
# values from # values from
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_linux.cc # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_linux.cc
self._v10_key = PBKDF2HMAC(algorithm=SHA1(), length=16, return PBKDF2(password, salt=b'saltysalt', dkLen=16, count=1, hmac_hash_module=SHA1)
salt=b'saltysalt', iterations=1).derive(b'peanuts')
if KEYRING_AVAILABLE:
password = _get_linux_keyring_password(browser_keyring_name)
self._v11_key = PBKDF2HMAC(algorithm=SHA1(), length=16,
salt=b'saltysalt', iterations=1).derive(password.encode('utf-8'))
def decrypt(self, encrypted_value): def decrypt(self, encrypted_value):
version = encrypted_value[:3] version = encrypted_value[:3]
@ -248,13 +246,13 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
if version == b'v10': if version == b'v10':
if self._v10_key is None: if self._v10_key is None:
warnings.warn('cannot decrypt cookie as the cryptography module is not installed') warnings.warn('cannot decrypt cookie as the module `pycryptodome` is not installed')
return None return None
return _decrypt_aes_cbc(ciphertext, self._v10_key) return _decrypt_aes_cbc(ciphertext, self._v10_key)
elif version == b'v11': elif version == b'v11':
if self._v11_key is None: if self._v11_key is None:
warnings.warn('cannot decrypt cookie as the cryptography or keyring modules are not installed') warnings.warn('cannot decrypt cookie as the `pycryptodome` or `keyring` modules are not installed')
return None return None
return _decrypt_aes_cbc(ciphertext, self._v11_key) return _decrypt_aes_cbc(ciphertext, self._v11_key)
@ -266,11 +264,13 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor):
def __init__(self, browser_keyring_name): def __init__(self, browser_keyring_name):
self._v10_key = None self._v10_key = None
if CRYPTO_AVAILABLE: if CRYPTO_AVAILABLE:
self._v10_key = self.derive_key(_get_mac_keyring_password(browser_keyring_name))
@staticmethod
def derive_key(password):
# values from # values from
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_mac.mm # https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_mac.mm
password = _get_mac_keyring_password(browser_keyring_name) return PBKDF2(password, salt=b'saltysalt', dkLen=16, count=1003, hmac_hash_module=SHA1)
self._v10_key = PBKDF2HMAC(algorithm=SHA1(), length=16,
salt=b'saltysalt', iterations=1003).derive(password.encode('utf-8'))
def decrypt(self, encrypted_value): def decrypt(self, encrypted_value):
version = encrypted_value[:3] version = encrypted_value[:3]
@ -278,7 +278,7 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor):
if version == b'v10': if version == b'v10':
if self._v10_key is None: if self._v10_key is None:
warnings.warn('cannot decrypt cookie as the cryptography module is not installed') warnings.warn('cannot decrypt cookie as the `pycryptodome` module is not installed')
return None return None
return _decrypt_aes_cbc(ciphertext, self._v10_key) return _decrypt_aes_cbc(ciphertext, self._v10_key)
@ -372,10 +372,9 @@ def _get_windows_v10_password(browser_root):
return _decrypt_windows_dpapi(encrypted_password[len(prefix):]) return _decrypt_windows_dpapi(encrypted_password[len(prefix):])
def _decrypt_aes_cbc(ciphertext, key): def _decrypt_aes_cbc(ciphertext, key, initialization_vector=b' ' * 16):
cipher = Cipher(algorithm=AES(key), mode=CBC(initialization_vector=b' ' * 16)) cipher = AES.new(key, AES.MODE_CBC, iv=initialization_vector)
decryptor = cipher.decryptor() plaintext = cipher.decrypt(ciphertext)
plaintext = decryptor.update(ciphertext) + decryptor.finalize()
padding_length = compat_ord(plaintext[-1]) padding_length = compat_ord(plaintext[-1])
try: try:
return plaintext[:-padding_length].decode('utf-8') return plaintext[:-padding_length].decode('utf-8')
@ -385,9 +384,13 @@ def _decrypt_aes_cbc(ciphertext, key):
def _decrypt_aes_gcm(ciphertext, key, nonce, authentication_tag): def _decrypt_aes_gcm(ciphertext, key, nonce, authentication_tag):
cipher = Cipher(algorithm=AES(key), mode=GCM(nonce, tag=authentication_tag)) cipher = AES.new(key, AES.MODE_GCM, nonce)
decryptor = cipher.decryptor() try:
plaintext = decryptor.update(ciphertext) + decryptor.finalize() plaintext = cipher.decrypt_and_verify(ciphertext, authentication_tag)
except ValueError:
warnings.warn('failed to decrypt cookie because the MAC check failed. Possibly the key is wrong?')
return None
try: try:
return plaintext.decode('utf-8') return plaintext.decode('utf-8')
except UnicodeDecodeError: except UnicodeDecodeError: