ElDavoo / wa-crypt-tools

Manage WhatsApp .crypt12, .crypt14 and .crypt15 files.
GNU General Public License v3.0
635 stars 80 forks source link

wacreatekey bug with crypt14 PLUS solution #140

Closed code-consensus closed 2 months ago

code-consensus commented 2 months ago

Hi,

I noticed a bug in using wacreatekey to make a crypt14 key, but I also have found the solution. However, I have never made any contributions before to github code, so I don't know how to make suggested changes directly to @ElDavoo's code, but I will outline the problem and solution here.

As per the help (wacreatekey -h), you can create a crypt14 key as follows, optionally specifying the server salt (64-digit hex number) and googleid salt (32-digit hex number): wacreatekey --crypt14 --server-salt SERVER_SALT --googleid GOOGLEID

Try either of these options on the command-line and you will get an invalid length error, e.g.: wacreatekey --crypt14 --server-salt 1234567890123456789012345678901234567890123456789012345678901234 gives an error of "Invalid server salt length" and wacreatekey --crypt14 --googleid 12345678901234567890123456789012 gives an error of "Invalid google id length"

The issue lies in wacreatekey.py under the "if args.crypt14:" section, line 70, and how it calls the Key14 class: key: Key14 = Key14(cipher_version=args.cipher_version.to_bytes(2, "big"), key_version=args.key_version.to_bytes(1, "big"), serversalt=args.server_salt, googleid=args.googleid, iv=None, key=None), specifically the items serversalt=args.server_salt, googleid=args.googleid

According to key14.py, the Key14 class expects bytes, but it is being passed strings in wacreatekey.py. Changing the code around the sever_salt and googleid fixes the problem: _serversalt=int(args.server_salt, 16).to_bytes(32, "big"), googleid=int(args.googleid, 16).to_bytes(16, "big")_ i.e. the full line would now be: key: Key14 = Key14(cipher_version=args.cipher_version.to_bytes(2, "big"), key_version=args.key_version.to_bytes(1, "big"), serversalt=int(args.server_salt, 16).to_bytes(32, "big"), googleid=int(args.googleid, 16).to_bytes(16, "big"), iv=None, key=None)

N.B. cipher_version and key_version are unaffected, given that they are either 0 or 1 in the case of cipher, and 1, 2 or 3 for key_version, but if those were to be larger hex numbers in the future, then the code around them many need to also change.

Related to key version, for which supported numbers are 1, 2 or 3 (default) -- I see that the key version has gone up all the way to 32 in newer WhatsApp installations. Is there any plan to add newer key versions to the algorithm?

Also, with regard to the way wacreatekey calls Key14, why does it only create a random key (passes key=None), versus potentially passing a user-specified key with --hex?

@ElDavoo thanks for the amazing work on these scripts -- this has been an absolute lifesaver in recovering my message data!!

ElDavoo commented 2 months ago

Wow, amazing job!

Related to key version, for which supported numbers are 1, 2 or 3 (default) -- I see that the key version has gone up all the way to 32 in newer WhatsApp installations. Is there any plan to add newer key versions to the algorithm?

Good to know! I arbitrarily named this field "key version", but I didn't actually know what does it do. From what you said it's more like a key ID rather than "version" (so it gets incremented at each change). I will change the checks so the only thing that will be checked is if the key ID in the key and in the encrypted file matches.

Also, with regard to the way wacreatekey calls Key14, why does it only create a random key (passes key=None), versus potentially passing a user-specified key with --hex?

Sigh. These tools were a little bit rushed out (untested). This afternoon I will be inside a train for a few hours, I will try to make some new tests about key14 creation, and then to fix the code as you said.

ElDavoo commented 2 months ago

I think I wrote tests for and fixed all the bugs mentioned here, so I will close as completed. Feel free to open more issues as you find more bugs. I only worked on wacreatekey, other tools (except wadecrypt) are largely untested and left to the user's guesswork.

However, I have never made any contributions before to github code, so I don't know how to make suggested changes directly to @ElDavoo's code, but I will outline the problem and solution here.

In order to do that you need to learn how to make a pull request.

code-consensus commented 2 months ago

Hi @ElDavoo,

I tested the new wacreatekey.py as posted in the latest version (post v0.1.0).

I can confirm that, using the flags --hex, --server-salt & --googleid for a crypt14 key works prefectly. I tested a created key versus the actual WhatsApp key that I got from my phone, and both resulted in the exactly same SHA256 hash.

However, I made an error above:

Related to key version, for which supported numbers are 1, 2 or 3 (default) -- I see that the key version has gone up all the way to 32 in newer WhatsApp installations. Is there any plan to add newer key versions to the algorithm?

Which then resulted in your commit above on removing key_checks.

I made a mistake on this -- with regard to the decryption key, I'm not sure if the key_version has gone up in the latest WhatsApp versions. I confused this with the key_version of the encrypted databases.

