Skip to main content

Data Encryption Policy

Data Encryption Policy

Project / Organization: ALAI Holding AS — Drop Payment App Policy Number: POL-SEC-ENC-001 Version: 1.0 Date: 2026-02-23 Author: ALAI Security Team Status: Draft Reviewers: CISO, CTO, DPO Next Review: 2027-02-23 (annual) or after Phase 2 infrastructure migration Classification: Confidential

Document History

Version Date Author Changes
0.1 2026-02-12 Security Agent (ALAI) Initial encryption standards from security audit
1.0 2026-02-23 Security Architect (ALAI) Policy document

Sources: security/drop-security-rapport.md, security/security-hardening-implementation.md, legal/ikt-sikkerhetspolicy.md §6, docs/security/SECURITY-ARCHITECTURE.md §4


1. Purpose & Scope

Purpose: This policy defines the minimum encryption standards for data at rest, in transit, and at the application level across all systems operated by ALAI Holding AS for the Drop payment application. Drop processes sensitive financial and personal data regulated by GDPR, PSD2, AML/hvitvaskingsloven, and DORA — all requiring strong encryption protections.

Scope: This policy applies to:

  • All Drop systems, APIs, and infrastructure (production, staging, development)
  • All employees, contractors, and third parties with access to Drop systems
  • All data classified as Internal, Confidential, or Restricted (see compliance-framework.md §6)
  • All cloud environments (AWS — EEA regions) and developer workstations
  • All third-party integrations: BankID, Sumsub, Neonomics, Swan, Sentry

Exceptions: Public-facing static content (HTML, CSS, JS bundles, public images) does not require encryption at rest beyond TLS in transit.

Regulatory basis: GDPR Art. 32, DORA Art. 9(4)(d), IKT-forskriften § 5, PSD2 Art. 95.


2. Encryption Standards & Approved Algorithms

2.1 Approved Algorithms

Symmetric Encryption

Use Case Algorithm Key Size Mode Notes
Data at rest — general AES 256-bit GCM Authenticated encryption — required for PII
Data at rest — legacy/interop AES 256-bit CBC Only with HMAC-SHA-256 for integrity
Fødselsnummer (national ID) AES 256-bit GCM HSM-backed key — separate key from general PII
Backup encryption AES 256-bit GCM Offline key copy in safe
Log encryption AES 256-bit GCM Key rotation every 90 days

Asymmetric Encryption

Use Case Algorithm Key Size Notes
Key exchange / TLS ECDHE P-256 Minimum for all external TLS
Digital signatures Ed25519 256-bit Preferred for JWT signing (future)
JWT signing (current) HMAC-SHA-256 (HS256) 256-bit jose library — symmetric, see auth.ts
Code signing Ed25519 256-bit All release artifacts
TLS certificates ECDSA P-256 256-bit External — managed by cert-manager/Let's Encrypt

Hashing & Password Storage

Use Case Algorithm Parameters Notes
Password hashing bcrypt cost factor 12 bcryptjs ^3.0.3 — implemented
Password hashing (future) Argon2id m=65536, t=3, p=4 Upgrade path for Phase 2
Session token storage SHA-256 Token hash stored in sessions.token_hash, not plaintext
HMAC (webhook signatures) HMAC-SHA-256 For Sumsub webhooks, future API tokens
File integrity SHA-256 For artifact verification

Current implementation: src/drop-app/src/lib/auth.ts (JWT/sessions), src/drop-app/src/lib/utils-server.ts (bcrypt)

2.2 Prohibited Algorithms — NEVER USE

Algorithm Reason Status
SHA-256 for passwords No salt, no key stretching — crackable in seconds with GPU Removed (fix C4, 2026-02-13)
MD5 Collision attacks since 2004 Prohibited
SHA-1 Collision attacks since 2017 Prohibited
DES / 3DES Key size insufficient Prohibited
RC4 Statistical biases Prohibited
ECB mode (any cipher) Leaks data patterns Prohibited
RSA < 2048-bit Insufficient key strength Prohibited
AES-128 for Restricted (L4) data Insufficient for Restricted classification Prohibited

Critical fix applied (2026-02-13): SHA-256 legacy password support completely removed from verifyPassword(). All accounts now require bcrypt.


3. Encryption at Rest

3.1 Database Encryption

MVP (Current — SQLite)

Database Method Key Management Classification
SQLite (drop.db) OS-level filesystem encryption (developer machine) Platform key Development only

