I am trying to implement End-to-end encryption support for pushbullet ephemeral messages in python3.
I'm using python-cryptography, but I get an InvalidTag
-Exception while decrypting. I have double checked the key, iv and tag, but I can't figure out where it goes wrong.
The key is derived like this:
salt = user_ident.encode()
pw = password.encode()
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=30000,
backend=backend)
dkey = kdf.derive(pw)
It is then stored in a keyring as Base64 encoded string, but I double checked if I get the right byte string when encrypting (also by doing it manually in the REPL).
Decrypt:
ciphertxt = a2b_base64(msg['ciphertext'])
version = ciphertxt[0:1]
tag = ciphertxt[1:17]
iv = ciphertxt[17:29]
enc_msg = ciphertxt[29:]
# Construct an AES-GCM Cipher object
decryptor = Cipher(
algorithms.AES(self.dkey_),
modes.GCM(iv, tag),
backend=backend
).decryptor()
cleartxt = decryptor.update(enc_msg) + decryptor.finalize()
All vars are byte strings, here the relevant docs of python-cryptography.
To clarify: I have tried my own methods to encrypt and successfully decrypt some text. But when I activate Pushbullet e2e encryption on my phone and my client and I receive a notification, I get the error above.
The encryption method assembles the encrypted message like this:
b'1' + encryptor.tag + iv + ciphertxt
And I can decipher it. Doesn't work with the tag from a received message.
Any ideas? :/
I recently added some interactive javascript stuff on the docs page: https://docs.pushbullet.com/#example-encrypt-a-message
I have found that the best way to debug an issue like this is to test each part in isolation and make sure you get the correct output for a given input.
In your case, I think you should create a key, IV and message that don't change, and make sure your library produces the same encrypted_message as the javascript code in that example does. Here is how this might look:
// convert key from base64 to binary
var key = atob("1sW28zp7CWv5TtGjlQpDHHG4Cbr9v36fG5o4f74LsKg=");
var initialization_vector = atob("O2QAL8AYQB+qbre8"); // 96-bit
var message = "meow!";
var cipher = forge.cipher.createCipher('AES-GCM', key);
cipher.start({"iv": initialization_vector});
cipher.update(forge.util.createBuffer(forge.util.encodeUtf8(message)));
cipher.finish();
var tag = cipher.mode.tag.getBytes();
console.log("tag", btoa(tag));
var encrypted_message = cipher.output.getBytes();
console.log("encrypted_message", btoa(encrypted_message));
The output for that is:
tag OBA7UU/Rd9j0Zn+9korAyQ==
encrypted_message 7YS1aTE=
Once your python encryption matches this, you should make sure the decryption part works.
var key = atob("1sW28zp7CWv5TtGjlQpDHHG4Cbr9v36fG5o4f74LsKg=")
var tag = atob("OBA7UU/Rd9j0Zn+9korAyQ==")
var initialization_vector = atob("O2QAL8AYQB+qbre8"); // 96 bits
var encrypted_message = atob("7YS1aTE=");
var decipher = forge.cipher.createDecipher('AES-GCM', key);
decipher.start({
'iv': initialization_vector,
'tag': tag
});
decipher.update(forge.util.createBuffer(encrypted_message));
decipher.finish();
var message = decipher.output.toString('utf8');
console.log("message:", message);
Which should print:
message: meow!
I don't have any experience with that particular python library, but if you use this debugging technique you should be able to narrow down where the problem lies.