项目文件夹

文件
wehub-resource-sync 98e40dac97
CLI Smoke Test / smoke-test-linux (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-linux (24) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (20) (push) Has been cancelled
CLI Smoke Test / smoke-test-windows (24) (push) Has been cancelled
Expo App TypeScript typecheck / typecheck (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:40:49 +08:00

74 行
2.9 KiB
TypeScript

/**
* Round-trip tests for aes.web.ts running against the same crypto.subtle
* that web-secure-encryption uses on the web build, so a successful round
* trip here implies wire compatibility with what rn-encryption emits on
* the native side (which uses the AES.GCM SealedBox combined-format:
* 12-byte nonce + ciphertext + 16-byte tag).
*/
import { describe, it, expect } from 'vitest';
import {
encryptAESGCMString,
decryptAESGCMString,
encryptAESGCM,
decryptAESGCM,
} from './aes.web';
import { encodeBase64 } from './base64';
function randomKeyB64(): string {
const bytes = new Uint8Array(32);
crypto.getRandomValues(bytes);
return encodeBase64(bytes);
}
describe('aes.web', () => {
it('round-trips a string', async () => {
const key = randomKeyB64();
const plain = JSON.stringify({ msg: 'Hello, World!', n: 42 });
const encrypted = await encryptAESGCMString(plain, key);
expect(typeof encrypted).toBe('string');
const decrypted = await decryptAESGCMString(encrypted, key);
expect(decrypted).toBe(plain);
});
it('produces a fresh IV per call (no two ciphertexts equal)', async () => {
const key = randomKeyB64();
const a = await encryptAESGCMString('same', key);
const b = await encryptAESGCMString('same', key);
expect(a).not.toBe(b);
});
it('rejects ciphertext encrypted under a different key', async () => {
const k1 = randomKeyB64();
const k2 = randomKeyB64();
const encrypted = await encryptAESGCMString('secret', k1);
const result = await decryptAESGCMString(encrypted, k2);
expect(result).toBeNull();
});
it('rejects truncated ciphertext gracefully', async () => {
const key = randomKeyB64();
const encrypted = await encryptAESGCMString('hello', key);
const result = await decryptAESGCMString(encrypted.slice(0, 4), key);
expect(result).toBeNull();
});
it('round-trips a Uint8Array via the bytes API', async () => {
const key = randomKeyB64();
const data = new TextEncoder().encode('Hello, World!');
const encrypted = await encryptAESGCM(data, key);
expect(encrypted).toBeInstanceOf(Uint8Array);
const decrypted = await decryptAESGCM(encrypted, key);
expect(decrypted).toBeInstanceOf(Uint8Array);
expect(new TextDecoder().decode(decrypted!)).toBe('Hello, World!');
});
it('produces wire format: 12-byte IV prefix + ciphertext + 16-byte tag', async () => {
const key = randomKeyB64();
const encrypted = await encryptAESGCMString('a', key);
// base64 payload = IV(12) + ciphertext("a" → 1 byte) + GCM tag(16) = 29 bytes
// base64 encoded length for 29 bytes = ceil(29/3)*4 = 40 chars (with padding)
const decoded = Uint8Array.from(atob(encrypted), (c) => c.charCodeAt(0));
expect(decoded.length).toBe(12 + 1 + 16);
});
});