Security whitepaper

How Frendy protects your data

Each section pairs a technical description with a plain-language explanation. This is a faithful edition of the Frendy security whitepaper.

Document version 1.3 · Prepared from source-code analysis · Kept current as changes ship

§0Summary (for non-technical readers)

Frendy is a messenger with end-to-end encryption (E2EE). E2EE means a message is encrypted on the sender's device and can only be decrypted on the recipient's device — no intermediary, including the server, holds the key. In practice:

What E2EE does not hide: the fact and timing of communication and file sizes (metadata); and it can't help if the user's own phone is fully compromised by malware or physically broken into at the OS level.

§1Architecture and platforms

ComponentTechnology
Android (primary client)Kotlin, Jetpack Compose
Desktop (Windows)Kotlin / Compose Multiplatform
iOSSwift / SwiftUI
ServerNode.js / Express / MongoDB / Socket.IO
1:1 callsWebRTC (P2P), DTLS-SRTP encryption
Group calls / conferencesLiveKit with E2EE (shared key from ECDH)
In plain words: there are phone/desktop apps and a "postman" server that relays sealed encrypted envelopes between users without being able to open them.

§2Cryptographic primitives

Only standard, time-tested algorithms are used — no home-made cryptography:

PurposeAlgorithmParameters
Message & file encryptionAES-256-GCM256-bit key, 96-bit random IV, 128-bit auth tag
Key exchange between usersECDH on secp256r1 (P-256)dialog key = SHA-256(ECDH shared secret)
PIN protectionPBKDF2-HMAC-SHA25665,536 iterations, 128-bit salt
On-device key storageAndroid Keystore (AES-256-GCM)master key in the hardware module (TEE)
2FATOTP (RFC 6238)secret in DB encrypted with AES-256-GCM
Message forward secrecyDouble Ratchet + X3DH (libsignal)per-device sessions; each message key erased after decryption
Post-quantum session setupPQXDHX25519 + ML-KEM (Kyber-1024)classic and post-quantum exchange combined into one secret
Randomness sourceSecureRandom (CSPRNG)for all keys, IVs, salts
Next-generation forward secrecy (CR-01). On top of the base scheme, Frendy uses the Double Ratchet + X3DH protocol from libsignal (the same library as Signal/WhatsApp): each message gets a one-time key that is erased right after decryption, so a future device compromise doesn't reveal past conversations. Session setup uses PQXDH — a hybrid of classic X25519 and post-quantum ML-KEM (Kyber-1024), resistant to "harvest now, decrypt later" quantum attacks. Model B: each device has its own key set, and the sender encrypts per recipient device (fan-out), including the group forward-secrecy layer (Sender Keys).
In plain words: AES-GCM is the "lock" that seals a message and also verifies it wasn't tampered with. ECDH lets two people agree on a shared secret key over an open channel so an eavesdropper never learns it. PBKDF2 turns a short PIN into a strong key and makes brute-forcing the PIN very slow.

This is not "home-made" cryptography

Our mechanismWhere the same thing is used
AES-256-GCM (messages, files, calls)HTTPS (the browser padlock), Signal, WhatsApp, banking apps; NIST/FIPS standard, approved in the US for classified data
ECDH on P-256 (key exchange)TLS 1.2/1.3 — the entire secure internet; Apple iMessage; NIST standard
DTLS-SRTP (calls)Google Meet, Jitsi, Discord, WhatsApp calls — mandatory WebRTC mechanism
PBKDF2-HMAC-SHA256 (PIN, keys)password managers (1Password, Bitwarden), Wi-Fi security (WPA2), disk encryption
Hardware storage (Keystore / TEE)Google Pay, banking apps, contactless payment

How impossible is a brute-force attack

Our data encryption is AES-256-GCM, and the recovery key is derived from a seed phrase (BIP-39, 128 bits) via PBKDF2-HMAC-SHA256 with 600,000 iterations.

