Decrypting images using JavaScript within browser

15,034

Solution 1

Encrypt and Base64 encode the image's raw data when it is saved. (You can only do that on a web browser that supports the HTML5 File API unless you use a Java applet). When the image is downloaded, unencode it, decrypt it, and create a data URI for the browser to use (or again, use a Java applet to display the image).

You cannot, however, remove the need for the user to trust the server because the server can send whatever JavaScript code it wants to to the client, which can send a copy of the image to anyone when it is decrypted. This is a concern some have with encrypted e-mail service Hushmail – that the government could force the company to deliver a malicious Java applet. This isn't an impossible scenario; telecommunications company Etisalat attempted to intercept BlackBerry communications by installing spyware onto the device remotely (http://news.bbc.co.uk/2/hi/technology/8161190.stm).

If your web site is one used by the public, you have no control over your users' software configurations, so their computers could even already be infected with spyware.

Solution 2

You inspired me to give this a try. I blogged about it and you can find a demo here.

I used Crypto-JS to encrypt and decrypt with AES and Rabbit.

First I get the CanvasPixelArray from the ImageData object.

var ctx = document.getElementById('leif')
                  .getContext('2d');
var imgd = ctx.getImageData(0,0,width,height);
var pixelArray = imgd.data;

The pixel array has four bytes for each pixel as RGBA but Crypto-JS encrypts a string, not an array. At first I used .join() and .split(",") to get from array to string and back. It was slow and the string got much longer than it had to be. Actually four times longer. To save even more space I decided to discard the alpha channel.

function canvasArrToString(a) {
  var s="";
  // Removes alpha to save space.
  for (var i=0; i<pix.length; i+=4) {
    s+=(String.fromCharCode(pix[i])
        + String.fromCharCode(pix[i+1])
        + String.fromCharCode(pix[i+2]));
  }
  return s;
}

That string is what I then encrypt. I sticked to += after reading String Performance an Analysis.

var encrypted = Crypto.Rabbit.encrypt(imageString, password);

I used a small 160x120 pixels image. With four bytes for each pixels that gives 76800 bytes. Even though I stripped the alpha channel the encrypted image still takes up 124680 bytes, 1.62 times bigger. Using .join() it was 384736 bytes, 5 times bigger. One cause for it still being larger than the original image is that Crypto-JS returns a Base64 encoded string and that adds something like 37%.

Before I could write it back to the canvas I had to convert it to an array again.

function canvasStringToArr(s) {
  var arr=[];
  for (var i=0; i<s.length; i+=3) {
    for (var j=0; j<3; j++) {
      arr.push(s.substring(i+j,i+j+1).charCodeAt());
    }
    arr.push(255); // Hardcodes alpha to 255.
  }
  return arr;
}

Decryption is simple.

var arr=canvasStringToArr(
          Crypto.Rabbit.decrypt(encryptedString, password));
imgd.data=arr;
ctx.putImageData(imgd,0,0);

Tested in Firefox, Google Chrome, WebKit3.1 (Android 2.2), iOS 4.1, and a very recent release of Opera.

alt text

Share:
15,034
timeon
Author by

timeon

Believer of doing the right things right.

Updated on July 25, 2022

Comments

  • timeon
    timeon almost 2 years

    I have a web based application that requires images to be encrypted before they are sent to server, and decrypted after loaded into the browser from the server, when the correct key was given by a user.

    [Edit: The goal is that the original image and the key never leaves the user's computer so that he/she is not required to trust the server.]

    My first approach was to encrypt the image pixels using AES and leave the image headers untouched. I had to save the encrypted image in lossless format such as png. Lossy format such as jpg would alter the AES encrypted bits and make them impossible to be decrypted.

    Now the encrypted images can be loaded into the browser, with a expected completely scrambled look. Here I have JavaScript code to read in the image data as RGB pixels using Image.canvas.getContext("2d").getImageData(), get the key form the user, decrypt the pixels using AES, redraw the canvas and show the decrypted image to the user.

    This approach works but suffers two major problems.

    The first problem is that saving the completely scrambled image in lossless format takes a lot of bytes, close to 3 bytes per pixel.

    The second problem is that decrypting large images in the browser takes a long time.

    This invokes the second approach, which is to encrypt the image headers instead of the actual pixels. But I haven't found any way to read in the image headers in JavaScript in order to decrypt them. The Canvas gives only the already decompressed pixel data. In fact, the browser shows the image with altered header as invalid.

    Any suggestions for improving the first approach or making the second approach possible, or providing other approaches are much appreciated.

    Sorry for the long post.

  • timeon
    timeon over 13 years
    Thanks. That's an interesting approach. What are the JavaScript APIs that would allow me to download a file from the server, read the entire file in, manipulate it and create a data URI to be displayed as an image? If I can do this, I can encrypt the image headers instead of the image data and this would solve the problems I had for large encrypted files and slow decryption. So far I have not found a way to save downloaded files either as temporarily on the disk or in the memory.
  • timeon
    timeon over 13 years
    Agree with your comments about the trust. It would probably reduce the need to trust the server admin's technical ability of securely handling the images on the server. But not his intention. I could use any input here. This is the main motivation of encrypting the images. Are there any certification services that we can use? Our name is small and unknown, but maybe we can pay some big name to certify our process and code?
  • timeon
    timeon over 13 years
    This link below has the details of how to download a file form the server and show it as a image through data URL ajaxref.com/ch4/datauri.html