工作中有这样一个需求:
1)制作一个U盘离线版阅读器,只能访问U盘中对应的书本内容;
2)如果将U盘中的内容拷贝到其他磁盘,则无法打开阅读器。
以下是我的一个方案思路,因为一些原因未在实际工作中采用,故在此抛砖引玉权作交流。
环境:Windows
框架:Electron
语言:JS
一、方案思路
要让程序具有排他性,就要让它与U盘建立绑定关系。整体思路是:
1)当启动程序时,获取到程序所在磁盘的物理ID,然后与程序绑定的ID做对比,相等则通过,不相等则直接关闭程序;
2)因为此处使用的磁盘物理ID通过第三方工具是可以修改的,为防止别人通过“修改物理ID”加“拷贝资源文件”克隆更多U盘实例,于是又增加了时间校验,多一层防护;
3)既然上述步骤需要校验磁盘物理ID,就必须在程序中预设U盘ID,此处使用一个单独的加密文件(disk.disc)来存储敏感信息,当启动程序时,在内存中解密该文件数据来使用。
二、生成加密文件
为方便U盘制作者,编写了一个小工具,该工具也是用Electron开发的(nodejs环境),没有直接获取磁盘物理ID的API,需要使用child_process模块执行DOS命令来获取。
wmic logicaldisk get Caption,VolumeSerialNumber,Description /format:list
制作者可以选择目标盘符来生成特定加密文件(disk.disc)。
三、程序启动时的校验
实现比较简单,直接看主要代码:
function checkDiskDisc() {
// disk.disc内容
let diskDiscPath = 'X:\\xx\\disk.disc' // disk.disc文件路径
let diskDiscInfo = {} // 用于存储解密后的disk.disc文件数据
// 当前程序所在磁盘信息
let baseDataPath = 'X:\\xx\\xx' // 程序中的某个特定文件路径,用于时间校验
let baseDataInfo = fs.statSync(baseDataPath) // 特定文件信息
let baseDataDiskCode = baseDataPath.slice(0, 2).toUpperCase() // 当前程序所在磁盘盘符
let command = 'chcp 65001 & wmic logicaldisk get Caption,VolumeSerialNumber /format:list'
return new Promise((resolve, reject) => {
if (!fs.existsSync(diskDiscPath)) {return reject('资源文件不存在')}
try {
let data = fs.readFileSync(diskDiscPath)
diskDiscInfo = decrypt(data) // 解密disk.disc文件,假设解密后得到一个JSON对象
exec(command, {encoding: 'buffer'}, (error, stdout, stderr) => {
if (error) { return reject('命令执行失败,请以管理员身份运行程序后重试') }
let str = iconv.decode(stdout, 'utf8') || '' // 命令执行结果-编码处理
let lst = str.match(/.*=.*/g)
let result = [] // 用于存储电脑上所有磁盘的信息
let count = {}
lst && lst.forEach(v => {
let arr = v.split('=')
arr = arr.map(v => (v || '').trim())
if (!count[arr[0]] && count[arr[0]] !== 0) {count[arr[0]] = 0}
else {count[arr[0]] = count[arr[0]] + 1}
let index = count[arr[0]]
if (!result[index]) {result[index] = {}}
result[index][arr[0]] = arr[1]
})
result = result.map(v => {
return {
label: (v.Caption || '').toUpperCase(),
value: v.VolumeSerialNumber
}
})
let diskList = result.filter(v => v.value)
let diskItem = diskList.filter(v => v.label === baseDataDiskCode)[0] || {}
let timeDiff = diskDiscInfo.testDate - baseDataInfo.ctimeMs
// 物理ID校验、时间校验(规则根据实际需求调整)
if (diskItem.value === diskDiscInfo.deviceId && timeDiff > 0 && timeDiff < 2592000000) {
resolve(true) // 通过
} else {
resolve(false) // 不通过
}
})
} catch (error) {
reject('读取资源文件失败')
}
})
}
Tags:difftime