|
|
|
@ -353,7 +353,9 @@ class ChromeCookieDecryptor:
|
|
|
|
|
Linux:
|
|
|
|
|
- cookies are either v10 or v11
|
|
|
|
|
- v10: AES-CBC encrypted with a fixed key
|
|
|
|
|
- also attempts empty password if decryption fails
|
|
|
|
|
- v11: AES-CBC encrypted with an OS protected key (keyring)
|
|
|
|
|
- also attempts empty password if decryption fails
|
|
|
|
|
- v11 keys can be stored in various places depending on the activate desktop environment [2]
|
|
|
|
|
|
|
|
|
|
Mac:
|
|
|
|
@ -368,7 +370,7 @@ class ChromeCookieDecryptor:
|
|
|
|
|
|
|
|
|
|
Sources:
|
|
|
|
|
- [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/
|
|
|
|
|
- [2] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_linux.cc
|
|
|
|
|
- [2] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_linux.cc
|
|
|
|
|
- KeyStorageLinux::CreateService
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
@ -390,6 +392,7 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
def __init__(self, browser_keyring_name, logger, *, keyring=None):
|
|
|
|
|
self._logger = logger
|
|
|
|
|
self._v10_key = self.derive_key(b'peanuts')
|
|
|
|
|
self._empty_key = self.derive_key(b'')
|
|
|
|
|
self._cookie_counts = {'v10': 0, 'v11': 0, 'other': 0}
|
|
|
|
|
self._browser_keyring_name = browser_keyring_name
|
|
|
|
|
self._keyring = keyring
|
|
|
|
@ -402,25 +405,36 @@ class LinuxChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
@staticmethod
|
|
|
|
|
def derive_key(password):
|
|
|
|
|
# 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/sync/os_crypt_linux.cc
|
|
|
|
|
return pbkdf2_sha1(password, salt=b'saltysalt', iterations=1, key_length=16)
|
|
|
|
|
|
|
|
|
|
def decrypt(self, encrypted_value):
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
following the same approach as the fix in [1]: if cookies fail to decrypt then attempt to decrypt
|
|
|
|
|
with an empty password. The failure detection is not the same as what chromium uses so the
|
|
|
|
|
results won't be perfect
|
|
|
|
|
|
|
|
|
|
References:
|
|
|
|
|
- [1] https://chromium.googlesource.com/chromium/src/+/bbd54702284caca1f92d656fdcadf2ccca6f4165%5E%21/
|
|
|
|
|
- a bugfix to try an empty password as a fallback
|
|
|
|
|
"""
|
|
|
|
|
version = encrypted_value[:3]
|
|
|
|
|
ciphertext = encrypted_value[3:]
|
|
|
|
|
|
|
|
|
|
if version == b'v10':
|
|
|
|
|
self._cookie_counts['v10'] += 1
|
|
|
|
|
return _decrypt_aes_cbc(ciphertext, self._v10_key, self._logger)
|
|
|
|
|
return _decrypt_aes_cbc_multi(ciphertext, (self._v10_key, self._empty_key), self._logger)
|
|
|
|
|
|
|
|
|
|
elif version == b'v11':
|
|
|
|
|
self._cookie_counts['v11'] += 1
|
|
|
|
|
if self._v11_key is None:
|
|
|
|
|
self._logger.warning('cannot decrypt v11 cookies: no key found', only_once=True)
|
|
|
|
|
return None
|
|
|
|
|
return _decrypt_aes_cbc(ciphertext, self._v11_key, self._logger)
|
|
|
|
|
return _decrypt_aes_cbc_multi(ciphertext, (self._v11_key, self._empty_key), self._logger)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self._logger.warning(f'unknown cookie version: "{version}"', only_once=True)
|
|
|
|
|
self._cookie_counts['other'] += 1
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
@ -435,7 +449,7 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
@staticmethod
|
|
|
|
|
def derive_key(password):
|
|
|
|
|
# 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/sync/os_crypt_mac.mm
|
|
|
|
|
return pbkdf2_sha1(password, salt=b'saltysalt', iterations=1003, key_length=16)
|
|
|
|
|
|
|
|
|
|
def decrypt(self, encrypted_value):
|
|
|
|
@ -448,12 +462,12 @@ class MacChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
self._logger.warning('cannot decrypt v10 cookies: no key found', only_once=True)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
return _decrypt_aes_cbc(ciphertext, self._v10_key, self._logger)
|
|
|
|
|
return _decrypt_aes_cbc_multi(ciphertext, (self._v10_key,), self._logger)
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
self._cookie_counts['other'] += 1
|
|
|
|
|
# other prefixes are considered 'old data' which were stored as plaintext
|
|
|
|
|
# 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/sync/os_crypt_mac.mm
|
|
|
|
|
return encrypted_value
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -473,7 +487,7 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
self._logger.warning('cannot decrypt v10 cookies: no key found', only_once=True)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_win.cc
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc
|
|
|
|
|
# kNonceLength
|
|
|
|
|
nonce_length = 96 // 8
|
|
|
|
|
# boringssl
|
|
|
|
@ -490,7 +504,7 @@ class WindowsChromeCookieDecryptor(ChromeCookieDecryptor):
|
|
|
|
|
else:
|
|
|
|
|
self._cookie_counts['other'] += 1
|
|
|
|
|
# any other prefix means the data is DPAPI encrypted
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/os_crypt_win.cc
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc
|
|
|
|
|
return _decrypt_windows_dpapi(encrypted_value, self._logger).decode()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -673,27 +687,35 @@ class _LinuxDesktopEnvironment(Enum):
|
|
|
|
|
"""
|
|
|
|
|
OTHER = auto()
|
|
|
|
|
CINNAMON = auto()
|
|
|
|
|
DEEPIN = auto()
|
|
|
|
|
GNOME = auto()
|
|
|
|
|
KDE = auto()
|
|
|
|
|
KDE3 = auto()
|
|
|
|
|
KDE4 = auto()
|
|
|
|
|
KDE5 = auto()
|
|
|
|
|
KDE6 = auto()
|
|
|
|
|
PANTHEON = auto()
|
|
|
|
|
UKUI = auto()
|
|
|
|
|
UNITY = auto()
|
|
|
|
|
XFCE = auto()
|
|
|
|
|
LXQT = auto()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class _LinuxKeyring(Enum):
|
|
|
|
|
"""
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_util_linux.h
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_util_linux.h
|
|
|
|
|
SelectedLinuxBackend
|
|
|
|
|
"""
|
|
|
|
|
KWALLET = auto()
|
|
|
|
|
GNOMEKEYRING = auto()
|
|
|
|
|
BASICTEXT = auto()
|
|
|
|
|
KWALLET4 = auto() # this value is just called KWALLET in the chromium source but it is for KDE4 only
|
|
|
|
|
KWALLET5 = auto()
|
|
|
|
|
KWALLET6 = auto()
|
|
|
|
|
GNOME_KEYRING = auto()
|
|
|
|
|
BASIC_TEXT = auto()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
SUPPORTED_KEYRINGS = _LinuxKeyring.__members__.keys()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_linux_desktop_environment(env):
|
|
|
|
|
def _get_linux_desktop_environment(env, logger):
|
|
|
|
|
"""
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/base/nix/xdg_util.cc
|
|
|
|
|
GetDesktopEnvironment
|
|
|
|
@ -708,51 +730,97 @@ def _get_linux_desktop_environment(env):
|
|
|
|
|
return _LinuxDesktopEnvironment.GNOME
|
|
|
|
|
else:
|
|
|
|
|
return _LinuxDesktopEnvironment.UNITY
|
|
|
|
|
elif xdg_current_desktop == 'Deepin':
|
|
|
|
|
return _LinuxDesktopEnvironment.DEEPIN
|
|
|
|
|
elif xdg_current_desktop == 'GNOME':
|
|
|
|
|
return _LinuxDesktopEnvironment.GNOME
|
|
|
|
|
elif xdg_current_desktop == 'X-Cinnamon':
|
|
|
|
|
return _LinuxDesktopEnvironment.CINNAMON
|
|
|
|
|
elif xdg_current_desktop == 'KDE':
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE
|
|
|
|
|
kde_version = env.get('KDE_SESSION_VERSION', None)
|
|
|
|
|
if kde_version == '5':
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE5
|
|
|
|
|
elif kde_version == '6':
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE6
|
|
|
|
|
elif kde_version == '4':
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE4
|
|
|
|
|
else:
|
|
|
|
|
logger.info(f'unknown KDE version: "{kde_version}". Assuming KDE4')
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE4
|
|
|
|
|
elif xdg_current_desktop == 'Pantheon':
|
|
|
|
|
return _LinuxDesktopEnvironment.PANTHEON
|
|
|
|
|
elif xdg_current_desktop == 'XFCE':
|
|
|
|
|
return _LinuxDesktopEnvironment.XFCE
|
|
|
|
|
elif xdg_current_desktop == 'UKUI':
|
|
|
|
|
return _LinuxDesktopEnvironment.UKUI
|
|
|
|
|
elif xdg_current_desktop == 'LXQt':
|
|
|
|
|
return _LinuxDesktopEnvironment.LXQT
|
|
|
|
|
else:
|
|
|
|
|
logger.info(f'XDG_CURRENT_DESKTOP is set to an unknown value: "{xdg_current_desktop}"')
|
|
|
|
|
|
|
|
|
|
elif desktop_session is not None:
|
|
|
|
|
if desktop_session in ('mate', 'gnome'):
|
|
|
|
|
if desktop_session == 'deepin':
|
|
|
|
|
return _LinuxDesktopEnvironment.DEEPIN
|
|
|
|
|
elif desktop_session in ('mate', 'gnome'):
|
|
|
|
|
return _LinuxDesktopEnvironment.GNOME
|
|
|
|
|
elif 'kde' in desktop_session:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE
|
|
|
|
|
elif 'xfce' in desktop_session:
|
|
|
|
|
elif desktop_session in ('kde4', 'kde-plasma'):
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE4
|
|
|
|
|
elif desktop_session == 'kde':
|
|
|
|
|
if 'KDE_SESSION_VERSION' in env:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE4
|
|
|
|
|
else:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE3
|
|
|
|
|
elif 'xfce' in desktop_session or desktop_session == 'xubuntu':
|
|
|
|
|
return _LinuxDesktopEnvironment.XFCE
|
|
|
|
|
elif desktop_session == 'ukui':
|
|
|
|
|
return _LinuxDesktopEnvironment.UKUI
|
|
|
|
|
else:
|
|
|
|
|
logger.info(f'DESKTOP_SESSION is set to an unknown value: "{desktop_session}"')
|
|
|
|
|
|
|
|
|
|
else:
|
|
|
|
|
if 'GNOME_DESKTOP_SESSION_ID' in env:
|
|
|
|
|
return _LinuxDesktopEnvironment.GNOME
|
|
|
|
|
elif 'KDE_FULL_SESSION' in env:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE
|
|
|
|
|
if 'KDE_SESSION_VERSION' in env:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE4
|
|
|
|
|
else:
|
|
|
|
|
return _LinuxDesktopEnvironment.KDE3
|
|
|
|
|
return _LinuxDesktopEnvironment.OTHER
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _choose_linux_keyring(logger):
|
|
|
|
|
"""
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/key_storage_util_linux.cc
|
|
|
|
|
SelectBackend
|
|
|
|
|
SelectBackend in [1]
|
|
|
|
|
|
|
|
|
|
There is currently support for forcing chromium to use BASIC_TEXT by creating a file called
|
|
|
|
|
`Disable Local Encryption` [1] in the user data dir. The function to write this file (`WriteBackendUse()` [1])
|
|
|
|
|
does not appear to be called anywhere other than in tests, so the user would have to create this file manually
|
|
|
|
|
and so would be aware enough to tell yt-dlp to use the BASIC_TEXT keyring.
|
|
|
|
|
|
|
|
|
|
References:
|
|
|
|
|
- [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/key_storage_util_linux.cc
|
|
|
|
|
"""
|
|
|
|
|
desktop_environment = _get_linux_desktop_environment(os.environ)
|
|
|
|
|
desktop_environment = _get_linux_desktop_environment(os.environ, logger)
|
|
|
|
|
logger.debug(f'detected desktop environment: {desktop_environment.name}')
|
|
|
|
|
if desktop_environment == _LinuxDesktopEnvironment.KDE:
|
|
|
|
|
linux_keyring = _LinuxKeyring.KWALLET
|
|
|
|
|
elif desktop_environment == _LinuxDesktopEnvironment.OTHER:
|
|
|
|
|
linux_keyring = _LinuxKeyring.BASICTEXT
|
|
|
|
|
if desktop_environment == _LinuxDesktopEnvironment.KDE4:
|
|
|
|
|
linux_keyring = _LinuxKeyring.KWALLET4
|
|
|
|
|
elif desktop_environment == _LinuxDesktopEnvironment.KDE5:
|
|
|
|
|
linux_keyring = _LinuxKeyring.KWALLET5
|
|
|
|
|
elif desktop_environment == _LinuxDesktopEnvironment.KDE6:
|
|
|
|
|
linux_keyring = _LinuxKeyring.KWALLET6
|
|
|
|
|
elif desktop_environment in (
|
|
|
|
|
_LinuxDesktopEnvironment.KDE3, _LinuxDesktopEnvironment.LXQT, _LinuxDesktopEnvironment.OTHER
|
|
|
|
|
):
|
|
|
|
|
linux_keyring = _LinuxKeyring.BASIC_TEXT
|
|
|
|
|
else:
|
|
|
|
|
linux_keyring = _LinuxKeyring.GNOMEKEYRING
|
|
|
|
|
linux_keyring = _LinuxKeyring.GNOME_KEYRING
|
|
|
|
|
return linux_keyring
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_kwallet_network_wallet(logger):
|
|
|
|
|
def _get_kwallet_network_wallet(keyring, logger):
|
|
|
|
|
""" The name of the wallet used to store network passwords.
|
|
|
|
|
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/kwallet_dbus.cc
|
|
|
|
|
https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/kwallet_dbus.cc
|
|
|
|
|
KWalletDBus::NetworkWallet
|
|
|
|
|
which does a dbus call to the following function:
|
|
|
|
|
https://api.kde.org/frameworks/kwallet/html/classKWallet_1_1Wallet.html
|
|
|
|
@ -760,10 +828,22 @@ def _get_kwallet_network_wallet(logger):
|
|
|
|
|
"""
|
|
|
|
|
default_wallet = 'kdewallet'
|
|
|
|
|
try:
|
|
|
|
|
if keyring == _LinuxKeyring.KWALLET4:
|
|
|
|
|
service_name = 'org.kde.kwalletd'
|
|
|
|
|
wallet_path = '/modules/kwalletd'
|
|
|
|
|
elif keyring == _LinuxKeyring.KWALLET5:
|
|
|
|
|
service_name = 'org.kde.kwalletd5'
|
|
|
|
|
wallet_path = '/modules/kwalletd5'
|
|
|
|
|
elif keyring == _LinuxKeyring.KWALLET6:
|
|
|
|
|
service_name = 'org.kde.kwalletd6'
|
|
|
|
|
wallet_path = '/modules/kwalletd6'
|
|
|
|
|
else:
|
|
|
|
|
raise ValueError(keyring)
|
|
|
|
|
|
|
|
|
|
stdout, _, returncode = Popen.run([
|
|
|
|
|
'dbus-send', '--session', '--print-reply=literal',
|
|
|
|
|
'--dest=org.kde.kwalletd5',
|
|
|
|
|
'/modules/kwalletd5',
|
|
|
|
|
f'--dest={service_name}',
|
|
|
|
|
wallet_path,
|
|
|
|
|
'org.kde.KWallet.networkWallet'
|
|
|
|
|
], text=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
|
|
|
|
|
|
|
|
|
@ -778,8 +858,8 @@ def _get_kwallet_network_wallet(logger):
|
|
|
|
|
return default_wallet
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_kwallet_password(browser_keyring_name, logger):
|
|
|
|
|
logger.debug('using kwallet-query to obtain password from kwallet')
|
|
|
|
|
def _get_kwallet_password(browser_keyring_name, keyring, logger):
|
|
|
|
|
logger.debug(f'using kwallet-query to obtain password from {keyring.name}')
|
|
|
|
|
|
|
|
|
|
if shutil.which('kwallet-query') is None:
|
|
|
|
|
logger.error('kwallet-query command not found. KWallet and kwallet-query '
|
|
|
|
@ -787,7 +867,7 @@ def _get_kwallet_password(browser_keyring_name, logger):
|
|
|
|
|
'included in the kwallet package for your distribution')
|
|
|
|
|
return b''
|
|
|
|
|
|
|
|
|
|
network_wallet = _get_kwallet_network_wallet(logger)
|
|
|
|
|
network_wallet = _get_kwallet_network_wallet(keyring, logger)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
stdout, _, returncode = Popen.run([
|
|
|
|
@ -809,8 +889,9 @@ def _get_kwallet_password(browser_keyring_name, logger):
|
|
|
|
|
# checks hasEntry. To verify this:
|
|
|
|
|
# dbus-monitor "interface='org.kde.KWallet'" "type=method_return"
|
|
|
|
|
# while starting chrome.
|
|
|
|
|
# this may be a bug as the intended behaviour is to generate a random password and store
|
|
|
|
|
# it, but that doesn't matter here.
|
|
|
|
|
# this was identified as a bug later and fixed in
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/bbd54702284caca1f92d656fdcadf2ccca6f4165%5E%21/#F0
|
|
|
|
|
# https://chromium.googlesource.com/chromium/src/+/5463af3c39d7f5b6d11db7fbd51e38cc1974d764
|
|
|
|
|
return b''
|
|
|
|
|
else:
|
|
|
|
|
logger.debug('password found')
|
|
|
|
@ -848,11 +929,11 @@ def _get_linux_keyring_password(browser_keyring_name, keyring, logger):
|
|
|
|
|
keyring = _LinuxKeyring[keyring] if keyring else _choose_linux_keyring(logger)
|
|
|
|
|
logger.debug(f'Chosen keyring: {keyring.name}')
|
|
|
|
|
|
|
|
|
|
if keyring == _LinuxKeyring.KWALLET:
|
|
|
|
|
return _get_kwallet_password(browser_keyring_name, logger)
|
|
|
|
|
elif keyring == _LinuxKeyring.GNOMEKEYRING:
|
|
|
|
|
if keyring in (_LinuxKeyring.KWALLET4, _LinuxKeyring.KWALLET5, _LinuxKeyring.KWALLET6):
|
|
|
|
|
return _get_kwallet_password(browser_keyring_name, keyring, logger)
|
|
|
|
|
elif keyring == _LinuxKeyring.GNOME_KEYRING:
|
|
|
|
|
return _get_gnome_keyring_password(browser_keyring_name, logger)
|
|
|
|
|
elif keyring == _LinuxKeyring.BASICTEXT:
|
|
|
|
|
elif keyring == _LinuxKeyring.BASIC_TEXT:
|
|
|
|
|
# when basic text is chosen, all cookies are stored as v10 (so no keyring password is required)
|
|
|
|
|
return None
|
|
|
|
|
assert False, f'Unknown keyring {keyring}'
|
|
|
|
@ -877,6 +958,10 @@ def _get_mac_keyring_password(browser_keyring_name, logger):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _get_windows_v10_key(browser_root, logger):
|
|
|
|
|
"""
|
|
|
|
|
References:
|
|
|
|
|
- [1] https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/os_crypt/sync/os_crypt_win.cc
|
|
|
|
|
"""
|
|
|
|
|
path = _find_most_recently_used_file(browser_root, 'Local State', logger)
|
|
|
|
|
if path is None:
|
|
|
|
|
logger.error('could not find local state file')
|
|
|
|
@ -885,11 +970,13 @@ def _get_windows_v10_key(browser_root, logger):
|
|
|
|
|
with open(path, encoding='utf8') as f:
|
|
|
|
|
data = json.load(f)
|
|
|
|
|
try:
|
|
|
|
|
# kOsCryptEncryptedKeyPrefName in [1]
|
|
|
|
|
base64_key = data['os_crypt']['encrypted_key']
|
|
|
|
|
except KeyError:
|
|
|
|
|
logger.error('no encrypted key in Local State')
|
|
|
|
|
return None
|
|
|
|
|
encrypted_key = base64.b64decode(base64_key)
|
|
|
|
|
# kDPAPIKeyPrefix in [1]
|
|
|
|
|
prefix = b'DPAPI'
|
|
|
|
|
if not encrypted_key.startswith(prefix):
|
|
|
|
|
logger.error('invalid key')
|
|
|
|
@ -901,11 +988,13 @@ def pbkdf2_sha1(password, salt, iterations, key_length):
|
|
|
|
|
return pbkdf2_hmac('sha1', password, salt, iterations, key_length)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _decrypt_aes_cbc(ciphertext, key, logger, initialization_vector=b' ' * 16):
|
|
|
|
|
def _decrypt_aes_cbc_multi(ciphertext, keys, logger, initialization_vector=b' ' * 16):
|
|
|
|
|
for key in keys:
|
|
|
|
|
plaintext = unpad_pkcs7(aes_cbc_decrypt_bytes(ciphertext, key, initialization_vector))
|
|
|
|
|
try:
|
|
|
|
|
return plaintext.decode()
|
|
|
|
|
except UnicodeDecodeError:
|
|
|
|
|
pass
|
|
|
|
|
logger.warning('failed to decrypt cookie (AES-CBC) because UTF-8 decoding failed. Possibly the key is wrong?', only_once=True)
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|