大道至简,知易行难
广阔天地,大有作为

Chrome浏览器Cookie及密码解密的分析过程及Java实现(Windows平台下v10及以上Cookie文件encrypted_value及Login Data文件password_value的解密)

本文的贡献在于:第一个给出了当encrypted_value以v10或v11开头时Windows平台下的解密方法(Java实现)及分析过程,测试版本为最新的Chrome 80.0.3987.106。代码位于:https://github.com/mlkui/chrome-cookie-password-decryption

解密Chrome浏览器Cookie可以有如下几个用途:
1)弥补WebDriver的不足,主要由于WebDriver相关Cookie操作的API大多仅能针对当前domain,处理极为不便。实际上笔者正是受制于此,而决定直接在Cookie的物理存储层面而非WebDriver层面处理Cookie。当然,如果能够容忍使用headless模式时的不便,也可以使用《解决Chrome浏览器无法通过–disable-cookie-encryption禁用Cookie加密的问题》一文中的方法禁用Cookie加密。
2)非法窃取Cookie拿到别人的登陆状态;
3)非法窃取Chrome浏览器中保存的密码;

Chrome的Cookie默认是加密的,根据不同的操作系统位于https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md所述的路径中,例如:

Cookies文件是一个SQLite3文件,被加密的Cookie位于encrypted_value中:

Chrome被加密的Cookie(Windows上的DPAPI加密)

Chrome被加密的Cookie(Windows上的DPAPI加密)

上述encrypted_value是被对称加密的,根据操作系统的不同,加密的Key分别保存在:
1)Windows,当前用户的ProtectedData中;
2)Linux,固定的密钥或钥匙链;
3)Mac,钥匙链;
参考资料5中给出了很多重要的讨论,例如使用secret-tool查找密钥等,但没有针对Windows的讨论。

实际上,Chrome的源码中(https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_unittest.cc?q=CryptProtectData&dr=C)我们可以看出Windows下有两种加密方式:

// This test verifies that the header of the data returned from CryptProtectData
// never collides with the kEncryptionVersionPrefix (“v10”) used in
// os_crypt_win.cc. If this ever happened, we would not be able to distinguish
// between data encrypted using the legacy DPAPI interface, and data that’s been
// encrypted with the new session key.

根据Chrome源码分析Cookie加密算法1

根据Chrome源码分析Cookie加密算法1

我们可以在Cookies文件对应的SQLite文件中验证:

Chrome被加密的Cookie(v10或v11开头)

Chrome被加密的Cookie(v10或v11开头)

