Security
A trust model that is explained can be audited; one that is hidden cannot. This is what SecureChat does, and also what it doesn’t.
In short
- Messages are end-to-end encrypted between your phone and the business’s server. We only carry encrypted text.
- We never generate or see private keys: yours is created on your phone; the business’s, on its own infrastructure.
- We verify businesses and sign their public key; the app warns you if that key changes.
- Encryption ends at the business’s backend: what it does with the decrypted text is beyond our control.
- The current version of the protocol does not offer forward secrecy.
- We are the key directory for both sides: without the extra protection of the link, an active malicious operator (us) could swap a key at first contact.
How a message is encrypted (envelope v1)
We use libsodium, an audited and widely used cryptographic library. These are the building blocks of envelope v1 (sc1):
| Component | Construction |
|---|---|
| Identity (your device and the business) | X25519 key pair |
| Conversation key | 32 random bytes generated by your phone when you redeem the invitation. If we generated it, we would know it. |
| Distributing that key | It is sealed separately (crypto_box_seal) for your public key and for the business’s. We only store the two sealed envelopes. |
| Encryption of each message | XChaCha20-Poly1305 with a random 24-byte nonce |
| Associated data | Each message is bound to its conversation, its sender, its identifier and the key version. A server cannot move it to another conversation, duplicate it under another identifier or pass off one of your messages as one from the business. |
| Padding | Up to multiples of 256 bytes, to hide the exact length |
| Business key signature | Ed25519, with the platform key |
| Key rotation signature | Ed25519, with the business’s own signing key (see below) |
| Fingerprints | BLAKE2b-256 with domain separation |
Encrypted attachments
Photos and documents are encrypted on the device that sends them (your phone or the business’s server), just like text:
- Each file has its own key of 32 random bytes, different from the conversation key.
- It is encrypted in 64 KiB chunks with XChaCha20-Poly1305. Each chunk is authenticated together with the attachment identifier, its position and whether it is the last one: reordering, truncating or adding chunks makes decryption fail.
- The file key, its name, type, size and fingerprint (BLAKE2b) travel inside a regular encrypted message. When it finishes, the app checks the fingerprint; if anything doesn’t match, it shows “This attachment could not be decrypted” and never a partial file.
- We only store opaque bytes and an identifier. We see the size of the encrypted file, which is practically that of the original: attachments are not padded like messages. Only the two ends of the conversation can upload and download it, using temporary links valid for 5 minutes.
- Maximum 25 MB per file. We don’t generate thumbnails (we can’t: we don’t see the content); the app creates the preview after decrypting.
What it protects
- A leak of our database or our backups: they only contain encrypted text.
- A curious employee with access to the servers.
- A passive attacker on the network, or anyone intercepting the traffic.
- Push notifications: they don’t carry the content, so neither Apple nor Google see it.
- Tampering with messages in transit or on our servers: decryption fails instead of showing altered text.
What it doesn’t protect
- The other end is the business’s backend. That is where the message is decrypted. If the business forwards it unencrypted to a cloud helpdesk or CRM, that provider can see it. If you work in a regulated sector, ask the business where the text ends up.
- Us, acting actively. Your app gets the business’s key from our server, and the business also gets yours from us. A malicious operator could swap either of them. The app pins the business’s key at first contact and warns you if it changes, but that doesn’t protect that very first contact. The link fingerprint (see below) closes that gap when the invitation arrives as a link generated with the SDK.
- A compromised device. If someone controls your phone or the business’s server, they can read the messages there.
- Metadata. We explain it below.
Platform key and business verification
Before a business can send invitations, we check that it legally exists, that it controls its domain (DNS record) and the WhatsApp number it invites from. We then sign its public key with our Ed25519 platform key. The app verifies that signature and shows the Verified business badge.
Platform public key (Ed25519, base64url):
qxh_sOak_wQVqZifJjDcoy-TeWIF3NADjqsjta0GPlw
What gets signed is exactly SecureChat|v1|company-key|<companyId>|<keyVersion>|<public key>. This concentrates the risk on us: if someone stole the platform key, they could impersonate a brand. That is why the link check also exists.
The business generates its own key pair on its infrastructure with our SDK (npx @securechat/sdk keygen) and only uploads the public key to us. The portal does not offer to generate it in the browser: if the private key went through our servers, the promise would no longer be verifiable.
Unverified businesses and Test mode
So that a business can test its integration while we review it, it can create test invitations. Its key is not signed as verified, so anyone could sign up as “Bank X” and use them to impersonate a brand. That is why:
- The app only opens a test invitation if you have turned on Test mode in Settings → Diagnostics. It is off by default: a regular customer never ends up in a test conversation by mistake, even if they receive the link. Don’t turn it on because someone asks you to: a real business doesn’t need it to talk to you.
- Those conversations always show the “Unverified business · test environment” banner and never the verified badge.
- They have strict limits: 5 active conversations per business, 200 messages from the business per day and invitations valid for 24 hours at most. When we verify the business, its test conversations are closed.
Encryption is the same as in a regular conversation. What changes is the identity guarantee: in a test conversation we have not checked who the business is.
The fingerprint in the invitation link
When the business creates the invitation with the SDK, it adds a #s=…&fp=… fragment to the link. Browsers don’t send the fragment to the server, so we never see it, but the link travels over WhatsApp, a channel we don’t control. When you redeem it:
- The app checks that the business key we provide matches the
fpfingerprint in the link. - The app binds its own key to the secret
swith a MAC, and the business verifies it.
If we swapped either key, one of the two ends would detect it. Limits: it only covers redeeming by link, not a typed short code, and invitations created manually in the portal don’t include that secret.
Key rotation signed by the business
Besides its encryption key, the business uses the SDK to generate an Ed25519 signing key that also stays on its infrastructure; it only uploads the public key to us. The app pins it at first contact, together with the encryption key. When the business rotates its encryption key, it signs the change with that signing key: SecureChat|v1|company-key-rotation|<companyId>|<previous version>|<previous key>|<new version>|<new key>. We check the signature before accepting the new key, and the app checks it again.
- Rotation signed by the pinned key: a discreet notice (“the business renewed its security key; the business itself signed the change”).
- Unsigned, or signed with another key: a prominent warning. It’s best to confirm with the business through another channel before continuing. Businesses that didn’t upload a signing key always rotate this way.
Why it matters: with only our platform signature, a legitimate rotation and an impersonation by us would look the same. With the business’s signature, impersonating a business you have already pinned requires its signing key, which never goes through us. The platform signature remains mandatory in both cases. For now the signing key does not rotate: changing it is treated as an unsigned rotation.
No forward secrecy in v1
Each conversation uses a stable key. If your private key or the business’s were ever leaked, together with the stored encrypted messages, the earlier messages of that conversation could also be decrypted. Protocols like Signal renew keys with every message to prevent this; we deliberately left it out of the first version of the protocol and it is on our roadmap.
Metadata
Even though we don’t see the content, we do see who talks to which business, when and how often, the approximate size of each message (rounded to 256 bytes), the size and date of each attachment, IP addresses and device details. That is enough to infer quite a lot, which is why we declare it in the privacy policy.
Your history is tied to your device: if you lose it, you get each conversation back with a new invitation from the business, which seals the key again for your new device.
Report a vulnerability
Email us at soporte@securechat-app.com with the subject “Security”. We ask you to allow us a reasonable time to fix it before disclosing it.