import * as ExpoCrypto from 'expo-crypto'; import { Buffer } from 'buffer'; import { pbkdf2 as noblePbkdf2 } from '@noble/hashes/pbkdf2'; import { sha1 } from '@noble/hashes/sha1'; import { sha256 } from '@noble/hashes/sha256'; import { sha512 } from '@noble/hashes/sha512'; import { gcm } from '@noble/ciphers/aes'; /** * Node.js Crypto Polyfill for React Native */ export function randomBytes(size: number): Buffer { const bytes = new Uint8Array(size); ExpoCrypto.getRandomValues(bytes); return Buffer.from(bytes); } const hashMap: Record = { sha1, sha256, sha512, }; export function pbkdf2( password: string | Buffer, salt: string | Buffer, iterations: number, keylen: number, digest: string, callback: (err: Error | null, derivedKey: Buffer) => void ): void { try { const passwordBytes = typeof password === 'string' ? Buffer.from(password) : password; const saltBytes = typeof salt === 'string' ? Buffer.from(salt) : salt; const hasher = hashMap[digest.toLowerCase()]; if (!hasher) { throw new Error(`Unsupported digest: ${digest}`); } const result = noblePbkdf2(hasher, passwordBytes, saltBytes, { c: iterations, dkLen: keylen, }); callback(null, Buffer.from(result)); } catch (err) { callback(err as Error, Buffer.alloc(0)); } } // AES-GCM Implementation class Cipher { private key: Uint8Array; private iv: Uint8Array; private authTag: Buffer | null = null; private aesGcm: any; private buffer: Buffer = Buffer.alloc(0); constructor(key: Buffer, iv: Buffer) { this.key = new Uint8Array(key); this.iv = new Uint8Array(iv); // @noble/ciphers/aes gcm takes (key, nonce) this.aesGcm = gcm(this.key, this.iv); } update(data: string | Buffer, inputEncoding?: string): Buffer { const input = typeof data === 'string' ? Buffer.from(data, inputEncoding as any) : data; this.buffer = Buffer.concat([this.buffer, input]); return Buffer.alloc(0); } final(): Buffer { const result = this.aesGcm.encrypt(this.buffer); // @noble/ciphers returns ciphertext + tag (16 bytes) const tag = result.slice(-16); const ciphertext = result.slice(0, -16); this.authTag = Buffer.from(tag); return Buffer.from(ciphertext); } getAuthTag(): Buffer { if (!this.authTag) throw new Error('Ciphers: TAG not available before final()'); return this.authTag; } } class Decipher { private key: Uint8Array; private iv: Uint8Array; private tag: Uint8Array | null = null; private aesGcm: any; private buffer: Buffer = Buffer.alloc(0); constructor(key: Buffer, iv: Buffer) { this.key = new Uint8Array(key); this.iv = new Uint8Array(iv); this.aesGcm = gcm(this.key, this.iv); } setAuthTag(tag: Buffer): void { this.tag = new Uint8Array(tag); } update(data: Buffer): Buffer { this.buffer = Buffer.concat([this.buffer, data]); return Buffer.alloc(0); } final(): Buffer { if (!this.tag) throw new Error('Decipher: Auth tag not set'); // @noble/ciphers expects ciphertext then tag const full = new Uint8Array(this.buffer.length + this.tag.length); full.set(this.buffer); full.set(this.tag, this.buffer.length); const decrypted = this.aesGcm.decrypt(full); return Buffer.from(decrypted); } } export function createCipheriv(algorithm: string, key: Buffer, iv: Buffer): Cipher { if (algorithm !== 'aes-256-gcm') { throw new Error(`Polyfill only supports aes-256-gcm, got ${algorithm}`); } return new Cipher(key, iv); } export function createDecipheriv(algorithm: string, key: Buffer, iv: Buffer): Decipher { if (algorithm !== 'aes-256-gcm') { throw new Error(`Polyfill only supports aes-256-gcm, got ${algorithm}`); } return new Decipher(key, iv); }