Updated: May 2026
OV Message is an end-to-end encrypted messaging application that operates without a centralized server. Messages are encrypted before sending and decrypted upon receipt, regardless of the transport channel used. The current implementation uses the SMS network, but the encryption protocol is transport-agnostic. It works over any channel capable of transmitting text.
Serverless architecture: This deliberate choice eliminates a single point of failure, removes the need to trust a third-party operator, and requires no Internet connection.
This document describes the cryptographic mechanisms used, the design choices, and the known limitations of the system.
Operator, ISP, network attacker: E2E encryption, the channel only sees ciphertext.
HMAC-SHA256, any alteration invalidates the authentication tag.
Anti-replay through tag storage (128 bits, clock-independent).
Stream cipher based on SHA3-256, no fixed character-by-character correspondence.
Random per-message index; a (plaintext, ciphertext) pair does not help for another message.
Fortress Mode, automatic deletion of unauthenticated messages.
Cellebrite, JTAG: KEK/DEK, keys encrypted at rest, DEK destroyed on lock.
Native auto-lock, process kill, RAM freed by the OS.
Panic Mode, data destruction via alternate password.
System anti-screenshot flag on all application windows.
Backup disabled on all domains (cloud, local, transfer).
Argon2id (64 MB per attempt) + 3 failures = automatic Panic.
Deep links blocked when a password is active.
If a key exists, the application systematically encrypts messages intended for the relevant contact.
Per-contact isolation, each key produces independent alphabets and MAC keys.
Anonymized notifications (Fortress Mode), no name, no preview.
Operating system compromise (active root/jailbreak with keylogger). Side-channel attack on cryptographic hardware.
| Component | Technology |
|---|---|
| Framework | React Native 0.83 / Expo 55 |
| Engine | Hermes (compiled bytecode) |
| Cryptography | Native OpenSSL bindings (SHA3-256, HMAC-SHA256, AES-256-GCM, HKDF, Argon2id) + @noble/post-quantum (Hybrid KEM ML-KEM-768 + X25519, FIPS 203) |
| Sensitive storage | Android Keystore |
| Non-sensitive storage | Local database |
Secure keystore: Android Keystore + DEK encryption (AES-256-GCM)
Secure keystore: Android Keystore
Secure keystore: salt for DEK derivation and encrypted canary to verify the password at login
Local storage: stored in encrypted form (OV format)
Separation of responsibilities: All cryptographic logic is isolated in a dedicated services layer, separated from the user interface. Each service has a single responsibility.
OV Message uses a hash-based stream cipher. Each character of the message is encrypted individually using a substitution index derived from SHA3-256, depending on the position, a random index unique to the message, and a subkey derived from the contact key.
Not a classical substitution cipher: There is no fixed mapping table between plaintext characters and ciphertext characters.
Standard primitives (AES-CTR, ChaCha20) produce binary output. Using them on an SMS channel would require additional encoding (Base64, Base85) that increases the message size by 33 to 50%. On SMS, every character counts.
The OV Message cipher operates directly in Unicode space. Plaintext is transformed into ciphertext without going through an intermediate binary representation. The ratio is 1:1. One plaintext character produces one ciphertext character.
The chosen design uses SHA3-256 as a position-indexed pseudorandom generator, with a random index per message and a subkey derived by HKDF. Each character is encrypted independently with 256 bits of entropy per position.
Assumed trade-off: A non-standard cipher in exchange for an optimal size ratio over a bandwidth-limited channel. Standard primitives (AES-256-GCM, HKDF-SHA256, HMAC-SHA256) are used for all other cryptographic operations.
Cryptographic uniqueness: Each message uses a cryptographically independent subkey. Two identical messages sent to the same contact produce different ciphertexts.
SHA3-256 at each position, non-linear relationship between plaintext and ciphertext.
The random index changes the entire ciphertext for the same message.
No fixed character-to-character mapping.
Unique sub-key per message, knowing one (plaintext, ciphertext) pair does not help for another message.
The system uses Unicode alphabets designed to cover 100+ languages as input and produce output in a distinct Unicode space.
The MAC key is derived independently from the encryption key via HKDF-SHA256 with domain separation. Each conversation produces a unique MAC key.
The HMAC-SHA256 tag is computed over the concatenation of additional authenticated data (conversation identifier, message index) and the ciphertext. The tag is truncated to 128 bits.
Any modification of the ciphertext invalidates the tag.
The unique random index per message makes each tag unique.
Tag verification is mandatory upon decryption.
The MAC key is derived with a different info string from the file AES key.
Each contact key is generated locally with a CSPRNG. Keys have variable length and high entropy. Variable length makes deducing the key size from metadata difficult.
OV Message offers two out-of-band exchange methods:
| Method | Channel | Mechanism |
|---|---|---|
| In person | QR code physically scanned | Direct key transmission |
| Remote | SMS + trusted voice/video channel | Hybrid KEM ML-KEM-768 + X25519, SAS verification |
Hybrid post-quantum pairing: The initiator generates a hybrid key pair (ML-KEM-768 + X25519) and transmits the public key via SMS (format OVPK:<sessId>:I:<base64>). The responder encapsulates a shared secret with this public key and returns the ciphertext via SMS. After decapsulation, both sides have the same shared secret, from which the following are derived:
OV_PQHybrid_ContactKey_v1)OV_PQHybrid_SAS_v1)Both parties compare the SAS via a trusted external channel (voice, video). If the codes match, the key is established. Ephemeral keys are destroyed, the shared secret is erased from RAM. The SMS OVPK can therefore be publicly observed without compromising security.
Why hybrid? The final shared secret combines the ML-KEM secret (designed to resist quantum computers) and the X25519 secret (resistant to modern classical attacks). An attacker must break both to recover the contact key. This is intended to neutralize the strategy « harvest now, decrypt later » : an adversary recording the SMS today would need, to decapsulate ML-KEM-768, a cryptographically relevant quantum computer, beyond foreseeable capabilities in the near future.
A key pool pre-generated locally for each contact. Each key indexed by a number.
Pool encrypted with AES-256-GCM (key derived by HKDF-SHA256) before exchange.
Selection by index, only the number transits. Never the key itself.
Each consumed key is physically deleted, allowing manual forward secrecy at the rotation level.
| Usage | Algorithm | Details |
|---|---|---|
| Password hashing | Argon2id (m=64 MB, t=3, p=1, L=32) | Random salt per hash |
| DEK (master key) | Argon2id + HKDF-SHA256 | Info string OV_DEK_v1, dedicated salt |
| AES file key | HKDF-SHA256 | Dedicated info string |
| AES pool key | HKDF-SHA256 | Dedicated info string |
| MAC key | HKDF-SHA256 | Dedicated info string + conversation ID |
| Hybrid exchange (KEM) | ML-KEM-768 + X25519 + HKDF-SHA256 | Ephemeral keys per session, domain separation for contactKey vs SAS |
| Charset permutation | Deterministic derivation | Different input per contact |
Domain separation: Each context uses a distinct info string or salt, ensuring that the same contact key produces independent derived keys for each use.
Protect contact keys against forensic extraction (Cellebrite, JTAG, RAM extraction) in the event of physical seizure of the phone.
Symetric key of 256 bits, deterministically derived from the password via Argon2id (m=64 MB, t=3, p=1, L=32) then HKDF-SHA256 with the info string OV_DEK_v1. Never stored, regenerated at each login, loaded in RAM only, destroyed on lock (process kill).
At login, an AES-256-GCM encrypted canary under the DEK is decrypted to validate the password. An incorrect password produces a different DEK that cannot decrypt the canary: immediate detection without revealing the correct DEK value.
Each key encrypted individually with the DEK using AES-256-GCM. Lazy decryption (on demand).
Without a password configured, the keys remain in the secure keystore (protected by Android Keystore). Migration to encrypted mode is transparent upon the first password configuration.
Files shared between contacts are encrypted with AES-256-GCM. The AES key is derived via HKDF-SHA256 from the contact key and a random 16-byte salt, with a dedicated info string. A 12-byte IV is randomly generated.
Total overhead: 44 bytes (16-byte salt + 12-byte IV + 16-byte tag).
GCM authentication guarantees the integrity of the ciphertext and AAD. A file name consistency check is performed after decryption.
Clock independent: No timestamp, no TTL, no synchronisation required between devices.
| Threshold | Action |
|---|---|
| 1,500 stored tags | Alert: key rotation recommended |
| 1,800 stored tags | Alert: key rotation strongly recommended |
| 1,999 stored tags | Alert: urgent key rotation |
At each key rotation, the replay store is cleared. The new key produces a new MAC key, so old tags cannot be replayed.
Fortress Mode is an active filter that automatically deletes any incoming message that could not be authenticated. It constitutes protection against phishing, identity spoofing, and unsolicited messages.
The number is an OV Message contact with an encryption key. Failure = message deleted.
The message is in OV encrypted format. Failure = message deleted.
The HMAC-SHA256 tag is valid and decryption succeeds. Failure = message deleted.
Native layer: intercepts messages before they are broadcast to the system. Unknown numbers are blocked immediately, without notification.
Application layer: full cryptographic validation (HMAC + decryption) for messages that have passed the first filter.
In Fortress Mode, notifications are generic (no sender name, no content preview). Notifications displayed on the lock screen do not reveal the sender's identity or the content of the message.
Two levels of emergency destruction. Triggered by a dedicated alternate password. If self-destruction is enabled, 3 password failures automatically trigger Panic Mode.
| Data | Panic (complete destruction) | Panic keys (keys only) |
|---|---|---|
| Contact keys | Destroyed | Destroyed |
| DEK / KEK | Destroyed | Destroyed |
| Physical SMS | Deleted | Preserved |
| Address book contacts | Deleted | Preserved |
| Conversations, settings | Erased | Preserved |
| Encrypted messages | Deleted | Present but undecipherable |
| Memory caches (RAM) | Overwritten then flushed | Flushed |
| Reversibility | No | No |
Discreet: No visual alert, the application opens as after a normal login. The mode is designed not to betray its activation to an attacker who would not have a reference to the previous content.
A native module schedules process termination via an exact system alarm, immune to Doze mode. When the timer expires, the process is killed by the OS and all RAM is freed (DEK, keys, sensitive data). If the application returns to the foreground before expiration, the alarm is cancelled.
System flag preventing screen capture and recording.
Backup disabled on all domains (cloud, local, transfer).
Android Keystore for secrets (extraction requires PIN/biometrics).
The application operates without a permanent key exchange server, making the Double Ratchet impractical. A hash ratchet would cause desynchronization on lost or reordered messages.
Security in static key mode: Each message derives an independent subkey via HKDF-SHA256. Compromise of a subkey does not compromise other messages. The key pool provides manual forward secrecy. Deletion of a consumed key renders past messages undecipherable.
Hybrid pairing and FS: The ML-KEM-768 + X25519 pairing generates keys ephemeral per session, destroyed after derivation of the contact key. The pairing session itself is therefore forward-secret : compromising the phone after pairing does not allow replaying this session. However, the derived contact key remains static (shared between both devices). Forward secrecy at the level message still relies on manual rotation via the key pool.
The transport channel exposes metadata (sender, recipient, timestamp). OV Message cannot hide this information because it is part of the underlying transport protocol.
Messages are deterministically padded. An observer can approximate the length of the plaintext message. This trade-off is accepted because transport metadata already leaks more information than the message length.
Audit: The stream cipher based on SHA3-256 is an original design whose security relies on the well-established PRF properties of SHA3-256. An independent formal cryptographic audit is planned and will be published. All other cryptographic operations use standard audited primitives.
Security relies on the confidentiality of the shared key and the authenticity of the verification channel:
OV Message targets a threat model that conventional messengers (Signal, WhatsApp, Olvid) do not address: operation without a server or Internet connection, resilience to physical phone seizure, and emergency data destruction under duress.
Complete destruction or keys only, silent on the interface side.
Process termination, RAM release by the OS.
3-step anti-phishing filtering with anonymized notifications.
Zero infrastructure cost, no dependency on a third party, independent of centralized servers.
| Component | Parameter | Value |
|---|---|---|
| Message encryption | Position-based hash | SHA3-256 (256 bits) |
| Message encryption | Random index | High entropy (CSPRNG) |
| Message encryption | Per-message subkey | HKDF-SHA256 (256 bits) |
| Message encryption | Alphabets | Derived per contact |
| HMAC | Algorithm | HMAC-SHA256 |
| HMAC | Tag | 128 bits |
| HMAC | Derivation | HKDF-SHA256 with domain separation |
| Argon2id | Memory (m) | 64 MB |
| Argon2id | Iterations (t) | 3 |
| Argon2id | Parallelism (p) | 1 |
| Argon2id | Output | 256 bits |
| AES-256-GCM | Key | 256 bits |
| AES-256-GCM | IV | 96 bits |
| AES-256-GCM | Tag | 128 bits |
| KEK/DEK | DEK | 256 bits, deterministically derived (Argon2id + HKDF-SHA256, info OV_DEK_v1) |
| KEK/DEK | Password verification | Canary encrypted with AES-256-GCM under the DEK |
| Key generation | Entropy | High entropy (CSPRNG) |
| Hybrid KEM | ML-KEM-768 | FIPS 203, NIST level 3 (post-quantum) |
| Hybrid KEM | X25519 | 128 bits of classical security |
| Pairing derivation | HKDF-SHA256 | Domain separation contactKey / SAS |
| SAS | Truncated HMAC-SHA256 | 12 hex chars (48 bits), out-of-band comparison |
| Ephemeral keys | Destroyed after exchange, live-only sessions (RAM only, no persistence) | |
| Anti-replay | Storage | Per contact, per direction |
| Files | Encryption | AES-256-GCM + HKDF |
OV Message: encryption without server, without dependency, without compromise on security.