In the WhatsApp backup files, e.g. msgstore.db.crypt14, it is there where I noted that the key version is 32 (examining with wainfo), not with the decryption key.

And I can confirm that my WhatsApp-provided crypt14 decryption key does not have key version of 32, though my resulting crypt14 databases show key version 32. So the key version of the decryption key must not have a relation to the crypt14 database's key version.

For the record, if I encrypt an unencrypted database (e.g. msgstore.db) using waencrypt, the resulting msgstore.db.crypt14 actually also shows a key version of 32 (examining with wainfo) -- this is regardless of what the decryption key's key_version is. I guess this is being picked up from the msgstore.db or being automatically set from within your code.

So I'm not sure if it changes anything with regard to your removing the key_checks from the key creation. Sorry for the confusion!!

ElDavoo commented 2 months ago

examining with wainfo

maybe it's a bug in there?

ElDavoo commented 2 months ago

I made a mistake on this

ok, i reverted the commit for now, after my graduation i will take a look at that.

code-consensus commented 2 months ago

ok, i reverted the commit for now, after my graduation i will take a look at that.

Hi, here is some further information I discovered on the crypt14 key...

Regardless of whether wainfo is showing the correct information on key_version of the crypt14 databases (or are they all legitimately "32"), I can confirm that wadecrypt works perfectly with a key that has a completely different key_version than the database's listed key_version in wainfo.

Furthermore, creating a new crypt14 key with a different key_version, different server salt, and different googleid (but same hexadecimal key), also works to decrypt the crypt14 database...! I guess somehow all these factors are irrelevant in decryption? But maybe they are relevant for WhatsApp if the database is encrypted legitimately (i.e. in the case of reimporting a database that you encrypt with crypt14).

code-consensus commented 2 months ago

maybe it's a bug in there?

Well, one thing that I did notice in the code is that, in waencrypt, as it calls the functions from db14.py, and there you are actually always setting the database Key Version to 32 when encrypting the database.

From the encrypt() function in db14.py: cipher.key_version = "2".encode() -- i.e. "2" is ASCII character 50 or hexadecimal 32 But then you also have a "#FIXME" above it, and I'm not sure if that is related to cipher.key_version or not.

Is it because all crypt14 files have a key_version of 32?

N.B. in db14.py under Database14(Database), you also have a large section commented out that makes reference to the key_version of the key and the key_version of the database, and specifically that would reject key_version differences between the key and the database. But again -- see my comment above that you can still decrypt with different key_versions anyway.

So as for wainfo if there is a bug, I looked at the code. I can see that it calls header_info() from utils.py, and there it is pulling out the the database's key_version: line 170: header.c14_cipher.key_version.hex())

What I can't understand (I don't understand protobuf), is how header.c14_cipher.key_version is getting the key_version -- I guess this is somehow pulling from backup_prefix_pb2.py or C14_cipher_pb2.py? What I can't tell from those files, is whether it is legitimately pulling out the key_version, or is it just defaulting it at 32?

code-consensus commented 2 months ago

I did some further testing, which I think is instructive.

I did as follows:

  1. I took an encrypted crypt14 database (with key_version shown as "32" with wainfo), and its decryption key (exported from the phone) with a key_version listed as "3" as per wainfo.
  2. I decrypted the crypt14 database with wadecrypt (the new version @ElDavoo posted on github, not v0.1.0), and noted all the crypt14 database details (IV, whatsapp version, phone number jid backup version, features list, and max feature number).
  3. The decryption was successful, and I could view the database, let's call it "msgstore.db".
  4. I then re-encrypted the msgstore.db with crypt14 using waencrypt, specifying every command-line option to match the originally encrypted database file, resulting in msgstore_reencrypted.db.crypt14
  5. I then create a NEW crypt14 key using wacreatekey (the new version @ElDavoo posted on github, not v0.1.0), using the same "key" as the original decryption key, same server-salt, same googleid, but _different keyversion -- instead of "3", I changed it to "1".
  6. Then as in step 4 above, I take this altered key with key_version 1 and I encrypt msgstore.db, specifying every command-line option to match the original database, resulting in msgstore_altered_key_version.db.crypt14.

The result? The original msgstore.db.crypt14, msgtore_reencrypted.db.crypt14, and msgstore_altered_key_version.db.crypt14 ALL resulted in the exact same SHA256 hash.

maybe it's a bug in there?

Clearly this means that there is no relevant connection between the key_version of the decryption key and the key_version of the crypt14 database. I noted above commented out sections the code that would have caused the program to exit if key_versions where different -- clearly that should stay commented out.

So, from what I gather above, regardless of whether wainfo is showing correct info on the key_version of crypt14 databases, the end-result is that wadecrypt and waencrypt are working correctly with crypt14!