UNPKG

react-native-quick-crypto

Version:

A fast implementation of Node's `crypto` module written in C/C++ JSI

140 lines (121 loc) 4.07 kB
import { NativeQuickCrypto } from './NativeQuickCrypto/NativeQuickCrypto'; import type { InternalHash } from './NativeQuickCrypto/hash'; import { type Encoding, toArrayBuffer, validateMaxBufferLength, normalizeHashName, type BufferLike, bufferLikeToArrayBuffer, } from './Utils'; import Stream from 'readable-stream'; import { Buffer } from '@craftzdog/react-native-buffer'; import { lazyDOMException } from './Utils'; import type { SubtleAlgorithm } from './keys'; interface HashOptionsBase extends Stream.TransformOptions { outputLength?: number | undefined; } type HashOptions = null | undefined | HashOptionsBase; global.process.nextTick = setImmediate; const createInternalHash = NativeQuickCrypto.createHash; export function createHash(algorithm: string, options?: HashOptions) { return new Hash(algorithm, options); } class Hash extends Stream.Transform { private internalHash: InternalHash; constructor(other: Hash, options?: HashOptions); constructor(algorithm: string, options?: HashOptions); constructor(arg: string | Hash, options?: HashOptions) { super(options ?? undefined); if (arg instanceof Hash) { this.internalHash = arg.internalHash.copy(options?.outputLength); } else { this.internalHash = createInternalHash(arg, options?.outputLength); } } copy(options?: HashOptionsBase): Hash { const copy = new Hash(this, options); return copy; } /** * Updates the hash content with the given `data`, the encoding of which * is given in `inputEncoding`. * If `encoding` is not provided, and the `data` is a string, an * encoding of `'utf8'` is enforced. If `data` is a `Buffer`, `TypedArray`, or`DataView`, then `inputEncoding` is ignored. * * This can be called many times with new data as it is streamed. * @since v0.1.92 * @param inputEncoding The `encoding` of the `data` string. */ update(data: string | ArrayBuffer, inputEncoding?: Encoding): Hash { if (data instanceof ArrayBuffer) { this.internalHash.update(data); return this; } const buffer = Buffer.from(data, inputEncoding); this.internalHash.update(toArrayBuffer(buffer)); return this; } _transform( chunk: string | ArrayBuffer, encoding: Encoding, callback: () => void, ) { this.update(chunk, encoding); callback(); } _flush(callback: () => void) { this.push(this.digest()); callback(); } /** * Calculates the digest of all of the data passed to be hashed (using the `hash.update()` method). * If `encoding` is provided a string will be returned; otherwise * a `Buffer` is returned. * * The `Hash` object can not be used again after `hash.digest()` method has been * called. Multiple calls will cause an error to be thrown. * @since v0.1.92 * @param encoding The `encoding` of the return value. */ digest(): Buffer; digest(encoding: 'buffer'): Buffer; digest(encoding: Encoding): string; digest(encoding?: Encoding | 'buffer'): string | Buffer { const result: ArrayBuffer = this.internalHash.digest(); if (encoding && encoding !== 'buffer') { return Buffer.from(result).toString(encoding); } return Buffer.from(result); } } // Implementation for WebCrypto subtle.digest() export const asyncDigest = async ( algorithm: SubtleAlgorithm, data: BufferLike, ): Promise<ArrayBuffer> => { validateMaxBufferLength(data, 'data'); switch (algorithm.name) { case 'SHA-1': // Fall through case 'SHA-256': // Fall through case 'SHA-384': // Fall through case 'SHA-512': return internalDigest(algorithm, data); } throw lazyDOMException( `Unrecognized algorithm name: ${algorithm.name}`, 'NotSupportedError', ); }; const internalDigest = ( algorithm: SubtleAlgorithm, data: BufferLike, ): ArrayBuffer => { const normalizedHashName = normalizeHashName(algorithm.name); const hash = new Hash(normalizedHashName); hash.update(bufferLikeToArrayBuffer(data)); return toArrayBuffer(hash.digest()); };