Layer 1 — the cipher itself (AES-256-GCM): breaking it is physically impossible. Brute-forcing AES-256 = 2²⁵⁶ ≈ 1.2 × 10⁷⁷ keys. This isn't "a long time" — it's thermodynamically impossible: by the Landauer limit, merely iterating through 2²⁵⁶ states needs ~3 × 10⁵⁶ J of energy, while the Sun over its entire remaining life (~5 billion years) will emit ~6 × 10⁴³ J — so you'd need about 10¹³× more energy than the Sun will ever produce.

Layer 2 — the real target: the seed phrase. An attacker goes after the 128-bit seed phrase, not the cipher. The seed key is derived via PBKDF2-SHA-256, so classical brute-force speed is capped by SHA-256 throughput. Even Earth's largest SHA-256 hashing power — the entire Bitcoin network (~6 × 10²⁰ SHA-256/s) — aimed at a single account would take about 2.2 × 10¹⁶ years ≈ 1.6 million times the age of the Universe. Probability of a break within 100 years of running such a cluster ≈ 4 × 10⁻¹⁵ — practically zero.

Honest note: the real risk isn't brute force — it's key theft. Encrypted data is realistically opened only if the seed phrase itself is stolen (device malware, phishing, a carelessly recorded phrase). So protection reduces to protecting the seed phrase: hardware key storage, wrapping under a PIN/biometrics, and user care in storing the recovery phrase.

Protection against quantum computers

§3How messages travel (data flow)

Sender Server (Node.js) Recipient ┌──────────┐ ┌──────────────┐ ┌──────────┐ │ plaintext│ │ sees only │ │ plaintext│ │ │ │ TLS 1.2/1.3 │ CIPHERTEXT │ TLS 1.2/1.3 │ ▲ │ │ ▼ │ (+ pinning) │ + routing │ (+ pinning) │ │ │ │ AES-GCM │ ════════════▶ │ metadata │ ════════════▶ │ AES-GCM │ │ (dialog │ ciphertext │ (NO keys!) │ ciphertext │ (dialog │ │ key) │ └──────────────┘ │ key) │ └──────────┘ └──────────┘
  1. On the sender's device text is encrypted with the dialog key (AES-256-GCM) → ciphertext + IV.
  2. The ciphertext travels to the server over TLS (double protection: transport TLS + payload already E2EE-encrypted).
  3. The server stores and forwards only ciphertext. It cannot decrypt — it holds no key.
  4. On the recipient's device the ciphertext is decrypted with the dialog key.

Transport: all connections (HTTPS + WebSocket/Socket.IO) run over TLS. On Android certificate pinning is additionally enabled (pinned to ISRG root certificates) — protection against certificate substitution and corporate/state MITM proxies.

Call encryption

In plain words: calls are encrypted with AES — the same encryption used by Google Meet, Jitsi, Discord and WhatsApp calls. Eavesdropping on a call over the network — on Wi-Fi, at your ISP, on the TURN relay — is impossible.

§4What and where things are stored

4.1 On the server

4.2 On the device (Android)

WhatHow it's stored
Message text (local cache)ciphertext only in the local DB (SQLite/SQLDelight)
Media files (cache)encrypted bytes only on disk; decryption happens in RAM
Keys (dialog, EC-private, tokens, PIN hash, salts)EncryptedSharedPreferences: values AES-256-GCM, master key in Android Keystore (TEE)
Main & EC-private keys (when a PIN is set)additionally encrypted with AES-256-GCM under KEK = PBKDF2(PIN) on top of Keystore; plaintext only in RAM after unlock
Note: on Android the local DB is encrypted with SQLCipher (key in Keystore), so both metadata (contact IDs, timestamps, file names) and message ciphertext are encrypted on disk. On Desktop/iOS this is in progress — there cache metadata is still in the clear (content is always E2EE ciphertext).

§5Key management

§6Protection on the device itself

MechanismDescription
App PINstored as PBKDF2-HMAC-SHA256 (65,536 iterations, salt), never in the clear
PIN key-wrappingmain & EC-private keys encrypted under KEK = PBKDF2(PIN, 120,000); decryption only after PIN/biometric entry
Biometricsoptional unlock by fingerprint/face
Duress PINa special PIN triggers a panic wipe of keys and exit
Auto-lockby timeout (configurable) and on system screen lock
FLAG_SECUREoption: block screenshots/recording and hide the preview in "recents"
allowBackup=falseblocks extraction of app data via system backup (adb backup)
Plaintext-temp cleanupdecrypted temporary files (video/audio) are deleted after use

