views:

87

answers:

3

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.

A: 

I'm somewhat unclear on your goal.

Do the images need to be encrypted on the server? How about performing the actual encryption/decryption on the server, with the user providing a key via an HTTPS connection? Then you would encrypt / decrypt the image on the server and provide a regular image file to the client.

If I'm understanding the goal, I think this approach makes more sense, and avoids much of the messiness of trying to do encryption and decryption in the browser.

philipk
Thanks for the answer. Sorry I should have stated clearly. The goal is that the original image and the key never leaves the user's computer so that he/she does not need to trust the server.
timeon
+1  A: 
idealmachine
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
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
This link below has the details of how to download a file form the server and show it as a image through data URLhttp://ajaxref.com/ch4/datauri.html
timeon
A: 

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

Jonas Elfström