当encrypted_value不以v10或v11开头时,在Windows下是使用Data Protection API(DPAPI)进行加密的,我们可以很容易地进行解密(下面为C#):

如果是Java的话,GitHub上有一个工程https://github.com/benjholla/CookieMonster有一些基本的逻辑可以直接拿来用。不过,该工程中用于操作JNI调用DPAPI并不能正常工作,可以使用另外的一个库windpapi4j:

接下来继续研究当encrypted_value以v10或v11开头时的处理逻辑,对于Linux和Mac而言可以直接参考参考资料4和参考资料5,然而对于Windows而言则是搜遍全网无任何资料。无奈只能看源码,在Chrome源码中,我们可以看到从Local State中读取了加密用Key:

根据Chrome源码分析Cookie加密算法2

根据Chrome源码分析Cookie加密算法2

Local State是一个JSON格式的文件:

Chrome的Local Data文件

Chrome的Local Data文件

结合源码,我们可以看到该encrypted_key为BASE64编码:

并以DPAPI开头:

密钥和NONCE/IV的长度分别为:

根据Chrome源码分析Cookie加密算法3

根据Chrome源码分析Cookie加密算法3

解密的方法为也可以在源码中看到(https://cs.chromium.org/chromium/src/components/os_crypt/os_crypt_win.cc?q=OSCrypt&dr=CSs):

根据Chrome源码分析Cookie加密算法4

根据Chrome源码分析Cookie加密算法4

可见,encrypted_value的前缀v10后为12字节的NONCE(IV),然后再是真正的密文。Chrome使用的是AES-256-GCM的AEAD对称加密,使用BoringsSSL实现:

根据Chrome源码分析Cookie加密算法5

根据Chrome源码分析Cookie加密算法5

根据Chrome源码分析Cookie加密算法6

根据Chrome源码分析Cookie加密算法6

根据Chrome源码分析Cookie加密算法7

根据Chrome源码分析Cookie加密算法7

其中的GCM_TAG_LENGTH=16需要重点关注。

对于Java而言,AES-256-GCM的实现方式有两种:
1、使用Java原生代码,但需要配置JCE。在J2EE中(JDK和JRE的7、8版本均包括),受JCE providers的进出口许可问题(The Export/Import Issues)影响,KeyGenerator生成的默认AES密钥为128bit=16Byte,配置JCE的方法可以参见笔者数年前的《Android与J2EE之间AES对称加密算法默认密钥长度不一致导致互操作失败的坑》一文,代码可以参考https://javainterviewpoint.com/java-aes-256-gcm-encryption-and-decryption;
2、使用Bouncy Castle;

我们直接基于Bouncy Castle,实现与Chrome源码中C++代码一致的逻辑:

可见v10开头的encrypted_value被正确解密了:

根据Chrome源码分析Cookie加密算法8

根据Chrome源码分析Cookie加密算法8

对于保存在Chrome中的密码而言,在Chome中显示时同样受到DPAPI的保护,正常情况下需要提供相应的凭证:

Chrome密码管理

Chrome密码管理

但我们亦可直接从Login Data中读取(路径形如:C:\Users\Alice\AppData\Local\Google\Chrome\User Data\Default\Login Data)。如果Chrome正在运行的话该文件无法直接打开,可将该文件复制一份后再打开:

Chromee的Login Data

Chromee的Login Data

使用同样的方法解密即可。

参考资料:
1、https://stackoverflow.com/questions/22532870/encrypted-cookies-in-chrome
2、https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata.unprotect?view=netframework-4.8
3、https://github.com/peter-gergely-horvath/windpapi4j
4、https://github.com/n8henrie/pycookiecheat
5、https://github.com/n8henrie/pycookiecheat/issues/12
6、http://www.nirsoft.net/utils/chromepass.html,一个可以解密不同user-data-dir下密码数据的工具(正是跟本文一样利用了Local State中的encrypted_key)
7、https://stackoverflow.com/questions/35558249/aes-gcm-with-bouncycastle-throws-mac-check-in-gcm-failed-when-used-with-iv
8、http://dy.163.com/v2/article/detail/D9OSVK640511CJ6O.html

转载时请保留出处,违法转载追究到底:进城务工人员小梅 » Chrome浏览器Cookie及密码解密的分析过程及Java实现(Windows平台下v10及以上Cookie文件encrypted_value及Login Data文件password_value的解密)

分享到:更多 ()

评论 8

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #5

    兄弟,你并不是第一个https://github.com/CCob/gookies/commit/3eab185fd701a9aa1dc7fae1885874c7910c979c

    pppp4年前 (2020-03-05)回复
    • 我定语修饰的是第一个用Java实现及分析过程啊,丝毫没有抢这个第一的意思,我觉得我写的很明白了:第一个给出了当encrypted_value以v10或v11开头时Windows平台下的解密方法(Java实现)及分析过程

      mlkui4年前 (2020-03-06)回复
  2. #4

    不知道用易语言 怎么实现,密钥 先base64解码,再dpapi 解密 32字节,初始向量12节 ,后面的密文转明文不知道用什么函数实现

    reborn4年前 (2020-07-02)回复
  3. #3

    GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, nonce)
    这个是什么意思,12字节 转换为128bit吗

    reborn4年前 (2020-07-02)回复
  4. #2

    报错Illegal key size

    晓威3年前 (2021-01-11)回复
    • 仔细看文档。

      mlkui3年前 (2021-01-21)回复
    • 不太可能,代码我都测过

      mlkui3年前 (2021-03-05)回复
  5. #1

    纯java实现:https://github.com/hnuuhc/often-utils
    使用:Map cookies = LoginData.home().getLoginDatasForDomain(“pixiv.net”);
    不知道Local storage本地存储数据应该如何解密?

    haicdust2年前 (2022-01-21)回复