Skip to main content

Data Encryption Policy

Data Encryption Policy

Project / Organization: BilkoALAI Holding ASBalkanDrop AccountingPayment SaaSApp Policy Number: POL-SEC-ENC-001 Version: 1.0 Date: 2026-02-23 Author: ComplianceALAI ArchitectSecurity Team Status: Draft Reviewers: CISO, CTO, DPO, Engineering LeadDPO Next Review: 2026-08-2027-02-23 (annual) or after Phase 2 infrastructure migration Classification: Confidential

Document History

Version Date Author Changes
0.1 2026-02-2312 ComplianceSecurity ArchitectAgent (ALAI) Initial draft — Bilko encryption standards from security audit
1.02026-02-23Security 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 all data at rest, in transit, and at the application level across all systems operated by Bilko.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 systemsDrop operatedsystems, byAPIs, Bilkoand infrastructure (api.bilko.io,production, bilko.io,staging, PostgreSQL, Cloudflare R2)development)
  • All employees, contractors, and third parties with access to BilkoDrop systems
  • All data classified as Internal, Confidential, or Restricted (see compliance-framework.md §6)
  • RailwayAll PostgreSQLcloud environments (EUAWS West), VercelEEA (frontend),regions) Cloudflareand R2developer (fileworkstations
  • storage)
  • 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. 3232, — Appropriate technical measures including encryption
  • ZZPLDORA Art. 509(4)(d), (Serbia)IKT-forskriften § Security5, of personal data processing
  • ZZLPPSD2 Art. 14 (BiH) — Technical data protection measures
  • Zakon o računovodstvu (RS/HR/BA) — Integrity of financial records
95.


2. Encryption Standards & Approved Algorithms

2.1 Approved Algorithms

Symmetric Encryption

Use Case Algorithm Key Size Mode Notes
Data at rest (general)— general AES 256-bit GCM Authenticated encryption — defaultrequired for PII
DatabaseData diskat encryptionrest — legacy/interop AES 256-bit XTSCBC RailwayOnly PostgreSQLwith defaultHMAC-SHA-256 for integrity
FileFødselsnummer storage(national ID) AES 256-bit GCM CloudflareHSM-backed R2key server-side— separate key from general PII
Backup encryption AES 256-bit GCM RailwayOffline automatickey backupcopy in safe
Log encryptionAES256-bitGCMKey rotation every 90 days

Asymmetric Encryption

1.3
Use Case Algorithm Key Size Notes
TLS keyKey exchange / TLS ECDHE P-256 / P-384 CloudflareMinimum +for Railwayall external TLS
Digital signaturesEd25519256-bitPreferred for JWT signing (future)
JWT signing (current) HMAC-SHA-256 (HS256) 256-bit JWT_SECRETjose (32+library chars, CSPRNG)symmetric, see auth.ts
JWTCode refreshsigning HMAC-SHA-256 (HS256)Ed25519 256-bit JWT_REFRESH_SECRETAll (separaterelease key)artifacts
Future:TLS JWT asymmetriccertificates Ed25519ECDSA (EdDSA)P-256 256-bit PlannedExternal Phase 2managed migrationby cert-manager/Let's Encrypt

Hashing & Password Storage

tokens)
Use Case Algorithm Parameters Notes
Password hashing bcrypt cost factor = 12 bcrypt.hash(password,bcryptjs 12)^3.0.3 — implemented
TokenPassword hashing (refreshfuture) Argon2idm=65536, t=3, p=4Upgrade path for Phase 2
Session token storageSHA-256Token hash stored in sessions.token_hash, not plaintext
HMAC (webhook signatures) HMAC-SHA-256 RefreshFor Sumsub webhooks, future API tokens hashed before DB storage
DataFile integrity (general) SHA-256 FileFor checksums,artifact non-security hashingverification

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)USE

completely broken — deterministic
Algorithm Reason Status
SHA-256 for passwordsNo salt, no key stretching — crackable in seconds with GPURemoved (fix C4, 2026-02-13)
MD5 Collision attacks (2004+)since 2004 Prohibited
SHA-1 Collision attacks (2017+)since 2017Prohibited
DES / 3DES Key size insufficient Prohibited
RC4 Statistical biasesProhibited
ECB mode (any cipher) Leaks data patterns Prohibited
RSA < 2048-bit Insufficient key strength Prohibited
AES-128 for Restricted (L4) data Insufficient for financial/taxRestricted ID data
bcrypt < 12 roundsclassification Insufficient work factor
MD5 for password hashingNEVER — use bcryptProhibited

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 CoverageClassification
SQLite (drop.db)OS-level filesystem encryption (developer machine)Platform keyDevelopment 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)

