admin管理员组

文章数量:1201801

I'm trying to decrypt data on the browser using the AES-CTR algo. The WebCrypto API requires the counter to be passed as a BufferSource. How do I convert the counter (a number) to the expected input (a byte array)?

I'm using an all zero IV, so the counter starts at 0. Let's say I'm trying to decrypt data where counter = 445566. How do I convert 445566 into an 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)

EDIT: After digging around some crypto libraries, I ended up using this. It seems to do what I want, but no idea on correctness, performance, etc.

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
}

I'm trying to decrypt data on the browser using the AES-CTR algo. The WebCrypto API requires the counter to be passed as a BufferSource. How do I convert the counter (a number) to the expected input (a byte array)?

I'm using an all zero IV, so the counter starts at 0. Let's say I'm trying to decrypt data where counter = 445566. How do I convert 445566 into an 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)

EDIT: After digging around some crypto libraries, I ended up using this. It seems to do what I want, but no idea on correctness, performance, etc.

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
}
Share Improve this question edited Apr 9, 2019 at 0:02 Eric Guan asked Apr 8, 2019 at 23:16 Eric GuanEric Guan 16k10 gold badges51 silver badges62 bronze badges 3
  • How did you create an integer counter during encryption? – karthick Commented Apr 8, 2019 at 23:50
  • Like so, in node: const iv = Buffer.alloc(16). I was told an all-zero iv was okay if the key is used to encrypt only a single message. – Eric Guan Commented Apr 8, 2019 at 23:58
  • On the client, i'm expecting to decrypt data for a given byte range, like 243-695. So i need to derive a counter ArrayBuffer from the byte range's first value. – Eric Guan Commented Apr 9, 2019 at 0:07
Add a comment  | 

5 Answers 5

Reset to default 12

There is a one-liner solution for IE10+ :)

new Int32Array([n]).buffer

This is what I use:

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
}

Not sure if this is helpful. But just to cross check the authenticity of the code.

I wrote some test case to first convert the number to array buffer and use the array buffer value to decode it to the same.

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]

Both the results are same. It's just that your code is producing the result in big endian format and mine in little endian.

So the approach you have followed is correct. As for the performance i don't think there is much impact

You don't need to do anything, the ArrayBuffer is initialized to bytes all with value zero, which of course also represents the number value zero using in statically unsigned, big endian encoding - which is the common way of representing the counter in CTR mode.

Here is the description of the return value of ArrayBuffer with just a specified size:

A new ArrayBuffer object of the specified size. Its contents are initialized to 0.

so there you go, you were ready without knowing it. The only thing I would certainly do is to comment that the counter in the buffer has been initialized to zero for the next developer (which may actually be you in a couple of years).

Note that CTR mode is secure if the counter is never repeated for the same key. For this, the nonce (which takes an unknown number of bytes to the left hand, most significant side) is commonly set to a random value, usually by simply initializing the first 8 bytes to random values. So you don't need to do any number encoding for that either.

You only need to encode the nonce if it consists of a 32 or 64 bit counter itself.

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}`

Usage:
toBytes(5, "u8")

本文标签: javascriptConvert number to ArrayBufferStack Overflow