Skip to content

Commit

Permalink
[feat] introduce mediacipher
Browse files Browse the repository at this point in the history
This enables decryption of downloaded media and encryption
of media pre-upload.
Fixes #1449 .enc files instead of pictures
Fixes #1713 Decrypt images
  • Loading branch information
tgalal committed May 2, 2019
1 parent fdc684e commit 5ac5c3c
Show file tree
Hide file tree
Showing 2 changed files with 115 additions and 0 deletions.
70 changes: 70 additions & 0 deletions yowsup/layers/protocol_media/mediacipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from axolotl.kdf.hkdfv3 import HKDFv3
from axolotl.util.byteutil import ByteUtil

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import binascii
import hmac
import hashlib


class MediaCipher(object):
INFO_IMAGE = binascii.unhexlify(b'576861747341707020496d616765204b657973')
INFO_AUDIO = binascii.unhexlify("576861747341707020417564696f204b657973")
INFO_VIDEO = binascii.unhexlify("576861747341707020566964656f204b657973")
INFO_DOCUM = binascii.unhexlify("576861747341707020446f63756d656e74204b657973")

def encrypt_image(self, plaintext, ref_key):
return self.encrypt(plaintext, ref_key, self.INFO_IMAGE)

def decrypt_image(self, ciphertext, ref_key):
return self.decrypt(ciphertext, ref_key, self.INFO_IMAGE)

def decrypt_audio(self, ciphertext, ref_key):
return self.decrypt(ciphertext, ref_key, self.INFO_AUDIO)

def decrypt_video(self, ciphertext, ref_key):
return self.decrypt(ciphertext, ref_key, self.INFO_VIDEO)

def decrypt_document(self, ciphertext, ref_key):
return self.decrypt(ciphertext, ref_key, self.INFO_DOCUM)

def encrypt(self, plaintext, ref_key, media_info):
derived = HKDFv3().deriveSecrets(ref_key, media_info, 112)
parts = ByteUtil.split(derived, 16, 32)
iv = parts[0]
key = parts[1]
mac_key = derived[48:80]

cipher_encryptor = Cipher(
algorithms.AES(key), modes.CBC(iv), backend=default_backend()
).encryptor()
ciphertext = cipher_encryptor.update(plaintext) + cipher_encryptor.finalize()

mac = hmac.new(mac_key, digestmod=hashlib.sha256)
mac.update(iv)
mac.update(ciphertext)

return ciphertext + mac.digest()[:10]

def decrypt(self, ciphertext, ref_key, media_info):
derived = HKDFv3().deriveSecrets(ref_key, media_info, 112)
parts = ByteUtil.split(derived, 16, 32)
iv = parts[0]
key = parts[1]
mac_key = derived[48:80]
media_ciphertext = ciphertext[:-10]
mac_value = ciphertext[-10:]

mac = hmac.new(mac_key, digestmod=hashlib.sha256)
mac.update(iv)
mac.update(media_ciphertext)

if mac_value != mac.digest()[:10]:
raise ValueError("Invalid MAC")

cipher_decryptor = Cipher(
algorithms.AES(key), modes.CBC(iv), backend=default_backend()
).decryptor()

return cipher_decryptor.update(media_ciphertext) + cipher_decryptor.finalize()
45 changes: 45 additions & 0 deletions yowsup/layers/protocol_media/test_cipher.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from yowsup.layers.protocol_media.mediacipher import MediaCipher
import base64
import unittest


class MediaCipherTest(unittest.TestCase):
IMAGE = (
b'EOnnZIBu1vTSI51IeJvaKR+8W1FqBETATI2Ikl6nVQ8=',

b'/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8M'
b'CgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/2wBDAQMDAwQDBAgEBAgQCwkLEBAQEBAQEBAQEBAQEBAQEBAQ'
b'EBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBD/wAARCAABAAEDAREAAhEBAxEB/8QAHwAAAQUBAQEB'
b'AQEAAAAAAAAAAAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1FhByJxFDKB'
b'kaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1'
b'dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWmp6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl'
b'5ufo6erx8vP09fb3+Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQDBAcF'
b'BAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYkNOEl8RcYGRomJygpKjU2Nzg5'
b'OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0'
b'tba3uLm6wsPExcbHyMnK0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9oADAMBAAIRAxEAPwD9U6AP/9kL'
b'CwsLCwsLCwsLCw==',

b'dT9YPFSz4dprkH6uiXPEVERGZVIZbGYHzwue21WoLP1RCE2wmu11M8n6ysfROPtI39DCRFQhBBEVGFCT/nfV'
b'pt+fouIENBSXY44mR2en4HGvRR//dlM5OBLz2WuEOf01iKPazGtfacy6lnV0X5JagL4r1mKeyuSXJEV81kxj'
b'Vd3OArpLKt13XM36PcnTd/U6DHOV6Vf982Wc1UjR7kMb5JT+HlWrvz9CCGMTX5mqBYWEr3InCFyrmaZu8DXC'
b'60YUZCPLTHJP0hFQA1ooKQks4f3F39tzVL3dbX3io7XPQiSgHN6nuPCD0PF7dWINep+amk+ODjeQd/o2guqx'
b'O/AngNIxfFWq3915jMQWAXeARUFw7x+9Qx93UWC/sfQ72nTqdQaH5W4vsMUKaocZcAJ7YWudKo5Y15uo3ulP'
b'744Cyo54tvxUSV0zLC90LYCe8fJefREjreO73RlVqoynTyFHuVS7/YMnesO5lCHZJ2NpHuzpGKCU08RgCuSr'
b'K/wh4SLXZ1nhZEYX/xY4cDO7swrA3Y3Qt0gjFzNBfUT9aABQaU+WHY8pBS/oVfJsuj2iod2fsW8UoplImoOk'
b'bosYlMkdZSIfwBQ5kt3On2d+HPUNt7HVZWRECFj0C4IHhZCZIJw0wFmPMOiIHyzittXGb45uJA2Sd1gnRbw0'
b'1XFuoIyvS7z0MtW0QUiD6kaJFBkpTo7hxN/HyN8xQwOkeBcKORdpeSp62iPBuRvel9I2p07it7UyYhzas+Jv'
b'tl6i+hIz6Z5nzQEZ+zPAYxDmoy4h9GQuXUivTzU9I0/Sd4QvM3rfewGeXsNSjWI1CLVZG+4wL1TPCbQGaHEB'
b'NF8zmpTMYV+NvCzv/2DwYuYoiUI='
)

def setUp(self):
self._cipher = MediaCipher() # type: MediaCipher

def test_decrypt_image(self):
media_key, media_plaintext, media_ciphertext = map(base64.b64decode, self.IMAGE)
self.assertEqual(media_plaintext, self._cipher.decrypt_image(media_ciphertext, media_key))

def test_encrypt_image(self):
media_key, media_plaintext, media_ciphertext = map(base64.b64decode, self.IMAGE)
encrypted = self._cipher.encrypt(media_plaintext, media_key, MediaCipher.INFO_IMAGE)
self.assertEqual(media_ciphertext, encrypted)

0 comments on commit 5ac5c3c

Please sign in to comment.