Convert number to ArrayBuffer
Asked Answered
G

5

8

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
}
Gosh answered 8/4, 2019 at 23:16 Comment(3)
How did you create an integer counter during encryption?Harter
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.Gosh
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.Gosh
D
12

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

new Int32Array([n]).buffer
Diactinic answered 8/3, 2021 at 2:49 Comment(1)
Nice, but numbers >32 bit aren't supported. For numbers up to 64-bit: new BigUint64Array([BigInt(n)]).buffer.Abbottson
A
4

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
}
Aggressive answered 10/12, 2020 at 1:39 Comment(0)
H
1

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

Harter answered 9/4, 2019 at 0:28 Comment(0)
D
1

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.

Dejecta answered 9/4, 2019 at 8:51 Comment(0)
O
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}`

Usage:
toBytes(5, "u8")

Odontograph answered 8/6, 2021 at 5:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.