离线资源的加密保护

上次提到过工作中有个阅读器项目要做成离线版,其中涉及到离线资源的加密保护,需求如下:

1)产品为U盘形式,里面包含阅读器程序和课本资源两部分;

2)课本资源包括视频、音频、HTML5等格式,为了防止用户拷贝传播这些数据,考虑对它们资源进行加密以增加安全性;

3)课本资源整体是HTML5形式,首页是一个书本目录,下面包含很多单元,点击某个单元会跳转到对应的页面;

4)课本资源总体积好几G,每个单元约200多M,要支持远程对单个单元数据进行更新。

下文是为此设计的一个资源加密的粗简方案(毕竟不存在绝对的安全,所以这里并不追求加密的复杂性,仅仅只做个最基础的保护)。

        环境:Windows

        框架:Electron

        语言:JS

        资源加密,其实就是对正常文件进行破坏,只要用的时候有办法恢复就行。恢复的时候,避免将临时数据放到本地磁盘,以免被用户拷贝。

        根据文件类型的不同,加密的方式和难度也不同。

一、视频音频

        视频音频,破坏起来比较容易,可以用AES,或者对局部Buffer数据进行异或运算(参考微信DAT文件加密方式),往往稍微修改一点就可以导致其无法正常播放。

        当阅读器播放音频视频的时候,首先读取到文件的Buffer数据(加密),然后对其进行解密得到新的Buffer数据,最后将其转换成blob地址并交给video标签或audio标签播放即可。

        核心代码如下:

// 读取资源+解密+转换为URL

function res2url ({fileName, mimeType}) {

  if (!fileName) return Promise.resolve()

  if (fileName.includes('/')) {

    fileName = fileName.split('/').pop()

  }

  let filePath = path.join(resDir, fileName) // resDir资源目录,fileName文件名

  return new Promise((resolve, reject) => {

    fs.readFile(filePath, (err, data) => {

      if (err) {

        reject(err)

      } else {

        data = decrypt(data) // 解密

        let bufferData = new Uint8Array(data)

        let blobData = new Blob([bufferData], { type: mimeType })

        resolve(URL.createObjectURL(blobData))

      }

    })

  })

}

二、HTML5

        H5资源,因为文件数量众多,单个加密解密耗时耗力,再结合资源目录结构,以及单元更新的需求,考虑设计方案如下:

        1)将目录文件夹和单元文件夹分别打包成ZIP,再对每个ZIP包进行加密(加密方式不能只修改局部Buffer,这样只会对部分文件造成损坏);

        2)当要更新单元数据时,替换掉相应的单元ZIP加密包即可;

        3)当阅读器要使用H5时,首先用express启动一个WEB服务器,同时将课本目录ZIP解密并用Jszip装载,然后在阅读器中用iframe加载WEB服务器首页(比如访问index.html),服务器监听到请求后,从jszip对象中取出对应的文件返回即可;

        4)当要访问某个单元时,根据地址可以判断出需要从哪个ZIP包中获取,然后读取该ZIP包并解密、用Jszip装载,然后将数据返回。

        Demo示例如下:

        Demo核心代码:

// 读取资源 + 解密 => jszip对象

loadZipItem (zipName) {

  let zipPath = this.zipPathDict[zipName] // ZIP文件路径

  let zipData = fs.readFileSync(zipPath) // ZIP文件数据

  zipData = this.decrypt(zipData) // 解密ZIP文件数据

  return JSZip.loadAsync(zipData).then((zip) => {

    zipDataDict[zipName] = zip // 保存jszip对象,方便后续调用

  })

},

// 根据请求地址path获取对应的jszip对象

async getZipData (_path) {

  let _arr = _path.split('/')

  // 默认返回入口ZIP文件

  if (_arr.length === 1 || !this.zipPathDict[_arr[0]]) { return zipDataDict['main'] }

  // 读取子ZIP文件,如果不存在则加载

  if (!zipDataDict[_arr[0]]) {

    await this.loadZipItem(_arr[0])

  }

  // 返回计算后的ZIP文件

  return zipDataDict[_arr[0]]

},

// 启动WEB服务

async serverStart () {

  let app = express()

  await this.loadZipItem('main') // 加载入口ZIP文件

  app.get('/:path(*)', async (req, res) => {

    let _path = req.params.path

    if (this.zipChildPathPrefix && _path.indexOf(this.zipChildPathPrefix) === 0) {

      _path = _path.substring(this.zipChildPathPrefix.length)

    }

    let _zip = await this.getZipData(_path)

    let _file = _zip.file(_path)

    let _fileName = path.basename(_path)

    if (_file) {

      _file.async('nodebuffer').then((content) => {

        res.type(_fileName)

        res.send(content)

      }).catch((err) => {

        res.status(500).send('读取文件内容时出错')

      })

    } else {

      res.status(404).send('文件未找到')

    }

  })

  app.listen(this.port, () => {

    this.result += `服务器运行在 http://localhost:${this.port}` + '\n'

  })

}

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

推荐阅读更多精彩内容

  • 一、Vue原理:4部分: 1.通过document.createDocumentFragment()创建虚拟dom...
    战神七小姐阅读 394评论 0 0
  • 注意事项 Web API 的大多数都不可用,比如window、document等通用的只有==console==、...
    HouJ_8307阅读 117评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,991评论 19 139
  • 1、所需物料准备 1、jszip.min.js 仓库地址:https://github...
    e13673af57cb阅读 3,609评论 0 0
  • 33、JS中的本地存储 把一些信息存储在当前浏览器指定域下的某一个地方(存储到物理硬盘中)1、不能跨浏览器传输:在...
    萌妹撒阅读 2,112评论 0 2