在浏览器中使用 JavaScript 解密图像

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3930762/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-25 02:44:15  来源:igfitidea点击:

Decrypting images using JavaScript within browser

javascriptimagebrowserencryption

提问by timeon

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.

我有一个基于 Web 的应用程序,它要求在将图像发送到服务器之前对其进行加密,并在用户提供正确的密钥时从服务器加载到浏览器后进行解密。

[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.

我的第一种方法是使用 AES 加密图像像素并保持图像标题不变。我必须以无损格式(例如 png)保存加密图像。jpg 等有损格式会改变 AES 加密位并使其无法解密。

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.

现在加密的图像可以加载到浏览器中,看起来完全是乱七八糟的。在这里,我使用 JavaScript 代码将图像数据作为 RGB 像素读入Image.canvas.getContext("2d").getImageData(),从用户那里获取密钥,使用 AES 解密像素,重绘画布并向用户显示解密后的图像。

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.

第一个问题是,以无损格式保存完全打乱的图像需要大量字节,接近每个像素 3 个字节。

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.

这将调用第二种方法,即加密图像标题而不是实际像素。但是我还没有找到任何方法来读取 JavaScript 中的图像标题来解密它们。Canvas 仅提供已解压缩的像素数据。事实上,浏览器将更改标题的图像显示为无效。

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.

抱歉,帖子太长了。

采纳答案by PleaseStand

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).

保存时对图像的原始数据进行加密和 Base64 编码。(除非使用 Java 小程序,否则只能在支持 HTML5 文件 API 的 Web 浏览器上执行此操作)。下载图像后,对其进行解编码、解密,然后创建供浏览器使用的数据 URI(或再次使用 Java 小程序显示图像)。

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).