Known limitation: SQLite stored in app working directory (process.cwd()/drop.db). Risk of accidental exposure. Mitigation: add to .gitignore, restrict permissions.

Phase 2 Target (PostgreSQL on AWS RDS)

Database Method Key Management Classification
PostgreSQL primary TDE via AWS RDS (AES-256) AWS KMS All classifications
PostgreSQL — fødselsnummer field AES-256-GCM (application layer) Separate AWS KMS key Restricted (L4)
PostgreSQL — PII fields AES-256-GCM (application layer) AWS KMS Confidential (L3)
Backups (RDS automated) AES-256 (AWS RDS snapshot encryption) AWS KMS All classifications

Field-level encryption pattern (planned Phase 2):

// Envelope encryption for fødselsnummer and high-sensitivity PII
async function encryptField(plaintext: string): Promise<string> {
    const dek = crypto.randomBytes(32);
    const encryptedDek = await kmsClient.encrypt({ KeyId: KEK_ARN, Plaintext: dek });
    const iv = crypto.randomBytes(12);
    const cipher = crypto.createCipheriv('aes-256-gcm', dek, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
    const tag = cipher.getAuthTag();
    return Buffer.concat([encryptedDek, iv, tag, ciphertext]).toString('base64');
}

3.2 Card Data

Policy: Drop NEVER stores full card PANs, CVVs, or expiry dates.

  • Only last_four (display) and token_ref (future partner token) stored
  • Card feature gated behind feature flags (all default false in production)
  • Future card issuance will use PCI-compliant partner (Marqeta, Lithic, or similar)

Source: Fix C1 (2026-02-13) — src/drop-app/src/lib/db.ts cards schema

3.3 Backup Encryption

Phase 2 backup strategy:
  PostgreSQL WAL → Continuous replication (EEA)
  Daily full snapshot → AWS RDS encrypted snapshot (AES-256, KMS)
  Backup key → Separate AWS KMS key (different region from primary)
  Backup key escrow → Offline copy in secure location (physical)
  Retention: 30 days automated + quarterly offline backup

Source: legal/ikt-sikkerhetspolicy.md §12


4. Encryption in Transit

4.1 TLS Configuration Standards

Minimum TLS version:

  • External-facing services: TLS 1.3 (TLS 1.2 only for legacy integration with explicit exception)
  • Internal service-to-service: TLS 1.3 (Phase 3 mTLS when microservices introduced)
  • Third-party API calls: TLS 1.3 (BankID, Sumsub, Neonomics, Swan all require TLS 1.3)

Approved cipher suites (TLS 1.3):

TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256  (minimum — prefer 256)

Prohibited TLS configurations:

  • TLS 1.0 and TLS 1.1 — prohibited
  • SSL 2.0 / SSL 3.0 — prohibited
  • RC4 cipher suites — prohibited
  • NULL cipher suites — prohibited

Source: legal/ikt-sikkerhetspolicy.md §6.1

4.2 HTTP Strict Transport Security (HSTS)

Implemented (fix M2, 2026-02-13):

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

Source: src/drop-app/next.config.ts

4.3 Certificate Management

Certificate Type Validity Authority Rotation
External TLS (*.getdrop.no) 90 days Let's Encrypt (planned) Automated (cert-manager)
Internal service TLS (Phase 3) 30 days Internal CA Automated
Code signing 1 year TBD Manual

Certificate monitoring: Alert 30 days before expiry. Channel: #security-alerts (Slack).

4.4 Third-Party API Communications

Integration Protocol Auth Notes
BankID (OIDC) HTTPS / TLS 1.3 OIDC + client credentials Phase 2
Sumsub HTTPS / TLS 1.3 API key + HMAC-SHA-256 webhook signature Phase 2
Neonomics (PSD2) HTTPS / TLS 1.3 OAuth 2.0 Phase 2
Swan HTTPS / TLS 1.3 OAuth 2.0 Phase 2
Sentry HTTPS / TLS 1.3 DSN token Current

5. Application-Level Encryption

5.1 JWT Token Security

Current implementation (src/drop-app/src/lib/auth.ts):

  • Algorithm: HS256 (HMAC-SHA-256) — symmetric
  • Library: jose ^6.1.3
  • Secret: JWT_SECRET env var (fatal error if missing in production)
  • setIssuedAt(): Yes — prevents token reuse
  • setProtectedHeader(): Yes — explicit algorithm
  • Expiry: 24 hours
  • Storage: httpOnly cookie (never in localStorage)

Phase 2 upgrade path: RS256 (RSA-4096) or EdDSA (Ed25519) for asymmetric JWT signing, enabling token verification by multiple services without sharing the secret.

5.2 Field-Level Encryption — Sensitive Fields

Current MVP:

  • Passwords: bcrypt(cost=12) — implemented
  • Session tokens: SHA-256 hash stored — implemented
  • Card data: Only last_four + token_ref — implemented (fix C1)
  • Bank account numbers: Masked to last 4 digits in API responses — implemented

Phase 2 — Required for compliance:

Field Table Classification Encryption Key
fodselsnummer (national ID) users Restricted (L4) AES-256-GCM FODSELSNUMMER_KEY (separate HSM key)
KYC document hashes kyc_records Restricted (L4) AES-256-GCM KYC_KEY
AML investigation notes aml_cases Restricted (L4) AES-256-GCM AML_KEY

5.3 API Response Data Masking

Currently implemented:

  • Card numbers: Masked as **** **** **** XXXX in all API responses
  • CVV: Always *** in API responses
  • Bank account numbers: Only last 4 digits visible
  • Session tokens: Never returned in API responses (httpOnly cookie only)

Source: src/drop-app/src/app/api/cards/[id]/route.ts:25-35, src/drop-app/src/lib/utils-server.ts:23-26


6. Key Management

Full policy: key-management-policy.md

Summary

Key Type KMS Rotation Owner
JWT signing key (JWT_SECRET) Environment variable (MVP) → AWS Secrets Manager (Phase 2) Quarterly Security team
Database KEK (fødselsnummer) AWS KMS (Phase 2) — HSM-backed Annual Security team
Database DEKs (PII fields) AWS KMS (Phase 2) Quarterly Platform
TLS certificates cert-manager + Let's Encrypt (Phase 2) 90 days Platform
Sumsub webhook HMAC key AWS Secrets Manager (Phase 2) On rotation Engineering
Backup encryption key AWS KMS (separate region) Annual Security team

Current MVP key storage: JWT_SECRET in environment variable. Loaded at startup, fatal error if missing in production. Dev fallback uses process.cwd() hash.


7. Cryptographic Inventory

System Algorithm Key Size Mode Key Location Status
Password hashing bcrypt cost=12 N/A — one-way Implemented
JWT signing HS256 (HMAC-SHA-256) 256-bit JWT_SECRET env var Implemented
Session token storage SHA-256 256-bit N/A — hash only Implemented
External TLS ECDSA P-256 / TLS 1.3 256-bit GCM Hosting provider Planned Phase 2
PostgreSQL TDE AES-256 256-bit XTS AWS KMS Planned Phase 2
Fødselsnummer field encryption AES-256-GCM 256-bit GCM AWS KMS (HSM-backed) Planned Phase 2
Backup encryption AES-256-GCM 256-bit GCM AWS KMS (separate region) Planned Phase 2
Sumsub webhook HMAC-SHA-256 256-bit AWS Secrets Manager Planned Phase 2

8. Algorithm Deprecation Schedule

Algorithm Current Usage Deprecation Date Migration Target Status
SHA-256 for passwords REMOVED (fix C4) 2026-02-13 bcrypt cost 12 Completed
HS256 JWT (symmetric) Current auth 2026 H2 RS256 or EdDSA Planned Phase 2
bcrypt cost 12 Current MVP 2027 (review) Argon2id Long-term
SQLite (no TDE) Current MVP Phase 2 migration PostgreSQL + AWS KMS Planned

9. Exception Process

Exceptions are not permitted for: Restricted (L4) data (fødselsnummer, AML records) — no exceptions.

Exception request process:

  1. Submit exception request to: CISO ([email protected])
  2. Required information: System, data classification, algorithm excepted, business justification, risk assessment, compensating controls, duration (max 12 months)
  3. Approval required from: CISO
  4. Exceptions logged in: legal/internkontroll.md
  5. Review: Quarterly

Active exceptions:

System Exception Expiry Compensating Controls
Drop MVP database SQLite without TDE (not PostgreSQL) Phase 2 migration (est. 2026 Q2) File permissions restricted, not committed to git, drop.db in .gitignore
JWT signing HS256 (symmetric) instead of RS256 Phase 2 migration (est. 2026 Q2) JWT_SECRET required env var, session revocation table active, 24h expiry

Approval

Role Name Date Signature
Author ALAI Security Team 2026-02-23
CISO Alem Bašić
CTO Alem Bašić
DPO (GDPR relevance) TBD — appointment required
Management Alem Bašić