AES Encrypted Video – Decrypted in Temporary Memory

I recently created an addition to Cry About Crypt that allows for decrypted playback of encrypted video media inside temporary memory. In this article I will explain what I did along with the functional source code contained in order to achieve this.

First here are the methods to encrypt and decrypt the media in question.

import io
import uuid
import base64


### ENCRYPTION METHODS ###
### Courtesy of http://stackoverflow.com/questions/16761458/how-to-aes-encrypt-decrypt-files-using-python-pycrypto-in-an-openssl-compatible ###

from hashlib import md5
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Random import random

def derive_key_and_iv(password, salt, key_length, iv_length):
    d = d_i = ''
    while len(d) < key_length + iv_length:
        d_i = md5(d_i + password + salt).digest()
        d += d_i
    return d[:key_length], d[key_length:key_length+iv_length]


def encrypt(in_file, out_file, password, key_length=32):
    bs = AES.block_size
    salt = Random.new().read(bs - len('Salted__'))
    key, iv = derive_key_and_iv(password, salt, key_length, bs)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    out_file.write('Salted__' + salt)
    finished = False
    while not finished:
        chunk = in_file.read(1024 * bs)
        if len(chunk) == 0 or len(chunk) % bs != 0:
            padding_length = bs - (len(chunk) % bs)
            chunk += padding_length * chr(padding_length)
            finished = True
        out_file.write(cipher.encrypt(chunk))

        
def decrypt(in_file, out_file, password, key_length=32):
    bs = AES.block_size
    salt = in_file.read(bs)[len('Salted__'):]
    key, iv = derive_key_and_iv(password, salt, key_length, bs)
    cipher = AES.new(key, AES.MODE_CBC, iv)
    next_chunk = ''
    finished = False
    while not finished:
        chunk, next_chunk = next_chunk, cipher.decrypt(in_file.read(1024 * bs))
        if len(next_chunk) == 0:
            padding_length = ord(chunk[-1])
            if padding_length < 1 or padding_length > bs:
               raise ValueError("bad decrypt pad (%d)" % padding_length)
            # all the pad-bytes must be the same
            if chunk[-padding_length:] != (padding_length * chr(padding_length)):
               # this is similar to the bad decrypt:evp_enc.c from openssl program
               raise ValueError("bad decrypt")
            chunk = chunk[:-padding_length]
            finished = True
        out_file.write(chunk)

### END ####        

As noted these are from a resource I found on stack overflow. I won’t be going over how to encrypt/upload the media in web2py which should be pretty self explanatory in itself. It is more important to recognize how the decrypted media is echo’ed and used in HTML5.

The next section of code is for the media request and decryption of the media.

@auth.requires_login()
def controllerFunc():        
    filepath = os.path.join(request.folder, 'uploads/', '%s' % cry.file_upload)
    form = FORM(LABEL('The Associated Key'), INPUT(_name='key', value="", _type='text', _class='string'), INPUT(_type='submit', _class='btn btn-primary'), _class='form-horizontal')
    if form.accepts(request, session):
        outfile = io.BytesIO()
        with open(filepath, 'rb') as infile:
            key = str(form.vars.key).rstrip()
            try:
                decrypt(infile, outfile, key)
            except:
                session.flash = 'Decryption error. Please verify your key'
                return redirect(URL('default', 'index'), client_side = True)
            outfile.seek(0)
            msg = base64.b64encode(outfile.getvalue())
            outfile.close()
            response.flash = 'Success! Your video is now playable'
            return dict(form=form, cry=cry, msg=msg)
    elif form.errors:
        response.flash = 'Please correct the highlighted fields'
        return dict(form=form, msg=None)
    else:
        return dict(form=form, msg=None)

The important lines are: decrypt(infile, outfile, key) and, most importantly msg = base64.b64encode(outfile.getvalue())

Here we can see that the outfile is a Python BytesIO memory container holding the decrypted file.
As reference from the Python docs these are some notes associated with BytesIO.

BytesIO is a simple stream of in-memory bytes

msg becomes our base64 encoded BytesIO data.

In the returned HTML for this controller function we simply pass this base64 data, as base64, to the HTML5 Video Player. In this instance a Video JS player.

{{if msg != None:}}
<div class="row" style="margin-top: 20px;">
<div class="video-js-box">
<video class="video-js vjs-default-skin" data-setup="{"controls": true, "autoplay": false, "preload": "none", "poster": "https://www.cryaboutcrypt.ninja/static/crying.png", "width": 720, "height": 406}" width="300" height="150">
<source type="video/mp4; codecs="avc1.42E01E, mp4a.40.2"">
</video>
</div>
</div>
{{pass}}

In the HTML5 video source object the values of the src attribute is where to take notes from data:video/mp4;base64,{{=msg}}.
This was the generic conclusion/summary of the decrypted playback of encrypted video on the project Cry About Crypt.