但是,您无法消除用户信任服务器的需要,因为服务器可以向客户端发送它想要的任何 JavaScript 代码,客户端可以在解密后将图像的副本发送给任何人。这是加密电子邮件服务 Hushmail 的一些担忧——政府可能会强迫该公司提供恶意的 Java 小程序。这不是不可能的情况;电信公司 Etisalat 试图通过将间谍软件远程安装到设备上来拦截 BlackBerry 通信(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 alreadybe infected with spyware.

如果您的网站供公众使用,则您无法控制用户的软件配置,因此他们的计算机甚至可能已经感染了间谍软件。

回答by Jonas Elfstr?m

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

你激励我试一试。我写了一篇关于它的博客,你可以在这里找到一个演示

I used Crypto-JSto encrypt and decrypt with AES and Rabbit.

我用Crypto-JS用 AES 和 Rabbit 加密和解密。

First I get the CanvasPixelArray from the ImageData object.

首先,我从 ImageData 对象中获取 CanvasPixelArray。

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.

像素数组每个像素有四个字节作为 RGBA,但 Crypto-JS 加密一个字符串,而不是一个数组。起初我使用 .join() 和 .split(",") 从数组到字符串并返回。它很慢,而且绳子比它必须的要长得多。实际上要长四倍。为了节省更多空间,我决定放弃 alpha 通道。

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.

该字符串是我然后加密的内容。在阅读了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%.

我使用了一个 160x120 像素的小图像。每个像素有四个字节,提供 76800 个字节。即使我剥离了 alpha 通道,加密图像仍然占用 124680 字节,大 1.62 倍。使用 .join() 它是 384736 字节,大 5 倍。它仍然比原始图像大的一个原因是 Crypto-JS 返回一个 Base64 编码的字符串,并且增加了 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.

在 Firefox、Google Chrome、WebKit3.1 (Android 2.2)、iOS 4.1 和最新版本的 Opera 中测试。

alt text

替代文字

回答by Eyal

I wanted to do something similar: On the server is an encrypted gif and I want to download, decrypt, and display it in javascript. I was able to get it working and the file stored on the server is the same size as the original plus a few bytes (maybe up to 32 bytes). This is the code that performs AES encryption of the file calendar.gifand makes calendar.gif.enc, written in VB.Net.

我想做类似的事情:服务器上有一个加密的 gif,我想下载、解密并在 javascript 中显示它。我能够让它工作,并且存储在服务器上的文件与原始文件大小相同,外加几个字节(可能高达 32 个字节)。这是一个运行AES加密所述文件的代码calendar.gif和品牌calendar.gif.enc,写在VB.Net。

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        Dim AES As New System.Security.Cryptography.RijndaelManaged
        Dim encryption_key As String = "603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"
        AES.Key = HexStringToBytes(encryption_key)
        Dim iv_string As String = "000102030405060708090A0B0C0D0E0F"
        'System.IO.File.ReadAllBytes("calendar.gif")
        'Dim test_string As String = "6bc1bee22e409f96e93d7e117393172a"
        AES.Mode = Security.Cryptography.CipherMode.CBC
        AES.IV = HexStringToBytes(iv_string)
        Dim Encrypter As System.Security.Cryptography.ICryptoTransform = AES.CreateEncryptor
        Dim b() As Byte = System.IO.File.ReadAllBytes("calendar.gif")
        System.IO.File.WriteAllBytes("calendar.gif.enc", (Encrypter.TransformFinalBlock(System.IO.File.ReadAllBytes("calendar.gif"), 0, b.Length)))
    End Sub

This is the javascript code that downloads calendar.gif.encas binary, decrypts, and makes an image:

这是下载calendar.gif.enc为二进制文件、解密并生成图像的 javascript 代码:

    function wordArrayToBase64(wordArray) {
      var words = wordArray.words;
      var sigBytes = wordArray.sigBytes;

      // Convert
      var output = "";
      var chr = [];
      for(var i = 0; i < sigBytes; i++) {
        chr.push((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
        if(chr.length == 3) {
          var enc = [
            (chr[0] & 0xff) >> 2,
            ((chr[0] & 3) << 4) | ((chr[1] & 0xff) >> 4),
            ((chr[1] & 15) << 2) | ((chr[2] & 0xff) >> 6),
            chr[2] & 63
          ];
          for(var j = 0; j < 4; j++) {
            output += Base64._keyStr.charAt(enc[j]);
          }
          chr = [];
        }
      }
      if(chr.length == 1) {
        chr.push(0,0);
        var enc = [
          (chr[0] & 0xff) >> 2,
          ((chr[0] & 3) << 4) | ((chr[1] & 0xff) >> 4),
          ((chr[1] & 15) << 2) | ((chr[2] & 0xff) >> 6),
          chr[2] & 63
        ];
        enc[2] = enc[3] = 64;
        for(var j = 0; j < 4; j++) {
          output += Base64._keyStr.charAt(enc[j]);
        }
      } else if(chr.length == 2) {
        chr.push(0);
        var enc = [
          (chr[0] & 0xff) >> 2,
          ((chr[0] & 3) << 4) | ((chr[1] & 0xff) >> 4),
          ((chr[1] & 15) << 2) | ((chr[2] & 0xff) >> 6),
          chr[2] & 63
        ];
        enc[3] = 64;
        for(var j = 0; j < 4; j++) {
          output += Base64._keyStr.charAt(enc[j]);
        }
      }
    return(output);
  }
  var xhr = new XMLHttpRequest();
  xhr.overrideMimeType('image/gif; charset=x-user-defined');
  xhr.onreadystatechange = function() {
    if(xhr.readyState == 4) {
      var key = CryptoJS.enc.Hex.parse('603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4');
      var iv  = CryptoJS.enc.Hex.parse('000102030405060708090A0B0C0D0E0F');
      var aesEncryptor = CryptoJS.algo.AES.createDecryptor(key, { iv: iv });
      var words = [];
      for(var i=0; i < (xhr.response.length+3)/4; i++) {
        var newWord = (xhr.response.charCodeAt(i*4+0)&0xff) << 24;
        newWord += (xhr.response.charCodeAt(i*4+1)&0xff) << 16;
        newWord += (xhr.response.charCodeAt(i*4+2)&0xff) << 8;
        newWord += (xhr.response.charCodeAt(i*4+3)&0xff) << 0;
        words.push(newWord);
      }            
      var inputWordArray = CryptoJS.lib.WordArray.create(words, xhr.response.length);
      var ciphertext0 = aesEncryptor.process(inputWordArray);
      var ciphertext1 = aesEncryptor.finalize();

      $('body').append('<img src="data:image/gif;base64,' + wordArrayToBase64(ciphertext0.concat(ciphertext1)) + '">');
      $('body').append('<p>' + wordArrayToBase64(ciphertext0.concat(ciphertext1)) + '</p>');
    }
  };

Caveats:

注意事项:

  • I used a fixed IV and fixed password. You should modify the code to generate a random IV during encryption and prepend them as the first bytes of the output file. The javascript needs to be modified, too, to extract these bytes.
  • The password length should be fixed: 256-bits for AES-256. If the password isn't 256 bytes, one possibility is to use AES hashing to hash the password to 256 bits in length in both encryption and decryption.
  • You'll need crypto-js.
  • overrideMimeType might not work on older browsers. You need this so that the binary data will get downloaded properly.
  • 我使用了固定的 IV 和固定的密码。您应该修改代码以在加密期间生成随机 IV,并将它们作为输出文件的第一个字节。javascript 也需要修改,以提取这些字节。
  • 密码长度应该是固定的:AES-256 为 256 位。如果密码不是 256 字节,一种可能性是使用 AES 散列在加密和解密中将密码散列到 256 位长度。
  • 你需要crypto-js
  • overrideMimeType 可能不适用于旧浏览器。您需要这样做才能正确下载二进制数据。