DatabaseMethodKey ManagementClassification
PostgreSQL (Railway EU West)primary AES-256TDE diskvia encryptionAWS RDS (Railway TDE)AES-256) Railway-managedAWS KMS All data classifications
PostgreSQL — taxfødselsnummer IDs (PIB/JMBG/OIB/JIB)field AES-256-GCM application-layer(application field encryptionlayer) EnvironmentSeparate variableAWS (RailwayKMS secrets)key L4Restricted Restricted(L4)
PostgreSQL — IBANPII fields AES-256-GCM application-layer(application field encryptionlayer) EnvironmentAWS variable (Railway secrets)KMS L4Confidential Restricted(L3)
PostgreSQLBackups backups(RDS automated) AES-256 (RailwayAWS automatic)RDS snapshot encryption) Railway-managedAWS KMS All dataclassifications

Field-level encryption forpattern L4(planned RestrictedPhase data:2):

// TaxEnvelope IDsencryption for fødselsnummer and bankhigh-sensitivity account numbers encrypted at application layer
// In addition to Railway disk encryption
import crypto from 'crypto';PII
async function encryptRestrictedField(encryptField(plaintext: string): Promise<string> {
    const keydek = Buffer.from(process.env.FIELD_ENCRYPTION_KEY!,crypto.randomBytes(32);
    'hex'const encryptedDek = await kmsClient.encrypt({ KeyId: KEK_ARN, Plaintext: dek }); // 32 bytes
    const iv = crypto.randomBytes(12); // 96-bit IV for GCM
    const cipher = crypto.createCipheriv('aes-256-gcm', key,dek, iv);
    const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8')plaintext), cipher.final()]);
    const tag = cipher.getAuthTag();
    // Store: base64(iv || tag || ciphertext)
  return Buffer.concat([encryptedDek, iv, tag, ciphertext]).toString('base64');
}
async function decryptRestrictedField(stored: string): Promise<string> {
  const key = Buffer.from(process.env.FIELD_ENCRYPTION_KEY!, 'hex');
  const buf = Buffer.from(stored, 'base64');
  const iv = buf.subarray(0, 12);
  const tag = buf.subarray(12, 28);
  const ciphertext = buf.subarray(28);
  const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
  decipher.setAuthTag(tag);
  return decipher.update(ciphertext) + decipher.final('utf8');
}

3.2 FileCard Storage EncryptionData

Policy:

DropNEVERstoresfullPANs,CVVs,orexpirydates.

partnertoken)gatedbehindfeature
Storage Method Keycard Management Notes
Cloudflare
  • Only R2last_four (receipts,display) invoiceand PDFs)
  • AES-256 server-sidetoken_ref (Cloudflarefuture default) Cloudflare-managed EUstored region
  • Card bucketfeature required
  • 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

    Railway

    Phase provides2 automaticbackup dailystrategy:
      PostgreSQL backups,WAL → Continuous replication (EEA)
      Daily full snapshot → AWS RDS encrypted withsnapshot (AES-256.256, KMS)
      Backup retention:key → Separate AWS KMS key (different region from primary)
      Backup key escrow → Offline copy in secure location (physical)
      Retention: 30 days (Railwayautomated default).+ Customquarterly offline backup
    strategy
    to

    Source: belegal/ikt-sikkerhetspolicy.md implemented in Phase 2 with separate backup encryption key stored in Railway environment secrets.§12


    4. Encryption in Transit

    4.1 TLS Configuration Standards

    Minimum TLS version:

    • External-facing (api.bilko.io, bilko.io):services: TLS 1.3 (enforcedTLS via1.2 Cloudflare)only for legacy integration with explicit exception)
    • RailwayInternal 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)minimum — prefer 256)
    

    HSTSProhibited configuration:

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

    HTTP redirect: All HTTP traffic redirected to HTTPS (301). No HTTP allowed in production.

    Prohibited: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 CookieHTTP Strict Transport Security (HSTS)

    AllImplemented session(fix cookiesM2, set with:2026-02-13):

    res.cookie('refreshToken',Strict-Transport-Security: token,max-age=63072000; {includeSubDomains; httpOnly: true,    // Not accessible to JavaScript
      secure: true,      // HTTPS only
      sameSite: 'strict', // CSRF protection
      maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    });preload
    

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

    4.3 Certificate Management

    Certificate TypeValidityAuthorityRotation
    External TLS (*.getdrop.no)90 daysLet's Encrypt (planned)Automated (cert-manager)
    Internal service TLS (Phase 3)30 daysInternal CAAutomated
    Code signing1 yearTBDManual

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

    4.4 Third-Party API SecurityCommunications

    Headers
    app.use(helmet({
    63072000,truecontentSecurityPolicy:{directives:{["'self'",["'self'","https:"],},},
    IntegrationProtocolAuthNotes
    BankID (Helmet.js)OIDC) HTTPS hsts:/ {TLS maxAge:1.3 OIDC includeSubDomains:+ true,client preload:credentials Phase },2
    Sumsub HTTPS defaultSrc:/ ["'self'"],TLS scriptSrc:1.3 API "'unsafe-inline'"],key styleSrc:+ ["'self'",HMAC-SHA-256 "'unsafe-inline'"],webhook imgSrc:signature Phase "data:",2
    Neonomics }));(PSD2) HTTPS / TLS 1.3OAuth 2.0Phase 2
    SwanHTTPS / TLS 1.3OAuth 2.0Phase 2
    SentryHTTPS / TLS 1.3DSN tokenCurrent

    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 Requirements— 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 all L4 Restricted fields:compliance:

    must
    Field Table ReasonClassificationEncryptionKey
    taxIdfodselsnummer (organizationnational tax ID: PIB/JIB/OIB)organizationsNational business identifier
    taxId (contact tax ID: PIB/JMBG/OIB/JIB)contactsNational person/business identifier
    ibanorganizations, contacts, bankAccountsBank account number
    totpSecretID) users 2FARestricted seed(L4) AES-256-GCM FODSELSNUMMER_KEY not(separate beHSM readablekey)
    KYC document hasheskyc_recordsRestricted (L4)AES-256-GCMKYC_KEY
    AML investigation notesaml_casesRestricted (L4)AES-256-GCMAML_KEY

    5.23 JWTAPI TokenResponse EncryptionData Masking

    JWTsCurrently signed with HMAC-SHA-256 (HS256) using JWT_SECRET:implemented:

    • Card numbers: Masked as JWT_SECRET**** **** **** XXXX: 32+ character random string (CSPRNG), stored in Railwayall secretsAPI responses
    • CVV: Always JWT_REFRESH_SECRET***: Separatein 32+API character key — never same as JWT_SECRETresponses
    • AccessBank tokenaccount payload:numbers: {Only sub:last userId,4 org:digits organizationId, role, iat, exp }visible
    • NOSession PIItokens: Never returned in JWTAPI payloadresponses (httpOnly nocookie email, name, tax IDonly)

    RefreshSource: tokenssrc/drop-app/src/app/api/cards/[id]/route.ts:25-35, stored as HMAC-SHA-256 hash in database — raw token never stored.

    5.3 Password Hashing

    import bcrypt from 'bcrypt';
    
    // Registration
    const hash = await bcrypt.hash(plainPassword, 12);
    
    // Login verification
    const isValid = await bcrypt.compare(plainPassword, storedHash);
    src/drop-app/src/lib/utils-server.ts:23-26

    bcrypt parameters: cost factor 12. Never downgrade below 12.


    6. Key Management Summary

    Full policy: key-management-policy.md

    Summary

    Key Type StorageKMS Rotation Owner
    JWT signing key (JWT_SECRET) RailwayEnvironment environmentvariable secret(MVP) → AWS Secrets Manager (Phase 2) Quarterly Security team
    JWT_REFRESH_SECRETDatabase KEK (fødselsnummer) RailwayAWS environment secretQuarterlySecurity
    FIELD_ENCRYPTION_KEYKMS (taxPhase IDs,2) IBAN) Railway environment secretHSM-backed Annual Security team
    PostgreSQLDatabase diskDEKs encryption(PII fields) Railway-managedAWS KMS (TDE)Phase 2) Railway-managedQuarterly Railway
    Cloudflare R2 encryptionCloudflare-managedCloudflare-managedCloudflarePlatform
    TLS certificates (Cloudflare) Cloudflarecert-manager Certificate+ ManagerLet's Encrypt (Phase 2) 90 days (automatic) CloudflarePlatform
    Sumsub webhook HMAC keyAWS Secrets Manager (Phase 2)On rotationEngineering
    Backup encryption keyAWS KMS (separate region)AnnualSecurity team

    SecretsCurrent neverMVP committedkey to git.storage: .envJWT_SECRET files in .gitignore. All secrets managed via Railway environment variablesvariable. (production)Loaded orat startup, fatal error if missing in production. Dev fallback uses .env.localprocess.cwd() (development, git-ignored).hash.


    7. Cryptographic Inventory

    System Algorithm Key Size Mode Key Location Next RotationStatus
    PostgreSQL disk (Railway)AES-256256-bitXTSRailway-managedRailway-managed
    Tax ID field encryptionAES-256-GCM256-bitGCMRailway env secretAnnual
    IBAN field encryptionAES-256-GCM256-bitGCMRailway env secretAnnual
    Cloudflare R2AES-256256-bitCloudflare-managedCloudflare-managed
    External TLS (Cloudflare)ECDSA P-256256-bitCloudflare90 days (auto)
    JWT signingHMAC-SHA-256256-bitRailway env secretQuarterly
    Refresh token signingHMAC-SHA-256256-bitRailway env secretQuarterly
    Password hashing bcrypt cost=12 N/A — one-wayImplemented
    JWT signingHS256 (HMAC-SHA-256)256-bitJWT_SECRET env varImplemented
    Session token storageSHA-256256-bit N/A — hash onlyImplemented
    External TLSECDSA P-256 / TLS 1.3256-bitGCMHosting providerPlanned Phase 2
    PostgreSQL TDEAES-256256-bitXTSAWS KMSPlanned Phase 2
    Fødselsnummer field encryptionAES-256-GCM256-bitGCMAWS KMS (HSM-backed)Planned Phase 2
    Backup encryptionAES-256-GCM256-bitGCMAWS KMS (separate region)Planned Phase 2
    Sumsub webhookHMAC-SHA-256256-bitAWS Secrets ManagerPlanned Phase 2

    8. FinancialAlgorithm DataDeprecation IntegritySchedule

    Financial

    datarequiresnotjustbutintegrity.enforces:

    1. NUMERIC(19,4)
    2. monetaryneverfloat number.errorsinVATLoggedActionmutationsappend-onlywithold/newvalues.audit.enforcement=backend.Preventsimbalancedentries.
    3. Exchange rate locking — rates stored at transaction date. Historical accuracy preserved.
    4. Algorithm Current confidentialityUsage Deprecation alsoDate Migration BilkoTarget Status
      SHA-256 for allpasswords REMOVED amounts(fix C4) 2026-02-13 bcrypt cost 12Completed
      HS256 JWT (symmetric)Current auth2026 H2RS256 or JavaScriptEdDSA Planned PreventsPhase rounding2
      bcrypt calculations.cost
    5. Immutable12
    6. Current tableMVP 2027 all(review) Argon2id Long-term
      SQLite Enables(no financialTDE) Current
    7. Double-entryMVP
    8. Phase 2 debitmigration PostgreSQL credit+ validatedAWS atKMS Planned

      9. Exception Process

      Exceptions are not permitted for: L4Restricted Restricted(L4) data (taxfødselsnummer, IDs,AML IBAN, TOTP secrets, password hashes)records) — no exceptions.

      Exception request process:

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

      Active exceptions:

      Noneatthistime.

      SystemExceptionExpiryCompensating Controls
      Drop MVP databaseSQLite without TDE (not PostgreSQL)Phase 2 migration (est. 2026 Q2)File permissions restricted, not committed to git, drop.db in .gitignore
      JWT signingHS256 (symmetric) instead of RS256Phase 2 migration (est. 2026 Q2)JWT_SECRET required env var, session revocation table active, 24h expiry

      Approval

      Role Name Date Signature
      Author ComplianceALAI ArchitectSecurity Team 2026-02-23
      CISOAlem Bašić
      CTO Alem Bašić
      DPO (GDPR relevance) TBD — appointment required
      Engineering LeadManagement Alem Bašić