Question

What I'm trying to achieve

I'm working on a browser based quiz game with prerecorded audio clues which get sent to the user. To take load of the servers as well as having the audio already transmitted the files are already sent way earlier before the game starts to the user. As the game is to be played in real time and competetively, it is of importance that the player has no chance to listen to the audio files before. My idea now was to basically send AES enciphered files to the user, send only the password of the file in real time over websockets, decipher it in the browser using crypto.js and play it instantly.

The Problem

For some reason I can't get binary files decrypted properly using crypto.js so I can feed it into decodeAudioData of an audio context. I get empty responses from decrypt or gibberish back.

What I tried

The audio (in this case an mp3) is AES encrypted according to the docs using the following call:

openssl enc -aes-256-cbc -in infile -out outfile -pass pass:"testpass" -e -base64

The resulting file is then loaded via XMLHTTPRequest with request type text (I already tried arraybuffer but failed too) and the result is stored in a variable (this.protectedBuffer).

According to Crypto.js docs, this should now decrypt the file:

var decrypted = CryptoJS.AES.decrypt(this.protectedBuffer, 'testpass');
// next step obviously doesn't work as decrypted is
// is not an arraybuffer.
context.decodeAudioData(decrypted, function() { ... }); 

This however results in nothing playable and returns only a wordlist. I know I'm missing some step but I have absolutely no clue right now what is not working and would be more than glad if someone could point me in the right direction and tell me what I did wrong.

EDIT: Here is a jsfiddle with what I'm trying to achieve http://jsfiddle.net/HKu3n/1/

Was it helpful?

Solution

When openssl generates a base64-encoded file, it inserts a lot of whitespace. In particular, it breaks the output up into 64-byte lines:

U2FsdGVkX18AVcl/JumJawmKlaYWjQwo6wDcETza5AX6VK6aVCMtHXic2xtDIw1G
PJfxbKLJKWgvKXLNGO0jgPTI6qFPllts6VAIzvZ3uqV8BBPd0ge4YJe/q7e7ZGmx
QJyvFnyumo3b2pMxxMWngtl8qpxcsaiSrALnBXZizJ70PcTsyfk7aym1twkj0ofY
...

And at the end, a final newline. The crypto-js encryption utilities do not like all of that whitespace, so you must remove it (which I do with a split/join). For example:

$.get('/audio.aes', function(data){
    var decrypted = CryptoJS.AES.decrypt(data.split(/\s/).join(''), 'testpass');
    // ...
});

What CryptoJS.AES.decrypt returns is a WordArray object. To convert it to a string, you must call toString and provide an encoding:

decrypted.toString(CryptoJS.enc.Utf8);
decrypted.toString(CryptoJS.enc.Base64);

I didn't have any audio files handy, but I was able to make this work end-to-end with an image (served up at /image.aes):

$.get('/image.aes', function(data){
    var decrypted = CryptoJS.AES.decrypt(data.split(/\s/).join(''), 'testpass');
    $('#imageContainer').append('<img src="data:image/jpeg;base64,'
        + decrypted.toString(CryptoJS.enc.Base64) + '">');
    });
});

And I saw an image on the other end! You might have to get some wrangling to get the decoded data into the right format for your audio output, but I hope this will get you over the hump.

UPDATE

I got it working with your example jsFiddle, using your audio. To get the ArrayBuffer necessary, I used base64DecToArr available here:

var decoded = CryptoJS.AES.decrypt(request.response.split(/\s/).join(''),
    'testpass');

var arr = base64DecToArr(decoded.toString(CryptoJS.enc.Base64));
context.decodeAudioData(arr.buffer, function (buffer) {
    alert('success decoding buffer');
}, function (err) {
    alert('couldn\'t decode buffer');
});

Working jsFiddle here: http://jsfiddle.net/BTnpL/

There's probably a more efficient way to convert the WordArray object returned from the crypto-js functions to the ArrayBuffer used by decodeAudioData, but I'll leave that as an exercise for you.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top