openwall / john

John the Ripper jumbo - advanced offline password cracker, which supports hundreds of hash and cipher types, and runs on many operating systems, CPUs, GPUs, and even some FPGAs
https://www.openwall.com/john/
Other
9.98k stars 2.06k forks source link

ProtonMail offline password recovery #4658

Open solardiz opened 3 years ago

solardiz commented 3 years ago

ProtonMail uses OpenPGP keys, but with custom bcrypt-based key derivation (prior to the usual s2k?). We can probably add a format that would crack ProtonMail login password (in their one-password mode, or the mailbox password otherwise?) given an OpenPGP private key extracted from a memory dump and bcrypt salt and SRP modulus obtained when trying to log in, as I explained here:

https://github.com/ProtonMail/WebClient/issues/38#issuecomment-811028444

Anyone wants to try and implement this?

solardiz commented 3 years ago

FWIW, here's a first attempt at hacking our GPG format to support ProtonMail instead of normal GPG keys. This pretends to work (with --skip-self-test since the test vectors aren't updated), but I don't know if it actually works right or not.

diff --git a/src/gpg_common_plug.c b/src/gpg_common_plug.c
index 99618a2..2e38957 100644
--- a/src/gpg_common_plug.c
+++ b/src/gpg_common_plug.c
@@ -102,7 +102,7 @@ struct fmt_tests gpg_common_gpg_tests[] = {
 };

 // mul is at most (PLAINTEXT_LENGTH + SALT_LENGTH)
-#define KEYBUFFER_LENGTH ((PLAINTEXT_LENGTH + SALT_LENGTH) * 64)
+#define KEYBUFFER_LENGTH ((256 + SALT_LENGTH) * 64)

 // Returns the block size (in bytes) of a given cipher
 uint32_t gpg_common_blockSize(char algorithm)
@@ -574,7 +574,7 @@ static void S2KItSaltedSHA256Generator(char *password, unsigned char *key, int l
            SHA256_Update(&ctx, "\0", 1);
        }
        // Find multiplicator
-       tl = strlen(password) + SALT_LENGTH;
+       tl = 256/*strlen(password)*/ + SALT_LENGTH;
        mul = 1;
        while (mul < tl && ((64 * mul) % tl)) {
            ++mul;
@@ -583,7 +583,7 @@ static void S2KItSaltedSHA256Generator(char *password, unsigned char *key, int l
        bs = mul * 64;
        bptr = keybuf + tl;
        n = bs / tl;
-       memcpy(keybuf + SALT_LENGTH, password, strlen(password));
+       memcpy(keybuf + SALT_LENGTH, password, 256/*strlen(password)*/);
        while (n-- > 1) {
            memcpy(bptr, keybuf, tl);
            bptr += tl;
diff --git a/src/gpg_fmt_plug.c b/src/gpg_fmt_plug.c
index 706c20b..9ee7ba2 100644
--- a/src/gpg_fmt_plug.c
+++ b/src/gpg_fmt_plug.c
@@ -37,6 +37,9 @@ john_register_one(&fmt_gpg);
 #include <omp.h>
 #endif

+#include <assert.h>
+#include "crypt_blowfish-1.3/crypt_blowfish.c"
+
 #include "arch.h"
 #include "params.h"
 #include "common.h"
@@ -124,7 +127,25 @@ static int crypt_all(int *pcount, struct db_salt *salt)
        int res;
        unsigned char keydata[64];

-       gpg_common_cur_salt->s2kfun(saved_key[index], keydata, ks);
+       static const uint8_t modulus[] = {put, your, 256, byte, values, here};
+       struct {
+           char bcrypt_out[60];
+           uint8_t modulus[256];
+           uint8_t i;
+       } buf;
+       assert(sizeof(buf) == 60 + 256 + 1);
+       BF_crypt(saved_key[index], "$2y$10$your.22.char.salt.here", buf.bcrypt_out, sizeof(buf), 0);
+       assert(sizeof(modulus) == sizeof(buf.modulus));
+       memcpy(buf.modulus, modulus, sizeof(buf.modulus));
+       char promises[4 * 64];
+       SHA512_CTX ctx;
+       for (buf.i = 0; buf.i < 4; buf.i++) {
+           SHA512_Init(&ctx);
+           SHA512_Update(&ctx, &buf, sizeof(buf));
+           SHA512_Final((unsigned char *)&promises[buf.i * 64], &ctx);
+       }
+
+       gpg_common_cur_salt->s2kfun(promises, keydata, ks);
        res = gpg_common_check(keydata, ks);
        if (res) {
            cracked[index] = 1;