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: Security Architect Status: Draft Reviewers: CISO, CTO, DPO Next Review: 2027-02-23 Classification: Confidential

Document History

Version Date Author Changes
0.1 2026-02-23 Security Architect Initial draft — Drop encryption standards

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 app.

Regulatory basis:

  • GDPR Art. 32 — appropriate technical measures
  • Personopplysningsloven (LOV-2018-06-15-38) § 28
  • IKT-forskriften (FOR-2003-05-21-630) §§ 5-6
  • DORA (EU) 2022/2554 Art. 9(4)(d)
  • Hvitvaskingsloven (LOV-2018-06-01-23) § 30 — AES-256 for KYC data at rest

Scope: This policy applies to:

  • All systems, applications, and infrastructure operated by ALAI Holding AS for Drop
  • All employees, contractors, and third parties with access to Drop systems
  • All data classified as Internal, Confidential, or Restricted (see compliance-framework.md §7)
  • All cloud environments (AWS App Runner, S3) and developer workstations

Exceptions: Public-facing static content (HTML, CSS, JS, public exchange rate data) does not require encryption at rest beyond transport layer security.


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 — preferred; provides confidentiality + integrity
Fødselsnummer (national ID) field encryption AES 256-bit GCM Separate key from database master; application layer
KYC document encryption AES 256-bit GCM Stored in S3 with SSE-KMS
Database backups AES 256-bit GCM Separate backup KMS key in separate AWS region
Log encryption AES 256-bit GCM Rotation every 90 days

Asymmetric Encryption

Use Case Algorithm Key Size Notes
TLS key exchange ECDHE P-256 Cloudflare + AWS App Runner
BankID JWT verification RSA 2048-bit (BankID certificate) BankID Norway certificate authority
Code signing Ed25519 256-bit CI/CD artifact signing
AWS KMS key wrapping RSA-OAEP 4096-bit AWS-managed

Hashing & Password Storage

Use Case Algorithm Parameters Notes
Password hashing bcrypt cost factor 12 bcryptjs ^3.0.3 — pure JS; SHA-256 legacy support REMOVED (fix C4)
JWT token hashing (session lookup) SHA-256 Session table stores SHA-256(JWT) for revocation check
HMAC (webhook signatures) HMAC-SHA-256 For API integrations

Source: src/drop-app/src/lib/utils-server.ts:8-16 (bcrypt); src/drop-app/src/lib/auth.ts (JWT)

2.2 Prohibited Algorithms (NEVER USE)

Algorithm Reason Status
MD5 Collision attacks (2004+) Prohibited
SHA-1 Collision attacks (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
SHA-256 for password hashing Not a password hash — no salt/stretching REMOVED (security fix C4, 2026-02-13)

Critical fix (C4): SHA-256 legacy password support was completely removed in the 2026-02-13 hardening. verifyPassword() now only accepts bcrypt hashes. Source: src/drop-app/src/lib/utils-server.ts:14-19.


3. Encryption at Rest

3.1 Database Encryption

Database Method Key Management Classification Coverage
SQLite (current MVP) OS-level full-disk encryption Platform (AWS) All classifications
PostgreSQL — Phase 2 (primary) AES-256 TDE via AWS RDS AWS KMS — drop-db-master-key All classifications
PostgreSQL — fødselsnummer field AES-256-GCM application layer AWS KMS — drop-national-id-key (SEPARATE) Restricted (L4)
PostgreSQL — KYC documents AES-256-GCM application layer AWS KMS — drop-kyc-key Restricted (L4)

Implementation pattern — fødselsnummer field encryption:

// Envelope encryption for fødselsnummer
// Key: drop-national-id-key (AWS KMS — separate from db master)
// Stored: Only AES-256-GCM ciphertext — never plaintext in DB
// Access: Compliance function only, via role-based KMS key policy

async function encryptNationalId(fodselsnummer: string): Promise<string> {
    const iv = crypto.randomBytes(12);  // 96-bit random IV
    const dek = await kmsClient.generateDataKey({ KeyId: NATIONAL_ID_KEY_ARN, KeySpec: 'AES_256' });
    const cipher = crypto.createCipheriv('aes-256-gcm', dek.Plaintext, iv);
    const ciphertext = Buffer.concat([cipher.update(fodselsnummer, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    // Return: base64(encryptedDEK || iv || tag || ciphertext)
    return Buffer.concat([dek.CiphertextBlob, iv, tag, ciphertext]).toString('base64');
}

Card data note (critical fix C1): Cards feature is gated behind feature flags (all default false). No full PAN or CVV is ever stored. Only last_four and token_ref are stored. Card issuance will use a PCI-compliant partner (Marqeta/Lithic). Source: src/drop-app/src/lib/feature-flags.ts.

3.2 File / Object Storage Encryption

Storage Method Key Management Notes
AWS S3 — KYC documents SSE-KMS (AES-256) AWS KMS — drop-kyc-key Server-side encryption, per-object
AWS S3 — backups SSE-KMS (AES-256) AWS KMS — drop-backup-key (separate region) Separate key from primary
Developer workstations OS-level full disk encryption Platform key (FileVault on macOS) Required for all development machines

3.3 Backup Encryption

All database backups encrypted before transfer off primary system.
Key: drop-backup-key (AWS KMS — eu-west-1 region, separate from primary eu-north-1)
Rotation: Annual
Retention: 30 days rolling

Backup verification: Monthly restore test — see beredskapsplan.md

4. Encryption in Transit

4.1 TLS Configuration Standards

External-facing services (Cloudflare + AWS App Runner):

  • Minimum TLS version: TLS 1.3 (enforced at Cloudflare edge)
  • TLS 1.2: Not supported (Cloudflare configuration)
  • HTTP → HTTPS: Automatic redirect (HTTP 301)

Approved cipher suites (TLS 1.3):

TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256  (minimum — prefer 256)

HSTS configuration (source: next.config.ts):

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

Prohibited TLS configurations:

  • TLS 1.0 and TLS 1.1 — prohibited (Cloudflare blocks)
  • SSL 2.0 / SSL 3.0 — prohibited
  • RC4 cipher suites — prohibited
  • NULL cipher suites — prohibited

4.2 Certificate Management

Certificate Type Validity Authority Rotation Monitoring
External TLS (getdrop.no) 90 days Let's Encrypt (via Cloudflare) Automated Alert 14 days before expiry
BankID integration certificate Per BankID schedule BankID Norge AS CA Manual — BankID renewal process Calendar reminder
AWS internal certificates AWS-managed AWS Certificate Manager AWS-managed AWS CloudWatch alerts

4.3 API Communication

All Drop API traffic:
  Client → Cloudflare: TLS 1.3 (edge termination)
  Cloudflare → AWS App Runner: TLS 1.3 (re-encrypted)

Open Banking API calls (AISP/PISP — Phase 2):
  Drop API → Bank API: TLS 1.3 + OAuth 2.0 / eIDAS certificate

httpOnly cookie security:
  secure: true (HTTPS-only transport)
  sameSite: 'strict' (CSRF protection)
  Source: src/drop-app/src/lib/auth.ts:48-54

5. Application-Level Encryption

5.1 Field-Level Encryption

When required: All fields classified as Restricted (L4) must be encrypted at the application layer, in addition to database-level encryption.

Fields requiring field-level encryption:

Field Table Classification Key
national_id_encrypted users Restricted (L4) drop-national-id-key (AWS KMS)
kyc_document_ref users / kyc_records Restricted (L4) drop-kyc-key (AWS KMS)
bankid_sub users Confidential (L3) Database TDE
token_hash sessions Confidential (L3) Database TDE (SHA-256 hash — not raw token)

5.2 JWT Token Security

Algorithm: HS256 (HMAC-SHA-256) Library: jose ^6.1.3 Configuration:

// Source: src/drop-app/src/lib/auth.ts
const token = await new SignJWT({ userId, role })
    .setProtectedHeader({ alg: 'HS256' })
    .setIssuedAt()
    .setExpirationTime('24h')
    .sign(new TextEncoder().encode(process.env.JWT_SECRET));

Production requirement: JWT_SECRET env var is required — throws fatal error if missing (dev fallback uses process.cwd() hash).

Phase 2 upgrade path: Migrate to RS256 (RSA) or EdDSA (Ed25519) for better key rotation support and multi-service verification.

5.3 Tokenization

Payment data: Drop uses a pass-through PSD2 model — no card numbers stored. Cards feature gated behind feature flags (all default false). When cards feature is activated in future: PCI-compliant tokenization via partner (Marqeta/Lithic) — Drop stores only last_four + token_ref.


6. Key Management

Full policy: key-management-policy.md

Summary

Key Type KMS Rotation Owner
Fødselsnummer encryption key AWS KMS (drop-national-id-key) Annual Security team
Database master key AWS KMS (drop-db-master-key) Annual Security team
KYC document key AWS KMS (drop-kyc-key) Annual Security team
Backup encryption key AWS KMS — separate region Annual Security team
JWT signing secret AWS Secrets Manager (JWT_SECRET) Quarterly Security team
BankID certificates BankID Norge AS CA Per BankID schedule BankID Norge AS
TLS certificates (external) Cloudflare / Let's Encrypt 90 days (automated) Platform

7. Cryptographic Inventory

System Algorithm Key Size Mode Key Location Notes
SQLite (current) OS disk encryption Platform XTS Platform MVP only
PostgreSQL TDE (Phase 2) AES-256 256-bit XTS AWS KMS drop-db-master-key Phase 2
Fødselsnummer field encryption AES-256-GCM 256-bit GCM AWS KMS drop-national-id-key Phase 2 — separate key
External TLS ECDSA P-256 256-bit Cloudflare / Let's Encrypt 90-day cert
JWT signing HMAC-SHA-256 256-bit AWS Secrets Manager Rotation: quarterly
Password hashing bcrypt cost=12 N/A bcryptjs library
Session token lookup SHA-256 256-bit N/A Hash stored in sessions table
S3 backup encryption AES-256 SSE-KMS 256-bit AWS KMS drop-backup-key Separate region

8. Algorithm Deprecation Schedule

Algorithm Current Usage Deprecation Date Migration Target Status
SHA-256 for passwords REMOVED (fix C4, 2026-02-13) Removed bcrypt(12) DONE
bcrypt → Argon2id All user passwords 2027-Q1 Argon2id (m=65536,t=3,p=4) Planned
HS256 JWT → RS256/EdDSA JWT signing Phase 2 Ed25519 (EdDSA) Planned
SQLite TDE → PostgreSQL TDE Database Phase 2 PostgreSQL + AWS KMS Planned

9. Exception Process

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

Exception request process:

  1. Submit exception request to: [email protected]
  2. Required information: system, data classification, algorithm being excepted, business justification, risk assessment, compensating controls, proposed exception duration (max 12 months)
  3. Approval required from: CISO
  4. Exceptions logged in: compliance register (internal)
  5. Review: Quarterly — exceptions exceeding 12 months require board-level approval

Active exceptions:

System Exception Expiry Compensating Controls
JWT signing (HS256) Shared-secret JWT instead of asymmetric keys Phase 2 JWT_SECRET in AWS Secrets Manager; short expiry (24h); session revocation table
SQLite (MVP) Filesystem encryption only, no column-level TDE Phase 2 migration Full disk encryption; private network; no public DB access

Approval

Role Name Date Signature
Author Security Architect 2026-02-23
CISO
CTO
DPO (GDPR relevance)
Management