Security that relies on being secret is brittle. Login is built on published, auditable standards (WebAuthn/FIDO2, AES, PBKDF2, ECDSA P-256) because standards survive scrutiny—and customers buying authentication for frontline workers have a right to know exactly what they’re trusting.
What follows is the architecture at the altitude an informed buyer needs. Lower-level implementation notes and our internal security review live in the project repository and are shared with customers and auditors on request.
| Scenario | What protects you |
|---|---|
| Phone is lost or stolen | No passkeys persist on disk. The card is required to load them; without it, there is nothing to extract. |
| Card is lost or stolen | No PIN ⇒ no access. Application keys are derived from the PIN; without it, the card data is unreadable. |
| Attacker tries PIN guessing on the phone | After 3 wrong attempts, the in-memory cache, the stored PIN hash, and any locally held passkey state are wiped. The user has to tap their card again to start a new session. The card itself authenticates with a key derived from the PIN; a wrong PIN yields a wrong key and authentication fails, offering no oracle. |
| NFC eavesdropping during read/write | All NFC traffic is sent in DESFire Enciphered mode with per-session keys negotiated during authentication. |
| ADB backup extraction | Disabled at the manifest level. |
| Phishing site impersonating a real login page | WebAuthn passkeys are origin-bound by the browser. A passkey for github.com cannot be used on g1thub.com, even if the user is fooled. |
| Static reverse engineering of the APK | Release builds are obfuscated with R8 and signed via Google Play App Signing. Obfuscation slows analysis; it doesn’t replace the cryptographic protections above. |
| Layer | What we use | Why |
|---|---|---|
| Passkey cryptography | ECDSA on the NIST P-256 curve (WebAuthn COSE algorithm -7) |
The default WebAuthn algorithm; supported by every relying party that supports passkeys. |
| Card primary storage | MIFARE DESFire EV3 with AES-128 application keys | Hardware-isolated application domain. Authentication required for any read or write. |
| PIN-to-key derivation | PBKDF2-HMAC-SHA256, card UID as salt, 600,000 iterations by default (configurable up to 2,000,000) | Meets OWASP 2023 guidance at the default; higher levels available for cards that warrant them. Per-card salt prevents a single shared rainbow table. |
| Stored PIN hash on phone | PBKDF2-HMAC-SHA256, 600,000 iterations, random 16-byte salt per device | Lets the app verify an entered PIN before going to the card. The PIN itself is not stored or recoverable. |
| NFC over-the-air | DESFire CommunicationType.Enciphered with per-session keys |
Defeats passive eavesdropping, including specialised NFC sniffing rigs. |
| Passkey use on the phone | In-memory only. Each private key is envelope-encrypted with AES-256-GCM under a non-exportable Android Keystore wrapping key; nothing persists on disk. | Nothing for forensic tooling to extract from a powered-off or factory-reset device. |
| App distribution | Google Play App Signing (Google manages the production signing key) | Removes the risk of a leaked or lost private signing key. |
| Release builds | R8 minification & obfuscation, release-only logging stripped | Raises the cost of static analysis. Not a substitute for cryptographic security. |
See the Privacy Policy for the full list.
If you believe you’ve found a security issue, please email support@phenomenal.se with the details. Please do not post details publicly until we’ve had a chance to respond. We treat security reports as high priority.
Cryptography evolves. We will update this page when we change parameters (for example, raising PBKDF2 iteration counts), adopt new algorithms (such as hardware-backed ECDSA on Android Keystore for non-synced credentials), or learn about a vulnerability in a primitive we depend on. The “Last Updated” date at the top reflects material changes.