将数字转换为ArrayBuffer

7
我正在尝试使用AES-CTR算法在浏览器上解密数据。WebCrypto API要求将计数器作为BufferSource传递。如何将计数器(一个数字)转换为期望的输入(一个字节数组)?
我使用的是全零IV,所以计数器从0开始。假设我正在尝试解密计数器为445566的数据。如何将445566转换为ArrayBuffer?
const key = // retrieve decryption key
const encrypted = // retrieve encrypted data

const iv = new ArrayBuffer(16)
// iv is all zeros. I need it to represent 445566, how?

const algo = {
    name: 'AES-CTR',
    counter: iv,
    length: 128
}
const decrypted = await crypto.subtle.decrypt(algo, key, encrypted)

编辑:在查找一些加密库后,我最终使用了这个。它似乎可以做到我想要的,但对于正确性、性能等方面没有任何想法。

function numberToArrayBuffer(value) {
    const view = new DataView(new ArrayBuffer(16))
    for (var index = 15; index >= 0; --index) {
      view.setUint8(index, value % 256)
      value = value >> 8;
    }
    return view.buffer
}

你是如何在加密过程中创建一个整数计数器的? - karthick
在Node中,可以这样写:const iv = Buffer.alloc(16)。如果密钥仅用于加密单个消息,则可以使用全零IV。 - Eric Guan
在客户端,我希望解密给定字节范围内的数据,例如243-695。因此,我需要从字节范围的第一个值派生出一个计数器ArrayBuffer。 - Eric Guan
5个回答

11

针对IE10+,有一个一行代码的解决方案 :)

new Int32Array([n]).buffer

4
这是我使用的内容:
const bytesArray = (n) => {
  if (!n) return new ArrayBuffer(0)
  const a = []
  a.unshift(n & 255)
  while (n >= 256) {
    n = n >>> 8
    a.unshift(n & 255)
  }
  return new Uint8Array(a).buffer
}

1

您不需要做任何事情,ArrayBuffer已经初始化为所有值为零的字节,这当然也代表了使用静态无符号、大端编码表示的数字值零——这是在CTR模式下表示计数器的常见方式。

以下是仅指定大小的ArrayBuffer返回值的描述:

指定大小的新ArrayBuffer对象。其内容已初始化为0。

所以你已经准备好了,而你自己可能在未来的几年里就是下一个开发者,我唯一确定要做的就是为缓冲区中的计数器添加注释,说明它已经被初始化为零。

请注意,如果对于相同的密钥计数器从未重复,CTR模式是安全的。为此,随机数(它占据了左侧最高有效位的未知数量的字节)通常被设置为随机值,通常只需将前8个字节初始化为随机值即可。因此,您也不需要为此进行任何数字编码。

只有当nonce本身由32位或64位计数器组成时,才需要对其进行编码。


1

不确定这是否有帮助。但是只是为了验证代码的真实性。

我编写了一些测试用例,首先将数字转换为数组缓冲区,然后使用数组缓冲区值将其解码为相同的数字。

var hex = 445566..toString(16);
var buffer = new ArrayBuffer(16);
var dataView = new DataView(buffer);
dataView.setInt32(0, '0x'+ hex);
console.log(dataView.getInt32(0)); //445566


//Using the uint16array data generated by the above code 
var data = [0, 6, 204, 126, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
var buf = new ArrayBuffer(16);
var view = new DataView(buf);
data.forEach(function (b, i) {
    view.setUint8(i, b % 256);
});
var num = view.getInt32(0);
console.log(num);//445566


function numberToArrayBuffer(value) {
    const view = new DataView(new ArrayBuffer(16))
    for (var index = 15; index >= 0; --index) {
      view.setUint8(index, value % 256)
      value = value >> 8;
    }
    return view.buffer
}

console.log(numberToArrayBuffer(445566)) //  Uint8Array(16) [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 204, 126]

两个结果是一样的。只是你的代码以大端格式生成结果,而我的以小端格式生成结果。

所以你采用的方法是正确的。至于性能,我认为影响不大。


0
const uIntToBytes = (num, size, method) => {
   const arr = new ArrayBuffer(size)
   const view = new DataView(arr)
   view[method + (size * 8)](0, num)
   return arr
}

const toBytes = (data, type) =>
   type == "u8"  ? uIntToBytes(data, 1, "setUint") :
   type == "u16" ? uIntToBytes(data, 2, "setUint") :
   type == "u32" ? uIntToBytes(data, 4, "setUint") :
   type == "u64" ? uIntToBytes(BigInt(data), 8, "setBigUint")
                 : `Not Sure about type - ${type}`

使用方法:
toBytes(5, "u8")


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接