The messenger delivers the container
Secure Chat operates separately from Telegram, email and other delivery methods. On the sender's device, text or a file becomes an encrypted container. The chosen channel carries that container, and the recipient decrypts it locally with their private key.
- The participants create their own profiles and exchange public key cards.
- They independently compare the full key fingerprints and verify the contact in the app.
- The sender selects their profile, recipient and algorithm. Secure Chat encrypts the content and signs the container.
- The recipient opens the container, enters the wallet password if needed and receives a verified result.
Access to messenger history alone does not provide the Secure Chat private key. The additional protection applies specifically to content you encrypt beforehand. It does not hide the fact of communication, participants, transmission times or network addresses, and it does not protect a device already running malware.
Two key pairs in every profile
| Purpose | Mechanism |
|---|---|
| Content key transfer | RSA-3072 with OAEP-SHA256 |
| Container signing | ECDSA, NIST P-256 curve, SHA-256 hash |
| Public key format | Canonical DER SubjectPublicKeyInfo (SPKI) |
| Private key format | DER PKCS#8, inside the encrypted wallet |
The public card
.scpub
contains the name, both public keys, fingerprint and format version. It contains no private keys. The card is readable JSON; binary fields are encoded in Base64.
Format: SecureChat.PublicKey
Version: 1
Name
EncryptionPublicKey
SigningPublicKey
Fingerprint
How a contact is verified
The fingerprint combines both public keys into one SHA-256 hash: 64 hexadecimal characters. Before sending, compare it in full in person or through another trusted channel. The name in an imported card is not proof of identity.
SHA256(
UTF8("SecureChat.Identity.v1\0")
|| int32BE(length(RSA_SPKI)) || RSA_SPKI
|| int32BE(length(ECDSA_SPKI)) || ECDSA_SPKI
)
An imported contact is always marked unverified. The app permits encryption to that contact after you confirm the fingerprint comparison. Your own profiles can be chosen as recipients to test the app.
A valid digital signature proves that the container matches the signing key. A verified fingerprint links that key to a particular person. For an unknown or unverified key, the app separately indicates that the sender's identity is not confirmed.
A separate random key for every container
Every new message and file receives a new random 256-bit key using
RandomNumberGenerator.GetBytes
. The user chooses one of two authenticated encryption modes — AEAD:
| Algorithm | Key | Nonce | Tag |
|---|---|---|---|
| AES-256-GCM | 32 bytes | 12 bytes | 16 bytes |
| ChaCha20-Poly1305 | 32 bytes | 12 bytes | 16 bytes |
ChaCha20-Poly1305 is offered only when supported by the operating system. The selected algorithm is included in the authenticated header; there is no automatic downgrade to a weaker option.
The random content key is encrypted with the recipient's public RSA key. A separate copy is encrypted with the sender's public RSA key, so the sender can also open their outgoing message. Sending to yourself creates one such slot.
AEAD verifies the integrity of the content and associated data. The ECDSA-P256/SHA-256 signature additionally binds the container to the sender's key. Failed verification aborts decryption.
A wallet protected by a password
Profiles with private keys, contacts, protected settings and history are stored in a single file
Data/wallet.scvault
beside the app. AES-256-GCM is used to encrypt the wallet.
| Parameter | Value |
|---|---|
| Password-based key derivation | PBKDF2-HMAC-SHA-256, 600,000 iterations |
| Random salt | 32 bytes |
| Derived key | 32 bytes |
| Nonce on save | A fresh random 12 bytes |
| Authentication tag | 16 bytes |
| Password length at creation | 12 to 1024 characters |
| Maximum plaintext wallet size | 64 MiB |
Neither the password nor a separate password verification hash is stored. PBKDF2 slows guessing, but protection still depends on the password itself. For a long, unique passphrase, use random words or a password manager.
The 64-byte header is also authenticated. Ciphertext and the tag follow it:
"SCVAULT1"[8]
|| version:int32LE
|| iterations:int32LE
|| salt[32]
|| nonce[12]
|| ciphertext_length:int32LE
|| ciphertext
|| tag[16]
Version 1 accepts only the specified KDF parameters, a bounded size and an exact file length. Saving prepares a complete encrypted copy and replaces the file. Concurrent writes and saving over detected external changes are rejected. The auxiliary
.lock
contains no keys.
What the plaintext header contains
A message or file container begins with a four-byte type, the length of a JSON header and the header itself. The JSON size is limited to 16,384 bytes.
magic[4] || header_length:int32LE || header_json[header_length]
| Field | Contents |
|---|---|
Version
|
Format version: 1 |
Kind
|
text
or
file
|
Algorithm
|
0 — AES-256-GCM; 1 — ChaCha20-Poly1305 |
SenderEncryptionPublicKey
,
SenderSigningPublicKey
|
Sender's public keys |
RecipientFingerprint
|
Main recipient's fingerprint |
PlaintextLength
|
Original content length in bytes |
OriginalFileName
|
File name; an empty string for text |
ChunkSize
|
1,048,576 for a file, 0 for text |
Nonce
|
12 random bytes for text, or a random 4-byte prefix for a file |
KeySlots
|
One or two slots: a fingerprint and a 384-byte RSA-wrapped key |
This metadata is not encrypted. The algorithm, size, file name, public keys and fingerprints are visible. Persistent keys allow containers from the same sender to be linked. If the file name is sensitive, rename the file before encrypting it.
The plaintext header is protected against undetected modification: the signature and AEAD use its original bytes, not reserialized JSON. Below,
||
means byte concatenation. Integers are little-endian; exceptions using big-endian are explicitly marked.
Text: an SC1 container and .scmsg attachment
Original text is limited to 1 MiB in strict UTF-8 encoding. The binary container type is
SCT1
:
"SCT1"
|| header_length:int32LE || header_json
|| ciphertext_length:int32LE || ciphertext
|| tag[16] || signature[64]
The entire prefix — type, length and JSON header — is used as additional authenticated data (AAD). The ECDSA-P256 signature is calculated over the SHA-256 hash of all preceding bytes. The signature format is
IeeeP1363FixedFieldConcatenation
:
r || s
, 32 bytes each.
For transfer, the binary container is converted to Base64url without padding characters
=
:
SC1:<base64url(binary_container)>
securechat://message/<base64url(binary_container)>
Before revealing text, the app checks the structure, exact length and signature, then decrypts the RSA-wrapped key and verifies AEAD. Invalid characters, line breaks within the envelope, additional URI parameters and corrupted data are rejected.
Sending through Telegram
The “File for chat” button copies an encrypted
.scmsg
to the Windows file clipboard. Paste it into your chat using
Ctrl
+
V
and send the attachment. The recipient downloads the file and opens it in Secure Chat. A locked app asks for the password first.
An ordinary
SC1:…
envelope can also be copied and pasted manually. The Windows link
securechat://…
is limited to 8,000 characters and is not clickable in every client. Telegram Desktop does not turn this arbitrary scheme into a normal automatic link; use a
.scmsg
attachment there. Older
.sct
are opened explicitly inside the app.
Files: chunks, signature and two verification passes
An encrypted file receives the extension
.scx
and type
SCF1
. The format's original file limit is 1 TiB. Processing is streamed: the entire file is not loaded into memory, and content is divided into 1 MiB chunks.
"SCF1" || header_length:int32LE || header_json
|| (chunk_length:int32LE || ciphertext[chunk_length] || tag[16])*
|| 0:int32LE || terminal_tag[16]
|| signature[64]
Each chunk gets its own 12-byte nonce. It consists of the container's random four-byte prefix and an eight-byte chunk index:
nonce = Nonce[4] || uint64BE(chunk_index)
AAD = SHA256(full_prefix)[32]
|| uint64BE(chunk_index)
|| int32LE(chunk_length)
Indexing starts at zero. A final empty chunk receives the next index and its own tag. Even an empty file has a header, final chunk, tag and signature. A new content key is generated for every new file.
The signature covers all bytes from
SCF1
through the final AEAD tag, inclusive. Chunk order and size, exact container length and end of file are checked. Reordering, duplication, removal, truncation or appending data causes rejection.
What happens during decryption
- The bounded header, file length and availability of a matching private key are checked.
- The first pass reads the container and verifies the full signature. No plaintext temporary file is created yet.
- The second pass verifies AEAD for each chunk, writes decrypted data to a temporary file and verifies the final signature again.
- After successful completion, the result is renamed to a new file chosen by the user. An existing file is not overwritten.
The status bar shows overall progress: signature verification, decryption and saving. 100% means the result was saved successfully. Cancellation is available during processing; on a normal error or cancellation, the temporary file is deleted.
The original file is preserved. The decrypted result is not run automatically. Power loss, forced process termination or an external handle preventing deletion can leave a plaintext temporary file behind. Protecting the output folder and disk remains an important part of handling secrets.
Local storage, history and locking
| File or folder | Purpose |
|---|---|
Data/wallet.scvault
|
Encrypted profiles, keys, contacts, settings and history |
Data/window-state.json
|
Window size, position and state; no keys or conversations |
Data/ui-preferences.json
|
Language and update check preference; no wallet data |
Data/ClipboardAttachments
|
Temporary copies of encrypted messages for pasting into a chat |
History contains operation metadata and encrypted text envelopes. Plaintext and file contents are not recorded. The most recent 100 entries are retained, with a total envelope size of about 8 MiB; older entries are removed automatically. History can be disabled or cleared.
The wallet locks with Ctrl + L , when minimizing, locking or disconnecting the Windows session, going to sleep, and after inactivity. The idle interval can be set from 1 to 15 minutes; the default is 5 minutes. The current file operation is cancelled before keys are released.
Locking clears fields and entry references, and zeroes the application's private key and derived key arrays. Complete removal of all memory copies is not guaranteed: managed strings, clipboard contents, dumps and the page file can retain remnants.
Two different clipboard modes
“Copy text” marks the app's own clipboard entry and clears it after 30 seconds or on lock if the entry still belongs to Secure Chat. A newer entry from another app is not erased. Ordinary copying with the system command Ctrl + C is not controlled by this timer.
“File for chat” places the path of a prepared
.scmsg
in Windows FileDrop, containing only ciphertext. This copy remains available after locking and closing the wallet so the messenger has time to read it. Each copy action creates a separate attachment.
Copies older than 24 hours are cleaned up on a later launch or copy operation. Up to 128 temporary attachments are retained at once; at the limit, you can explicitly save a message to a chosen folder. Restrictions on clipboard history and cloud clipboard are passed to Windows as hints; third-party clipboard managers may ignore them.
Backups, moving and updating
A backup is the complete encrypted wallet. Opening it requires the password used when it was created. Update the backup after adding new keys. Access recovery without the password and private keys is not available.
Changing the password, deleting a profile or clearing history changes the current wallet. Older backups retain the previous password, keys and history. Deleting an entry also does not guarantee physical erasure from an SSD, snapshots or a journaled file system.
To update to 0.1.6, close Secure Chat and replace
SecureChat.exe
in the same folder.
Keep the entire Data folder.
Wallet and container formats remain at version 1; no migration is required. For future releases with format changes, migration steps will be documented separately.
When moving to another folder, move the EXE together with
Data
and any required external translations in
Languages
. Then re-enable handling of
.scmsg
,
.scx
and links in settings: Windows registration stores the absolute path. The single-file EXE may extract native .NET libraries into the user's system cache.
What network requests the app makes
Encryption and decryption require no server. Secure Chat has no message transport, key-exchange account or password reset service. Your chosen messenger, email or other channel delivers the containers.
When “Check for updates” is clicked, the app retrieves a small JSON file from
https://itmsft.com/securechat/update/latest.json
. Checking on launch can be enabled separately; it is disabled by default. Normal HTTPS request network details and a generic User-Agent containing the app version are transmitted. Passwords, keys, profiles, history and file contents are not included.
The client limits the response to 64 KiB and a 10-second read timeout, and checks the version and availability of a package for its architecture. Server, network or format errors report that the check could not be completed rather than claiming the installed version is current.
The download button opens an allowed HTTPS package URL on
itmsft.com
in the default browser. The app does not install updates automatically or run the downloaded file. It validates the SHA-256 field format but does not verify the bytes of the browser-downloaded ZIP. The EXE has no publisher digital signature in version 0.1.6.
Limits to keep in mind
- Device security. Malware, keyloggers, memory access, EXE replacement or remote access can expose text before encryption and after decryption.
- Metadata. The container's plaintext header and network details of the delivery channel are visible. This is not an anonymity tool.
- Static keys. Forward secrecy is not implemented: a stolen private key can decrypt previously intercepted containers addressed to that key.
- Restoring secrecy. There is no Double Ratchet or post-compromise security. This is a custom format, not an implementation of the Signal protocol.
- Replay and rollback. A valid container can be resent. A valid older wallet copy has no rollback protection: integrity does not prove freshness.
- Password. The minimum length and UI delays do not stop offline guessing against a stolen wallet. A strong password and the KDF are the main protections.
- Data deletion. Complete erasure from RAM, swap, cloud clipboard and storage is not guaranteed.
- Cryptographic properties. The scheme is not post-quantum. Third parties with a copy of the container can verify its signature; cryptographic deniability is not provided.
- Review status. Standard primitives and automated tests do not replace an independent audit of the protocol and implementation.
Secure Chat adds separate control over selected content. Use with highly sensitive data requires assessing your threat model, a protected device and independent expert review of the implementation.