The release of Ad Nihilum 0.4.3 has taken place — a minimalist service for exchanging encrypted messages based on the principle of 'read and burn', primarily aimed at self-hosting.
The server acts merely as a passive storage. Encryption and decryption occur exclusively on the client side, in the browser (via AES-GCM).
Features
- local encryption and decryption, server never sees the key;
- support for an additional layer of encryption with a password, which (1) the server cannot know, (2) cannot be inferred from the transmitted link;
- the project contains about 2200 lines of server code in C and 600 lines of client code in JS, simplifying audit;
- Ad Nihilum depends only on libmicrohttpd. A modified version of QRCode.js is included for generating QR codes;
- a guide is included for quickly setting up a local service without an external IP;
- Ad Nihilum works on Android, and a corresponding script for building in Termux is included;
- single-threaded and synchronous server.
Changes
Major redesign
- The message sending and receiving pages have been separated, along with the corresponding client code.
- Overall, the design has been significantly changed and adjusted based on the suggestions from the community.
- ‘Simple’ and local clients:
- client pages can be saved locally and accessed from file://.
Browser policies
- CSP has been implemented to combat XSS;
- the server sends HSTS.
Other changes
- the project has been renamed from Epha-ots;
- the domain adnihilum.net has been purchased;
- TLS is provided using Let’s Encrypt, this is important to note (no money);
- work with files has been simplified;
- transition to fat pointers;
- minor bugs have been fixed;
- auto-jsminify and client files assembly is done during the build via CMake.
Protocol Overview
Generate three random values: key K, initialization vector N and salt S. K — 256 bits, N — 96 bits, S — 128 bits.
Output ID from K and S using HKDF based on SHA-256.
Form a string of additional authenticated data aad — this is just a string of the form: id=ID
If the user has set a password:
- output Pk from the password and S using PBKDF2, SHA-256, 800000 iterations;
- the same salt is used for everything;
- encrypt the data using AES-GCM, using the key Pk, IV/nonce N and pass aad.
Add a two-byte tag to the already encrypted data if there was a password, or to the original data if there was no password.
The first byte matters: it indicates whether the data was encrypted with a password.
- 0x73 — data is encrypted with a password;
- 0x13 — data is not encrypted with a password.
The second byte is a constant value of 0x37.
Re-encrypt the result using AES-GCM, with the same iv = N and the same aad. This gives the final ciphertext ct.
Concatenate the bytes into a string: blob = N .. S .. ct
Send blob to the server along with ID. The server returns blob through this ID and cannot replace it: the client will first check ID using N and K before decryption, and then through aad.
The client stores K. K never sent to the server. Pk is also not sent; everything related to the password is cleared from memory.
The client forms a link: origin/#ID/K
Here ID and K — strings in base64url format.
When the recipient opens the link:
- the browser discards everything that starts with #; this is called location.hash;
- the client application loads from the server;
- in my opinion, this is the main hole: we actually run into the fact that 'TLS is leaky';
- however, nothing prevents keeping the client offline;
- ideally, there should be a separate standalone client.
The client-side JavaScript checks location.hash, and if there is ID and K, it loads the data from the server.
Then it verifies them, decrypts, and if necessary, requests a password and decrypts again.
License
The project is distributed under GPLv3.
Source: linux.org.ru
