react-native-quick-crypto
Version:
A fast implementation of Node's `crypto` module written in C/C++ JSI
173 lines (167 loc) • 5.5 kB
JavaScript
;
import { NitroModules } from 'react-native-nitro-modules';
import Stream from 'readable-stream';
import { Buffer } from '@craftzdog/react-native-buffer';
// @types/node
import { ab2str, binaryLikeToArrayBuffer } from './utils';
import { getDefaultEncoding, getUIntOption, normalizeEncoding, validateEncoding } from './utils/cipher';
class CipherUtils {
static native = NitroModules.createHybridObject('Cipher');
static getSupportedCiphers() {
return this.native.getSupportedCiphers();
}
}
export function getCiphers() {
return CipherUtils.getSupportedCiphers();
}
class CipherCommon extends Stream.Transform {
constructor({
isCipher,
cipherType,
cipherKey,
iv,
options
}) {
// Explicitly create TransformOptions for super()
const streamOptions = {};
if (options) {
// List known TransformOptions keys (adjust if needed)
const transformKeys = ['readableHighWaterMark', 'writableHighWaterMark', 'decodeStrings', 'defaultEncoding', 'objectMode', 'destroy', 'read', 'write', 'writev', 'final', 'transform', 'flush'
// Add any other relevant keys from readable-stream's TransformOptions
];
for (const key of transformKeys) {
if (key in options) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
streamOptions[key] = options[key];
}
}
}
super(streamOptions); // Pass filtered options
const authTagLen = getUIntOption(options ?? {}, 'authTagLength') !== -1 ? getUIntOption(options ?? {}, 'authTagLength') : 16; // defaults to 16 bytes
const factory = NitroModules.createHybridObject('CipherFactory');
this.native = factory.createCipher({
isCipher,
cipherType,
cipherKey: binaryLikeToArrayBuffer(cipherKey),
iv: binaryLikeToArrayBuffer(iv),
authTagLen
});
}
update(data, inputEncoding, outputEncoding) {
const defaultEncoding = getDefaultEncoding();
inputEncoding = inputEncoding ?? defaultEncoding;
outputEncoding = outputEncoding ?? defaultEncoding;
if (typeof data === 'string') {
validateEncoding(data, inputEncoding);
} else if (!ArrayBuffer.isView(data)) {
throw new Error('Invalid data argument');
}
const ret = this.native.update(binaryLikeToArrayBuffer(data, inputEncoding));
if (outputEncoding && outputEncoding !== 'buffer') {
return ab2str(ret, outputEncoding);
}
return Buffer.from(ret);
}
final(outputEncoding) {
const ret = this.native.final();
if (outputEncoding && outputEncoding !== 'buffer') {
return ab2str(ret, outputEncoding);
}
return Buffer.from(ret);
}
_transform(chunk, encoding, callback) {
this.push(this.update(chunk, normalizeEncoding(encoding)));
callback();
}
_flush(callback) {
this.push(this.final());
callback();
}
setAutoPadding(autoPadding) {
const res = this.native.setAutoPadding(!!autoPadding);
if (!res) {
throw new Error('setAutoPadding failed');
}
return this;
}
setAAD(buffer, options) {
// Check if native parts are initialized
if (!this.native || typeof this.native.setAAD !== 'function') {
throw new Error('Cipher native object or setAAD method not initialized.');
}
const res = this.native.setAAD(buffer.buffer, options?.plaintextLength);
if (!res) {
throw new Error('setAAD failed (native call returned false)');
}
return this;
}
getAuthTag() {
return Buffer.from(this.native.getAuthTag());
}
setAuthTag(tag) {
const res = this.native.setAuthTag(binaryLikeToArrayBuffer(tag));
if (!res) {
throw new Error('setAuthTag failed');
}
return this;
}
getSupportedCiphers() {
return this.native.getSupportedCiphers();
}
}
class Cipheriv extends CipherCommon {
constructor(cipherType, cipherKey, iv, options) {
super({
isCipher: true,
cipherType,
cipherKey: binaryLikeToArrayBuffer(cipherKey),
iv: binaryLikeToArrayBuffer(iv),
options
});
}
}
class Decipheriv extends CipherCommon {
constructor(cipherType, cipherKey, iv, options) {
super({
isCipher: false,
cipherType,
cipherKey: binaryLikeToArrayBuffer(cipherKey),
iv: binaryLikeToArrayBuffer(iv),
options
});
}
}
export function createDecipheriv(algorithm, key, iv, options) {
return new Decipheriv(algorithm, key, iv, options);
}
export function createCipheriv(algorithm, key, iv, options) {
return new Cipheriv(algorithm, key, iv, options);
}
/**
* xsalsa20 stream encryption with @noble/ciphers compatible API
*
* @param key - 32 bytes
* @param nonce - 24 bytes
* @param data - data to encrypt
* @param output - unused
* @param counter - unused
* @returns encrypted data
*/
export function xsalsa20(key, nonce, data,
// @ts-expect-error haven't implemented this part of @noble/ciphers API
// eslint-disable-next-line @typescript-eslint/no-unused-vars
output,
// @ts-expect-error haven't implemented this part of @noble/ciphers API
// eslint-disable-next-line @typescript-eslint/no-unused-vars
counter) {
const factory = NitroModules.createHybridObject('CipherFactory');
const native = factory.createCipher({
isCipher: true,
cipherType: 'xsalsa20',
cipherKey: binaryLikeToArrayBuffer(key),
iv: binaryLikeToArrayBuffer(nonce)
});
const result = native.update(binaryLikeToArrayBuffer(data));
return new Uint8Array(result);
}
//# sourceMappingURL=cipher.js.map