Skip to content

Phase 1: Crypto Utilities (AES-GCM, HKDF, SHA-256) #172

@kevalyq

Description

@kevalyq

📌 Parent Epic

#143 (Client-Side File Encryption for Zero-Knowledge Architecture)

🎯 Goal

Implement core encryption/decryption utilities using Web Crypto API with AES-GCM-256, including key derivation (HKDF), checksum calculation (SHA-256), and comprehensive TDD test coverage with known test vectors.


📋 Implementation Tasks

1. Test Vectors Setup

File: src/lib/crypto/testVectors.ts

  • Define known-answer tests (KAT) for AES-GCM-256
  • NIST-validated test vectors
  • Test data for key derivation (HKDF)
  • Test data for SHA-256 checksums

2. Encryption/Decryption Functions (TDD)

File: src/lib/crypto/encryption.test.ts (Write FIRST)

  • Test: Encrypt file with AES-GCM-256
  • Test: Decrypt file correctly (roundtrip)
  • Test: Fail to decrypt with wrong key
  • Test: Detect tampering (auth tag verification)
  • Test: Use known test vectors (NIST validation)
  • Test: Handle edge cases (empty files, large files)

File: src/lib/crypto/encryption.ts (Implement AFTER tests)

  • encryptFile(file: File, key: CryptoKey): Promise<EncryptedFileBlob>
    • Generate random IV (12 bytes)
    • Encrypt with AES-GCM (256-bit)
    • Include auth tag (16 bytes)
  • decryptFile(blob: EncryptedFileBlob, key: CryptoKey): Promise<File>
    • Verify version and algorithm
    • Decrypt with AES-GCM
    • Automatic auth tag verification
    • Throw on tampering/wrong key

3. Key Derivation (TDD)

File: src/lib/crypto/encryption.test.ts (extend)

  • Test: Derive file key from secret key using HKDF
  • Test: Different keys for different filenames
  • Test: Same key for same filename (deterministic)
  • Test: Keys are non-extractable
  • Test: Keys have correct usage flags

File: src/lib/crypto/encryption.ts (extend)

  • deriveFileKey(secretKey: CryptoKey, filename: string): Promise<CryptoKey>
    • Hash filename to create salt (SHA-256)
    • Derive key using HKDF
    • Return non-extractable AES-GCM key

4. Checksum Utilities (TDD)

File: src/lib/crypto/checksum.test.ts (Write FIRST)

  • Test: Calculate SHA-256 checksum of file
  • Test: Detect file modifications
  • Test: Verify checksum correctly
  • Test: Handle empty files
  • Test: Handle large files (>5MB)

File: src/lib/crypto/checksum.ts (Implement AFTER tests)

  • calculateChecksum(file: File | Blob): Promise<string>
    • SHA-256 hash
    • Hex-encoded output
  • verifyChecksum(file: File | Blob, expectedChecksum: string): Promise<boolean>
    • Calculate checksum
    • Compare with expected

5. TypeScript Types

File: src/lib/crypto/types.ts

  • EncryptedFileBlob interface
  • FileMetadata interface
  • Export all types

✅ Acceptance Criteria

  • All test files created BEFORE implementation (TDD mandatory)
  • ≥15 tests for encryption/decryption
  • ≥5 tests for checksum utilities
  • All tests passing (100% green)
  • Known test vectors validated (NIST)
  • Roundtrip encryption/decryption works
  • Tampering detection works (auth tag)
  • Key derivation deterministic
  • Checksum verification works
  • Code coverage ≥80% for new code
  • PHPStan/ESLint passing
  • TypeScript strict mode clean
  • REUSE compliant (SPDX headers)
  • No security warnings (CodeQL)

🧪 Testing Strategy

// Example test structure
describe('File Encryption', () => {
  it('should encrypt file with AES-GCM-256', async () => { ... });
  it('should decrypt file correctly (roundtrip)', async () => { ... });
  it('should fail to decrypt with wrong key', async () => { ... });
  it('should detect tampering (auth tag verification)', async () => { ... });
  it('should use known test vectors (NIST validation)', async () => { ... });
});

describe('Key Derivation', () => {
  it('should derive file key from secret key using HKDF', async () => { ... });
  it('should derive different keys for different filenames', async () => { ... });
  it('should derive same key for same filename (deterministic)', async () => { ... });
});

describe('File Checksum', () => {
  it('should calculate SHA-256 checksum of file', async () => { ... });
  it('should detect file modifications', async () => { ... });
  it('should verify checksum correctly', async () => { ... });
});

🔗 Dependencies


📝 Technical Notes

Web Crypto API Usage

// Generate key for testing
const key = await crypto.subtle.generateKey(
  { name: 'AES-GCM', length: 256 },
  false, // Non-extractable
  ['encrypt', 'decrypt']
);

// Encrypt
const iv = crypto.getRandomValues(new Uint8Array(12));
const encrypted = await crypto.subtle.encrypt(
  { name: 'AES-GCM', iv, tagLength: 128 },
  key,
  data
);

// Decrypt (auth tag verified automatically)
const decrypted = await crypto.subtle.decrypt(
  { name: 'AES-GCM', iv, tagLength: 128 },
  key,
  encrypted
);

HKDF Key Derivation

// Hash filename for salt
const salt = await crypto.subtle.digest(
  'SHA-256',
  new TextEncoder().encode(filename)
);

// Derive key
const derivedBits = await crypto.subtle.deriveBits(
  {
    name: 'HKDF',
    hash: 'SHA-256',
    salt,
    info: new TextEncoder().encode('file-encryption')
  },
  secretKey,
  256 // 256 bits for AES-256
);

🚀 Branch Strategy

# Create feature branch
git checkout -b feat/crypto-utilities-phase1

# Follow TDD cycle
# 1. Write tests FIRST
# 2. Run tests (they MUST fail initially)
# 3. Implement code (make tests pass)
# 4. Refactor (keep tests green)
# 5. Commit with signed commits

# Example commits
git commit -S -m "test: add encryption test vectors (Phase 1.1)"
git commit -S -m "test: add encryption/decryption tests (Phase 1.2)"
git commit -S -m "feat: implement AES-GCM file encryption (Phase 1.3)"
git commit -S -m "test: add key derivation tests (Phase 1.4)"
git commit -S -m "feat: implement HKDF key derivation (Phase 1.5)"

📏 PR Linking Instructions

When creating the PR for this sub-issue, use this in your PR description:

Fixes #TBD
Part of: #143

⚠️ Important:

  • Do NOT use Fixes #143 - this is not the last sub-issue
  • This is Phase 1 of 5 - foundation layer only
  • PR size: ~400-500 LOC (tests + implementation)

Type: Sub-Issue (Phase 1/5)
Priority: High
Estimated Effort: 2 days
Sprint: Encryption Foundation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status

    ✅ Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions