Skip to main content

Data Encryption Policy

Data Encryption Policy

Project / Organization: {{ORG_NAME}}ALAI Holding AS — Drop Payment App Policy Number: POL-SEC-{{NUMBER}}ENC-001 Version: {{VERSION}}1.0 Date: {{DATE}}2026-02-23 Author: {{AUTHOR}}Security Architect Status: Draft | In Review | Approved Reviewers: {{REVIEWERS}}CISO, CTO, DPO Next Review: {{REVIEW_DATE}}2027-02-23 Classification: Confidential

Document History

Version Date Author Changes
0.1 {{DATE}}2026-02-23 {{AUTHOR}}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 {{ORG_NAME}}.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 {{ORG_NAME}}ALAI Holding AS for Drop
  • All employees, contractors, and third parties with access to {{ORG_NAME}}Drop systems
  • All data classified as Internal, Confidential, or Restricted (see compliance-framework.md §5)7)
  • All cloud environments,environments on-premises(AWS systems,App Runner, S3) and developer workstations

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


2. Encryption Standards & Approved Algorithms

2.1 Approved Algorithms

Symmetric Encryption

preferred;providesconfidentiality (nationalID) IVper
Use Case Algorithm Key Size Mode Notes
Data at rest (general) AES 256-bit GCM Authenticated encryption — preferred
Data at rest (legacy/interop)AES256-bitCBCOnly with HMAC-SHA-256 for+ integrity
StreamFødselsnummer encryption ChaCha20-Poly1305 256-bitAlternative to AES on non-AES-NI hardware
Filefield encryption AES 256-bit GCM WithSeparate randomkey 96-from database master; application layer
KYC document encryptionAES256-bit GCM Stored filein S3 with SSE-KMS
Database backupsAES256-bitGCMSeparate backup KMS key in separate AWS region
Log encryptionAES256-bitGCMRotation every 90 days

Asymmetric Encryption

JWT KMSkey
Use Case Algorithm Key Size Notes
KeyTLS key exchange / TLS ECDHE P-256 / P-384 PreferCloudflare P-384+ forAWS Confidential/RestrictedApp dataRunner
DigitalBankID signatures Ed25519256-bitPreferred for new systems
Digital signatures (legacy)verification RSA 4096-2048-bit (BankID certificate) OnlyBankID whereNorway Ed25519certificate not supportedauthority
Code signing Ed25519 256-bit AllCI/CD releaseartifact artifactssigning
CertificateAWS signing RSA 4096-bitFor CA certificates only
Encryption (hybrid)wrapping RSA-OAEP 4096-bit Encrypt DEK with RSA when ECC not availableAWS-managed

Hashing & Password Storage

Use Case Algorithm Parameters Notes
Password hashing Argon2idm=65536, t=3, p=4Preferred for new systems
Password hashing (legacy)bcrypt cost factor 12 Acceptablebcryptjs if^3.0.3 Argon2 notpure availableJS; SHA-256 legacy support REMOVED (fix C4)
DataJWT integritytoken hashing (general)session lookup) SHA-256 ForSession non-security-criticaltable hashingstores SHA-256(JWT) for revocation check
HMAC (messagewebhook auth)signatures) HMAC-SHA-256 For webhook signatures, API tokens
File integritySHA-256For artifact verification
Token derivationHKDF-SHA-256For deriving keys from master keyintegrations

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 Migration Required ByStatus
MD5 Collision attacks (2004+) Already prohibitedProhibited
SHA-1 Collision attacks (2017+) Already prohibitedProhibited
DES / 3DES Key size insufficient Already prohibitedProhibited
RC4 Statistical biases Already prohibitedProhibited
ECB mode (any cipher) Leaks data patterns Already prohibitedProhibited
RSA < 2048-bit Insufficient key strength Already prohibitedProhibited
AES-128SHA-256 (Restrictedfor data)password hashing InsufficientNot fora Restrictedpassword classificationhash — no salt/stretching UpgradeREMOVED by(security {{DATE}}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

PII)
Database Method Key Management Classification Coverage
PostgreSQLSQLite (primary)current MVP) TDEOS-level viafull-disk cloud provider (AES-256)encryption CloudPlatform KMS(AWS) All classifications
PostgreSQL — Phase 2 (field-levelprimary) AES-256 TDE via AWS RDSAWS KMS — drop-db-master-keyAll classifications
PostgreSQL — fødselsnummer field AES-256-GCM atapplication layerAWS KMS — drop-national-id-key (SEPARATE)Restricted (L4)
PostgreSQL — KYC documentsAES-256-GCM application layer ApplicationAWS KMS drop-kyc-key Confidential / Restricted
RedisCloud provider encryption (AES-256)Cloud KMSInternal / Confidential
ElasticsearchAES-256 (X-Pack security)Cloud KMSInternalL4)

Implementation pattern (field-level):— fødselsnummer field encryption:

// Envelope encryption for PIIfødselsnummer
fields// 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 encryptField(plaintext:encryptNationalId(fodselsnummer: string): Promise<string> {
    // 1. Generate random DEK
    const dek = crypto.randomBytes(32);
    // 2. Encrypt DEK with KEK from KMS
    const encryptedDek = await kmsClient.encrypt({ KeyId: KEK_ARN, Plaintext: dek });
    // 3. Encrypt data with DEK
    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,dek.Plaintext, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext)fodselsnummer, 'utf8'), cipher.final()]);
    const tag = cipher.getAuthTag();
    // 4. Return: base64(encryptedDekencryptedDEK || iv || tag || ciphertext)
    return Buffer.concat([encryptedDek,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

development
Storage Method Key Management Notes
AWS S3 / GCSKYC / Azure Blobdocuments SSE-KMS (AES-256) CloudAWS KMS — per-bucket drop-kyc-key Server-side encryption, per-object
BackupAWS filesS3 — backups SSE-KMS (AES-256-GCM256)AWS KMS — drop-backup-key (separate region) Separate backupkey KMSfrom keyKeys in different accountprimary
Developer local storageworkstations OS-level full disk encryption Platform key (FileVault /on BitLocker)macOS) Required for all devices
USB / portable mediaAES-256Password + AES-256Prohibited for Restricted datamachines

3.3 Backup Encryption

All database backups MUST be encrypted before transfer off primary system.
pg_dumpKey: |drop-backup-key gzip | gpg --encrypt --recipient {{BACKUP_KEY_ID}} > backup.sql.gz.gpg

Backup encryption key: Stored in separate(AWS KMS region— eu-west-1 region, separate from primary Backupeu-north-1)
key rotation:Rotation: Annual
Retention: 30 days rolling

Backup keyverification: escrow:Monthly Storedrestore withtest {{ESCROW_LOCATION}}— see beredskapsplan.md

4. Encryption in Transit

4.1 TLS Configuration Standards

MinimumExternal-facing TLSservices version:(Cloudflare + AWS App Runner):

  • External-facing services:Minimum TLS 1.2version: (TLS 1.3 preferred)(enforced at Cloudflare edge)
  • Internal service-to-service: TLS 1.32: Not supported (enforced)Cloudflare configuration)
  • LegacyHTTP system support:HTTPS: DocumentedAutomatic exceptionredirect required(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)

ApprovedHSTS cipher suitesconfiguration (TLSsource: 1.2 — for backwards compatibility only)next.config.ts):

ECDHE-ECDSA-AES256-GCM-SHA384Strict-Transport-Security: ECDHE-RSA-AES256-GCM-SHA384max-age=63072000; ECDHE-ECDSA-CHACHA20-POLY1305includeSubDomains; ECDHE-RSA-CHACHA20-POLY1305preload

Prohibited TLS configurations:

  • TLS 1.0 and TLS 1.1 — prohibited (Cloudflare blocks)
  • SSL 2.0 / SSL 3.0 — prohibited
  • RC4 cipher suites — prohibited
  • Export-grade cipher suites — prohibited
  • NULL cipher suites — prohibited
  • Anonymous DH cipher suites — prohibited

4.2 Certificate Management

Certificate Type Validity Authority Rotation Monitoring
External TLS (*.{{DOMAIN}})getdrop.no) 90 days Let's Encrypt /(via {{CA}}Cloudflare) Automated (cert-manager) Alert 3014 days before expiry
InternalBankID serviceintegration TLScertificate 30Per daysBankID schedule InternalBankID Norge AS CA ({{TOOL}})AutomatedAlert 7 days before expiry
Code signing1 year{{CA}} Manual — BankID renewal process Calendar reminder
RootAWS CAinternal certificates 10 yearsAWS-managed Self-signedAWS Certificate Manager ManualAWS-managed CalendarAWS reminderCloudWatch alerts

Certificate monitoring: {{CERT_MONITORING_TOOL}} — alert channel: #security-alerts

4.3 mTLS for Internal ServiceAPI Communication

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

Open Banking API calls within(AISP/PISP the clusterPhase MUST2):
  useDrop mTLS.API Implementation: {{ISTIOBank API: TLS 1.3 + OAuth 2.0 / LINKERDeIDAS /certificate

cert-managerhttpOnly /cookie custom}}security:
  Certificatesecure: generation: SPIFFE/SVID standard
Rotation: Every {{ROTATION_PERIOD}} — automated via sidecar
Verification: Server verifies client cert against trusted CA
Service identity: SPIFFE URItrue (e.g.,HTTPS-only spiffe://{{TRUST_DOMAIN}}/ns/{{NS}}/sa/{{SERVICE_ACCOUNT}})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
mfa_secretnational_id_encrypted users Restricted (L4) USER_MFA_KEYdrop-national-id-key (AWS KMS)
{{PII_FIELD}}kyc_document_ref {{TABLE}}users / kyc_records ConfidentialRestricted (L4) USER_PII_KEYdrop-kyc-key (AWS KMS)
api_key_secretbankid_sub api_keysusers RestrictedConfidential (L3) API_KEY_KEYDatabase TDE
{{FINANCIAL_FIELD}}token_hash {{TABLE}}sessions RestrictedConfidential (L3) FINANCIAL_KEYDatabase TDE (SHA-256 hash — not raw token)

5.2 EnvelopeJWT EncryptionToken Security

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

Master// KeySource: (KEK)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 requiredstoredthrows infatal KMS,error neverif loaded into application memory ↓missing (KMSdev encryptfallback operation)uses Dataprocess.cwd() Encryptionhash).

Key

Phase 2 upgrade path: Migrate to RS256 (DEK)RSA) or generated per record/session, encrypted by KEK ↓EdDSA (applicationEd25519) encryptfor operationbetter key AES-256-GCM)rotation Datasupport and encryptedmulti-service at rest, decrypted only on read DEK lifetime: - Per-session DEKs: lifetime of session - Per-record DEKs: same as record (stored encrypted alongside data) - Bulk DEKs: rotate every {{ROTATION_PERIOD}} verification.

5.3 Tokenization

WhatPayment 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 tokenized:activated Paymentin cardfuture: data, bank account numbers Implementation: {{PAYMENT_PROVIDER}} handlesPCI-compliant tokenization via partner (Marqeta/Lithic) weDrop store tokensstores only Format-preservinglast_four encryption:+ {{YES/NO}} — {{TOOL}} for {{USE_CASE}}token_ref.


6. Key Management

Full policy: key-management-policy.md

Summary

Key Type KMS Rotation Owner
MasterFødselsnummer encryption keys (KEKs)key {{KMS_TOOL}}AWS KMS (drop-national-id-key) Annual Security team
DataDatabase encryptionmaster keys (DEKs)key Application-managedAWS KMS (drop-db-master-key) Per rotation policyAnnual EngineeringSecurity team
TLSKYC certificatesdocument key cert-managerAWS KMS (drop-kyc-key) 90 days / 30 daysAnnual PlatformSecurity team
APIBackup encryption keyAWS KMS — separate regionAnnualSecurity team
JWT signing keyssecret {{KMS_TOOL}}AWS Secrets Manager (JWT_SECRET) Quarterly Security team
BackupBankID encryption keyscertificates {{KMS_TOOL}}BankID (separateNorge region)AS CA AnnualPer BankID schedule SecurityBankID teamNorge AS
TLS certificates (external)Cloudflare / Let's Encrypt90 days (automated)Platform

7. Cryptographic Inventory

PhasePhase90-day Rotation:
System Algorithm Key Size Mode Key Location Last RotatedNext RotationNotes
SQLite (current)OS disk encryptionPlatformXTSPlatformMVP only
PostgreSQL TDE (Phase 2) AES-256 256-bit XTS CloudAWS KMS drop-db-master-key {{DATE}} {{DATE}}2
User PIIFødselsnummer field encryption AES-256-GCM 256-bit GCM AppAWS KMS drop-national-id-key {{DATE}} {{DATE}}2 — separate key
External TLS ECDSA P-256 256-bit GCM cert-managerCloudflare / Let's Encrypt {{DATE}} {{DATE}}cert
InternalJWT TLS (mTLS)Ed25519256-bitcert-manager{{DATE}}{{DATE}}
Password hashingArgon2idm=65536,t=3N/AN/AN/A
API key hashingsigning HMAC-SHA-256 256-bit AppAWS secretsSecrets Manager {{DATE}} {{DATE}}quarterly
S3Password bucket encryptionhashing AES-bcryptcost=12N/Abcryptjs library
Session token lookupSHA-256 (SSE-KMS) 256-bit CloudN/A Hash stored in sessions table
S3 backup encryptionAES-256 SSE-KMS {{DATE}}256-bit AnnualAWS KMS drop-backup-keySeparate region

8. Algorithm Deprecation Schedule

Algorithm Current Usage Deprecation Date Migration Target Migration OwnerStatus
RSA-2048SHA-256 (oldfor API keys)passwords {{N}}REMOVED keys(fix C4, 2026-02-13) {{DATE}}Removed RSA-4096 or Ed25519bcrypt(12) EngineeringIn progress
AES-128 ({{SYSTEM}}){{USAGE}}{{DATE}}AES-256EngineeringTODODONE
bcrypt cost < 12Argon2id LegacyAll user hashespasswords {{DATE}}2027-Q1 Argon2id (m=65536,t=3,p=4) EngineeringTODOPlanned
SHA-256HS256 (RestrictedJWT data)→ RS256/EdDSA {{USAGE}}JWT signing {{DATE}}Phase 2 SHA-384Ed25519 (EdDSA) EngineeringPlanned
SQLite TDE → PostgreSQL TDE TODODatabasePhase 2PostgreSQL + AWS KMSPlanned

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: {{SECURITY_TEAM_EMAIL}}[email protected]
  2. Required information:
    • System andsystem, data classificationclassification, affected
    • Algorithm/standardalgorithm being exceptedexcepted, from
    • business
    • Businessjustification, justification
    • risk
    • Riskassessment, assessment
    • compensating
    • Compensatingcontrols, controls
    • Proposedproposed exception duration (max 12 months)
  3. Approval required from: CISO
  4. Exceptions logged in: {{EXCEPTION_REGISTER_LOCATION}}compliance register (internal)
  5. Review: Quarterly — exceptions that cannot be remediated withinexceeding 12 months require board-level approval

Active exceptions:

System Exception Expiry Compensating Controls
{{LEGACY_SYSTEM}}JWT signing (HS256) TLSShared-secret 1.2JWT (TLSinstead 1.3of notasymmetric supported)keys {{DATE}}Phase 2 IsolatedJWT_SECRET networkin segmentAWS +Secrets IDSManager; monitoringshort expiry (24h); session revocation table
SQLite (MVP)Filesystem encryption only, no column-level TDEPhase 2 migrationFull 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