§7Threat model (what happens if…)

ThreatProtectionOutcome
Traffic interception (Wi-Fi, ISP, MITM)TLS + cert pinning + E2EEOnly ciphertext is visible
Server breach / seizureNo decryption keys on the serverConversation inaccessible
Dishonest operator / insiderE2EE: the server physically cannot readContent inaccessible
Contact-key substitution (key-level MITM)Fingerprint (safety number) verification + key-change alertsDetected by the user
App clones / emulatorsPlay Integrity (blocks illegitimate builds at login)Clones are rejected
User switch on a shared deviceFull wipe of local caches/decrypted data on login under another account; per-session push tokensPrevious user's data inaccessible
Phone theft — image dump (locked, no root)Android FBE + key in TEE; messages = ciphertextCannot decrypt
Phone theft — full OS compromise (root on unlocked)Keys encrypted under KEK = PBKDF2(PIN) on top of TEECannot decrypt without the PIN
In plain words: you can't just "dump" a phone and read the conversation. Even after fully compromising an unlocked phone, without your PIN the messages stay locked — the keys are sealed by your PIN, and the PIN is stored nowhere on the device.

§8Authentication, sessions, notifications

§9Results of the checks performed

CheckResult
Static analysis Semgrep (Kotlin: app+shared, 121 files, OWASP/secrets/security-audit)0 findings
Static analysis Semgrep (Node.js backend, 41 files, 131 rules)5 findings, all false after manual review
Backend security audit (internal)Closed: NoSQL injection, IDOR, session revocation, admin default password
Logging audit (Android)No secrets/plaintext in logs; HTTP bodies logged only in debug builds; allowBackup=false; components not exported
Crypto invariantsCovered by unit tests (cross-platform key format, AES-GCM, fingerprint)

§10Hardening

Implemented

  1. PIN key-wrapping — main & EC-private keys (all versions) additionally encrypted under KEK = PBKDF2(PIN, 120,000) on top of Keystore. Result: a full device compromise without the PIN can't decrypt the conversation.
  2. StrongBox for the Keystore master key — on devices with a dedicated security chip the master key is stored there (safe fallback to regular TEE).
  3. CryptoObject biometrics (Class 3) — on strong-biometric devices the KEK is released by a hardware Keystore key only after fingerprint auth; enrolling a new fingerprint invalidates the key.
  4. SQLCipher for the local DB (Android) — the local cache is AES-encrypted under a Keystore key; now metadata on disk is encrypted too.
  5. R8/obfuscation + Log stripping in release — the release Android client is obfuscated and Log calls are stripped at build time.
  6. Data isolation between accounts on a shared device — on login as another user, the previous user's local caches and decrypted data are fully wiped.

Redundant

7. HKDF instead of SHA-256 for deriving the dialog key — possible, but not particularly meaningful. SHA-256(ECDH shared secret) is already a cryptographically sound way to obtain a key with no known weakness HKDF would close; switching would be a breaking change requiring versioned migration across all three platforms. The current encryption is already at a high level; a rewrite to HKDF is not planned.

§11Conclusion

Frendy implements end-to-end encryption on standard primitives (AES-256-GCM, ECDH P-256, PBKDF2), stores conversations and media on the device encrypted, and keeps keys in a hardware secure module. The server is architecturally incapable of reading conversations. Transport is protected by TLS with pinning. Static analysis of the client found no vulnerabilities; backend findings turned out to be false.

On phone theft the conversation stays inaccessible: on an image dump — thanks to E2EE and hardware key storage; on a full OS compromise — thanks to PIN key-wrapping (no decryption without the PIN, and the PIN is stored nowhere on the device).

Prepared from analysis of the Frendy source code. Kept current as changes ship.
Download Frendy See the comparison