136 lines
4.0 KiB
TypeScript
136 lines
4.0 KiB
TypeScript
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<string, any> = {
|
|
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);
|
|
}
|