triops is a command-line tool for encryption/decryption of files that I released under GPL v3 some time ago.
This post will try to explain its operation and file format.
Explanation covers all versions published until present (v9.0).
At the end of the post the origin of the CHACHA20 and KECCAK C code used is indicated.
Note that by default triops overwrites the contents of the original file. This is usually desirable when encrypting data, as the clear text should not be available any more. Anyway, this behaviour can be overridden with -o and -O options (output to another file, or output to stdout).
triops uses CHACHA20 for data encryption.
As CHACHA20 requires an initialization vector (IV) of 8 bytes, one is randomly produced. The process of creation of the IV involves also the length of the file — if input is stdin, file length is not known in advance, so only random values are used.
file size (8 bytes) + random values (8 bytes) --KECCAK-512--> take one byte of four --> IV
As KECCAK-512 produces a 64 bytes long hash and IV requires only 8, only the first byte of each group of four bytes is used to form the IV.
KECCAK hashes are also used to make a hint of the password used. This hint is stored with the file in order to ensure that the file is only decrypted with the correct password, as by default it overwrites its contents.
The password hint is calculated from password text (or file contents, if a file is used as password) and from IV used. This ensures that every file produces different password hints each time it’s encrypted. This way, it is not possible to know if the password used in two different encrypted files has been the same or not.
+--500--+ password --KECCAK-512--+ | | text | v | +--> HASH ---+-> HASH + IV --KECCAK-512--> password (64 bytes) | hint file --KECCAK-512--+ contents
After the first KECCAK-512 hash is obtained, each hash is hashed again 500 times. After those 501 hashes, the IV is appended to the last hash obtained, and hashed again to produce the password hint.
On the other hand, the password really used to encrypt the file is obtained from the password text (or file contents, if a file is used as password) and the IV, but with a different process:
+-1000-+ password --KECCAK-512--+ | | text | v | +--> HASH + IV --KECCAK-512--> HASH --+--> encryption (64 bytes) | password file --KECCAK-512--+ contents
This ensures both that encryption password used for each file is unique per file and also that the encrypted content is different for the same file each time it is encrypted – this is so, because the IV is a random value. This way it is also not possible to know if two encrypted files with same length correspond or not to the same unencrypted content.
As password hint (64 bytes) and IV (8 bytes) are needed for decryption and password check, they must be written with the encrypted file contents.
Previous to v9.0, they we’re written at the end of the file.
From v9.0, they’re written at the very beginning of the file: this way, they can be immediately read if stdin is used, and password check can occur to verify the password and proceed -if it’s correct- with the decryption of the rest of the binary input.
So, from v9.0, triops tries to check the first 72 bytes of the file for password verification, but if it fails, it will try with the last 72 bytes of the file, as it may be a file encrypted with a previous version of triops.
Given the process of password hint generation, the possibility of a bad password checking using this double verification is negligible:
p = 2 ^ -(72*8) = 2 ^ -576 ~ 10^-174
Obviously, if both checks fail, the password is considered incorrect.
triops allows the user to not store the password hint. In this case the password hint is filled with zero bits. This 64 zero bytes are recognized as the signal not to make password check for the file. Note that the prevention about a bad password checking derived from the fact that triops checks two locations looking for IV+password hints, still applies, as the probability that 64 bytes zeroed in a row are produced as cypher text should be approximately:
p = 2 ^ -(64*8) = 2 ^ -512 ~ 10^-154
Once a file is decrypted, previous to v9.0, 72 bytes were left on disk. From v9.0 on, they are necessary overwritten as they’re the first bytes of the file. Just in case the file is smaller than 72 bytes, some of them will be left behind. These bytes were not in previous versions, and are not now, overwritten as they are considered as secure as the encrypted file contents, so no information is leaked from them, in case they could be retrieved.
There’s a Perl script for triops executable testing against random content files. See gist code.
Algorithms are based on reference implementation of CHACHA20 implemented by algorithm’s creator (D. J. Bernstein), and the implementation of KECCAK made by Thomas Pornin. Both can be found at this website:
CHACHA20 algorithm has been tested against test vectors found here:
Note that a skeleton for CHACHA20 testing is provided with triops‘ code, ready to compile as stand-alone: see chacha20/chacha20_test.c.
KECCAK algorithm has been tested against test vectors found here:
Note that a skeleton for KECCAK testing is provided with triops‘ code, ready to compile as stand-alone: see keccak/sha3_test.c.