expo-standard-web-crypto
Version:
A partial implementation of the W3C Crypto API for Expo
72 lines (62 loc) • 2.4 kB
text/typescript
import { getRandomValues as expoCryptoGetRandomValues } from 'expo-crypto';
const MAX_RANDOM_BYTES = 65536;
type IntegerArray =
| Int8Array
| Uint8Array
| Int16Array
| Uint16Array
| Int32Array
| Uint32Array
| Uint8ClampedArray;
/**
* An implementation of Crypto.getRandomValues that uses expo-random's secure random generator if
* available and falls back to Math.random (cryptographically insecure) when synchronous bridged
* methods are unavailable.
*
* See https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues
*/
export default function getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray {
if (arguments.length < 1) {
throw new TypeError(
`An ArrayBuffer view must be specified as the destination for the random values`
);
}
if (
!(values instanceof Int8Array) &&
!(values instanceof Uint8Array) &&
!(values instanceof Int16Array) &&
!(values instanceof Uint16Array) &&
!(values instanceof Int32Array) &&
!(values instanceof Uint32Array) &&
!(values instanceof Uint8ClampedArray)
) {
throw new TypeError(`The provided ArrayBuffer view is not an integer-typed array`);
}
if (values.byteLength > MAX_RANDOM_BYTES) {
throw new QuotaExceededError(
`The ArrayBuffer view's byte length (${values.byteLength}) exceeds the number of bytes of entropy available via this API (${MAX_RANDOM_BYTES})`
);
}
try {
// NOTE: Consider implementing `fillRandomBytes` to populate the given TypedArray directly
expoCryptoGetRandomValues(values);
} catch {
// TODO: rethrow the error if it's not due to a lack of synchronous methods
console.warn(`Random.getRandomBytes is not supported; falling back to insecure Math.random`);
return getRandomValuesInsecure(values);
}
return values;
}
export function getRandomValuesInsecure<TArray extends IntegerArray>(values: TArray): TArray {
// Write random bytes to the given TypedArray's underlying ArrayBuffer
const byteView = new Uint8Array(values.buffer, values.byteOffset, values.byteLength);
for (let i = 0; i < byteView.length; i++) {
// The range of Math.random() is [0, 1) and the ToUint8 abstract operation rounds down
byteView[i] = Math.random() * 256;
}
return values;
}
class QuotaExceededError extends Error {
name = 'QuotaExceededError';
code = 22; // QUOTA_EXCEEDED_ERR
}