2

I create a HTTP server that streams a video file.

http.createServer((req, res) => {
  const file = 'example.mp4';
  const size = fs.statSync(file).size;
  res.writeHead(200, { 'Content-Length': size, 'Content-Type': 'video/mp4' });
  fs.createReadStream(file).pipe(res);
}).listen(1911, '127.0.0.1');

I connect to it in my browser or video player to verify that it works. It does.

I encrypt a file:

fs.createReadStream('example.mp4')
  .pipe(crypto.createCipher('aes-256-ctr', 'x'))
  .pipe(fs.createWriteStream('encrypted_file'));

I decrypt it and play it back to verify that it works. It does.

Yet combining that decryption and streaming in the manner below fails silently.

const decrypt = crypto.createDecipher('aes-256-ctr', 'x');
http.createServer((req, res) => {
   const file = 'encrypted_file';
   const size = fs.statSync(file).size;
   res.writeHead(200, { 'Content-Length': size, 'Content-Type': 'video/mp4' });
   fs.createReadStream(file).pipe(decrypt).pipe(res);
}).listen(1911, '127.0.0.1');

The original and the encrypted file are the same size in bytes, and the original and the encrypted-then-decrypted file both have the same SHA-256 hash. Given that, I'd expect fs.createReadStream(original) and fs.createReadStream(encrypted).pipe(decrypt) to behave identically -- yet they don't. No video data is sent to the user, but no error is displayed to them either, and the error event never fires on the http.Server instance.

What am I missing?

Ryan Plant
  • 1,037
  • 1
  • 11
  • 18

1 Answers1

1

Your deciphering code look ok and works in my variant correctly for short file http://techslides.com/demos/sample-videos/small.mp4 in my test (code based on https://gist.github.com/paolorossi/1993068 which is based on Video streaming with HTML 5 via node.js):

var http = require('http'),
    fs = require('fs'),
    util = require('util'),
    crypto = require('crypto');

http.createServer(function (req, res) {
  var path = 'encrypted';
  var stat = fs.statSync(path);
  var total = stat.size;
  const decrypt = crypto.createDecipher('aes-256-ctr', 'x');
  console.log('ALL: ' + total);
  res.writeHead(200, { 'Content-Length': total, 'Content-Type': 'video/mp4' });
  fs.createReadStream(path).pipe(decrypt).pipe(res);
}).listen(1912, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1912/');

Your file may be bigger, so it can be useful to print to console request from the client. In my tests both unencrypted and encrypted servers got two requests and sent 383631 bytes.

The differences between my and your variant are: this is my first node.js server (and one of the first js programs) and it is not first for you. And I don't declare decrypt as global const but as local const. By debug prints I see two requests from the browser; and second one tries to modify global const decrypt in your variant with the error:

Server running at http://127.0.0.1:1912/
ALL: 383631
ALL: 383631
events.js:141
      throw er; // Unhandled 'error' event
      ^

Error: write after end
    at writeAfterEnd (_stream_writable.js:159:12)
    at Decipher.Writable.write (_stream_writable.js:204:5)
    at ReadStream.ondata (_stream_readable.js:528:20)
    at emitOne (events.js:77:13)
    at ReadStream.emit (events.js:169:7)
    at readableAddChunk (_stream_readable.js:146:16)
    at ReadStream.Readable.push (_stream_readable.js:110:10)
    at onread (fs.js:1743:12)
    at FSReqWrap.wrapper [as oncomplete] (fs.js:576:17)

So move decrypt inside server code. Your error is reusing of Decipher object which cant be reused (https://nodejs.org/api/crypto.html#crypto_class_decipher "Once the decipher.final() method has been called, the Decipher object can no longer be used to decrypt data."); and on second call decipher may try to decode file with wrong (non-zero) counter value.

Community
  • 1
  • 1
osgx
  • 90,338
  • 53
  • 357
  • 513