Protecting audio and music assets with Node and Javascript

In my previous post I discussed my latest small project of building an external music player for Bandcamp. What I realized is that many similar sites and services can easily be abused for pirating content, in particular copyrighted audio, music and video. In this post I will discuss several strategies for protecting such content.


Obtaining mp3 files (and other digital content) can usually be done by looking at the HTTP requests that are being made upon playing/using that particular content. In Bandcamp's case I only had to look at the network traffic and spot the "mpeg" data type of 5.37MB in size, then by copy pasting the GET URL you can download its corresponding mp3 file.

Today it's nearly impossible to fully secure digital content, there's always some way of obtaining it. But the purpose of security systems is to make the hacker's / pirate's life very painful. Either by making the process very long and/or complex, in the hope of them giving up.

A very basic, yet quite effective method is to encrypt the sensitive assets. In Bandcamp's case, they can encrypt the mp3 contents server-side using some key, send it to the client, and let the client's JavaScript code decrypt and play it. The client can still download the encrypted mp3 file, but without the proper decryption algorithm it's a useless file. This method is only as effective as our ability of hiding and obfuscating the decryption function.

In the code below I show my prototype for doing all of this.

NodeJS server code

"use strict";

const express = require("express")

const app = express()

const { Readable } = require('stream')

const fs = require('fs')

app.get("/audio", function (req, res) {

  res.setHeader('Access-Control-Allow-Origin','*')

  xor_encrypt(res)

})

function xor_encrypt(res) {

  // read audio file to buffer

  let buff = fs.readFileSync('./audio.mp3')

  // determine encryption key

  let key = buff[buff.length-1]

  // encrypt buffer contents

  buff = buff.map(x => x ^ key).map(x => ~x)

  // store the encryption key as last element

  buff[buff.length-1] = key

  // transform buffer to stream

  let readStream = Readable.from(buff)

  // send stream to client

  readStream.pipe(res)

  readStream.on('end', () => {

    res.status(200).send()

  })

}

app.use(express.static('.'))

const serverHost =  "localhost"

const serverPort =  3007

app.listen(serverPort)

JS client code

let curr_track = document.createElement('audio')

var oReq = new XMLHttpRequest()

oReq.open("GET", 'http://localhost:3007/audio', true)

oReq.responseType = "arraybuffer"

oReq.onload = function(oEvent) {

  xor()

}

oReq.send()

function xor() {

  // convert arrayBuffer to regular Array

  const arr = oReq.response

  var byteArray = new Uint8Array(arr)

  // obtain encryption key

  let key = byteArray[byteArray.length - 1]

  // use key to decrypt contents

  byteArray = byteArray.map(x => x ^ key).map(x => ~x)

  // restore key

  byteArray[byteArray.length - 1] = key

  // convert byteArray to Blob

  const blob = new Blob([byteArray], { type: 'audio/mp3' })

  // create playable URL from Blob object

  const url = URL.createObjectURL(blob) // memory leak possible!

  curr_track.src = url

  curr_track.load()

}

// now you can bind 'curr_track.play()' to some click-event

If you want to protect the client javascript code or nodejs code,you can use a js obfuscator,like JShaman or JScrambler etc.

In the client code, the url variable points to a temporary in-memory Blob object representing the mp3 file. If you print this url to console you will get something like this:

blob:http://localhost:3007/9a2ffb47-72af-4c58-a0f9-08b9a63b81d0

If you then copy paste this into a new tab you'll be able to play/download the decrypted mp3 track. This Blob object exists in-memory as long as your website window remains open, else it gets garbage collected; this also means that creating many Blobs can lead to memory leaks (but there is a method for cleaning them up manually).

This encryption strategy works fine, we made it harder for users to download mp3 files. It's still possible once a user figures out how the decrypt function works, then they can automate it. Or by debugging/editing the JavaScript code they can similarly obtain the mp3 file.

Alternatively, instead of using a Blob object, you could use base64 encoding, but that's just as trivial as Blobs are at decoding and downloading the binary contents.

A further improvement is to use many different encryption/decryption methods (instead of one) at random, but then again some kind of identifier will be needed to determine which method should be used client-sided. Once again the hacker/pirate can figure this out.

The bottom line is that we use the html5 tag for playing tracks, more specifically by providing an URL for its src attribute. To provide more security we should investigate different methods and techniques for playing audio without the need of using the audio tag.

原文链接:http://www.fairysoftware.com/tips/202404180001.html

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • # Awesome Python [![Awesome](https://cdn.rawgit.com/sindr...
    emily_007阅读 2,257评论 0 3
  • C++ 开源库列表https://en.cppreference.com/w/cpp/links/libs[htt...
    老陕西阅读 2,288评论 0 0
  • 在互联网飞速发展的今天,相信很多程序开发者对 GitHub 很熟悉,我们在这里学习知识、分享自己的开源库或者开源代...
    腾飞Tenfay阅读 3,374评论 0 6
  • Linear PCM 在介绍Core Audio之前,先介绍一下最常用的非压缩数字音频格式Linear PCM(线...
    huangjun0阅读 4,587评论 0 2
  • 用到的组件 1、通过CocoaPods安装 2、第三方类库安装 3、第三方服务 友盟社会化分享组件 友盟用户反馈 ...
    SunnyLeong阅读 14,788评论 1 180