0

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? :/

Benjamin Maurer
  • 3,602
  • 5
  • 28
  • 49

1 Answers1

0

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.

Chris Pushbullet
  • 1,039
  • 9
  • 10
  • Ty. I've seen your (great) documentation and I've tried this (multiple times I think). I can run your complete example. If I use my real key, iv and tag from received message, I can encrypt and decrypt, too. But I can't decrypt the original message with the tag from the message. Thought it might be encoding issue, but since I can use the tag to encrypt... – Benjamin Maurer Aug 27 '15 at 22:19
  • On closer examination of the python library you mentioned, you get an invalid tag error for at least the following reasons: invalid key, invalid tag, invalid iv, invalid encrypted message. It seems likely that one of these is incorrect. Can you double-check the bytes of each, make sure they look valid and have the correct bit length and that you get the same key when you use the interactive docs as the one you are using to decrypt? – Chris Pushbullet Aug 27 '15 at 23:25
  • I was wondering about the "interactive" part. I don't see a run button (http://imgur.com/YeiNyF4) and I get an error on the js console: "TypeError: code is undefined -- docs.pushbullet.com:2022:10". My JS is bad, but aren't there some ';' or ',' missing? – Benjamin Maurer Aug 28 '15 at 11:03
  • key len=32, iv len=12, tag len=16. – Benjamin Maurer Aug 28 '15 at 11:31
  • I take the whole 'ciphertext' field from the JSON data and convert it from base64 to binary, is that correct? 'cause there are a lot of '\n' in there. The first 29 bytes of the message (bytes) is (in base64): b'MVQifJKyMxY95rolWSG/N59OWqtjQH5E2LMP804=\n' The key is b'p7xrLWLPqC8QW0J4/RgQ9XCJP19TdTjki9y6tiFCNx0=\n' The password is just a bogus "Foo" for testing, but I don't want to publish the whole msg because it contains my e-mail address and name. Don't know how else I could generate notifications. – Benjamin Maurer Aug 28 '15 at 11:34
  • Sounds like I have some bugs in the docs then. The semicolons are not required, but it's likely there's some cross-browser issue going on. – Chris Pushbullet Aug 28 '15 at 22:05
  • The lengths seem correct, I guess the next thing to verify is that your key is correct. You pasted your key there, but does the javascript in the docs produce the same key? Without knowing your user_iden I can't try it out. The docs may only run in Chrome until I fix them in other browsers. – Chris Pushbullet Aug 28 '15 at 22:10
  • I fixed the examples to work in safari, firefox, internet explorer, and opera. I also tested sending myself an encrypted push and I was able to decode it using the javascript example in the docs. – Chris Pushbullet Aug 31 '15 at 20:24
  • I've installed Chromium, but I had no time to test. Works now like a charm in Firefox and was helpful debugging. Can encrypt/decrypt vice versa. Think I found a bug in encryption. But decryption works now too... Think that was my mistake... Probably have used different PW on device somehow... Feel a bit stupid and have wasted a ton of time, but thank you for your help, really appreciate it and love your product :) – Benjamin Maurer Aug 31 '15 at 22:52
  • Okay, so it was probably an issue with the password or password -> key part right? I was thinking of adding a python example to the docs and don't want to put any slightly wrong code in there. – Chris Pushbullet Sep 01 '15 at 00:55
  • This is a gist implementing the examples from the pushbullet docs: https://gist.github.com/cptwunderlich/09c3c44b675c77b5c73a Works with python3 and 2. Uses python-cryptography (https://cryptography.io/) - they strive to be the new "standard" python crypto library. With PyCrypto not being very actively developed and no other library supporting AES-GCM it's the only real option. – Benjamin Maurer Sep 01 '15 at 21:30