Enscryerypt — Encrypt and decrypt files with Scryer Prolog

Enscryerypt lets you encrypt and decrypt files with a password, using state-of-the-art cryptographic algorithms.


Prolog source file: enscryerypt.pl

Load the file with:
$ scryer-prolog --no-add-history enscryerypt.pl
You need Scryer Prolog version v0.9.0-23-gdfbdaa9 or later. I recommend to install the latest git version.

The --no-add-history switch is added for improved security: This switch prevents logging of the interaction history in ~/.scryer_history, and therefore eliminates the risk of saving a password to this file.


Use encrypt_file/1 to encrypt a file. For example:
?- encrypt_file("enscryerypt.pl").
Enter a desired password and press RET. The password is not shown. The encrypted content appears on standard output.

The encrypted content is emitted as ASCII text, and automatically broken into lines that have at most 75 characters each. This is done so that you can also easily print the output on a sheet of paper for safe storage.

For example, I get:
?- encrypt_file("enscryerypt.pl").
Password: [I enter a password, and press RET] cost(19).
Save the output to a file.

Use decrypt_file/1 analogously to decrypt a file.

Emacs integration

Using enscryerypt.pl from the toplevel is error prone, and you may accidentally omit parts of the output when copying it to a file. For instance, in the above interaction, cost(19). is part of the output, emitted directly after the preceding output, and easy to overlook.

It is much more convenient, and much less error-prone, to use enscryerypt.pl from inside Emacs.

Emacs Lisp definitions: enscryerypt.el

Copy enscryerypt.el and enscryerypt.pl to the same directory, open enscryerypt.el in Emacs (using C-x C-f), and evaluate the definitions with M-x eval-buffer RET.

enscryerypt.el gives you 4 commands to encrypt and decrypt files and buffer contents, respectively: For example, try encrypting a buffer with M-x enscryerypt-encrypt-buffer RET. After you enter a password, the encrypted text appears in a new buffer called enscryerypt-encrypted. As long as nobody can guess your password, it is safe to store this encrypted content anywhere you want, and also to print it out. The only realistic way to decrypt the buffer is for someone to figure out the password you used. Use a long password to render brute force attempts to guess the password infeasible.

Use M-x enscryerypt-decrypt-buffer RET to decrypt a buffer that contains encrypted content: After you enter the same password you used for encryption, the decrypted content appears in a new buffer called enscryerypt-decrypted. The buffer uses a custom mode (decrypted-mode) with the following keybindings:

qerase buffer and close it immediately
eswitch to fundamental-mode to edit the buffer

In decrypted-mode, a red background is used to indicate the "danger" inherent to showing decrypted plaintext on screen. Also, the buffer is not editable in decrypted-mode (press e to switch to fundamental-mode and edit the buffer), and you should not save it to any file if you want the content to remain secret, because it is very hard to reliably delete all traces of stored data from storage media such as hard disks and SSDs.

The buffers enscryerypt-decrypted and enscryerypt-encrypted are the only user-visible buffers that enscryerypt.el ever changes, all other buffers remain unchanged.

enscryerypt.el uses pipes to interact with the underlying Prolog process, so as to avoid writing secret content to disk. Note that secret content may still appear on disk even if Enscryerypt does not write it, for example in swap files generated by the operating system.

You can load enscryerypt.el from your Emacs configuration file with load, for example:
(load "~/enscryerypt/enscryerypt.el")
The files enscryerypt.pl and enscryerypt.el must be in the same directory for this to work, or enscryerypt-prolog-file must be configured to point to the Prolog source file.

Sample decryption

Here is an encrypted text that you can use to verify that you have set up everything correctly:
You can also download it as a file: message.enc

The password I used is: where the raven scry

If you place this content in an Emacs buffer, do M-x enscryerypt-decrypt-buffer RET, enter the password and confirm it with RET, you should see the decrypted text in a new buffer enscryerypt-decrypted.

You will see a message indicating that encryption has failed if you use a wrong password or do not copy the text correctly.

Cryptographic considerations

The most important definition in enscryerypt.pl is the relation between a plaintext, a password, and the resulting encrypted content. This relation is defined by the DCG nonterminal encrypt_string_//2, which I repeat here for inspection and discussion:
encrypt_string_(String, Password) -->
        { Cost = 19,
          crypto_n_random_bytes(16, Salt),
          crypto_password_hash(Password, Hash, [algorithm('pbkdf2-sha512'),
          crypto_data_hkdf(Hash, 32, Ks, [info("key"),algorithm(sha256)]),
          crypto_data_hkdf(Hash, 12, IV, [info("iv"),algorithm(sha256)]),
          crypto_data_encrypt(String, 'chacha20-poly1305', Ks, IV,
                              Encrypted, [tag(Ts),encoding(octet)]),
          chars_base64(Encrypted, B64, []) },
        string_lines(B64, 72),
The predicate crypto_n_random_bytes/2 is used to compute 16 cryptographically secure random bytes that act as a so-called salt. The salt will be used to ensure that even if you use a password repeatedly, the encrypted content will in all likelihood look different every time you perform an encryption. There will, in such extreme likelihood that it amounts to certainty in practice, be nothing in the encrypted content that lets anybody deduce from it any information about the password you used, or any single bit of the plaintext. Even if you reuse a password and encrypt the same plaintext repeatedly with the same password, this added randomness ensures that it will be infeasible for an attacker to recognize that you reused a password, or to deduce any information about the content, not even the fact that the plaintexts are identical.

With crypto_password_hash/3, a hash is derived from the specified password and the obtained salt. The cost/1 parameter is specified so as to enforce 219 = 524288 iterations of the pbkdf2-sha512 algorithm for password-based key-derivation. This number of iterations is used to make the derivation slow: An attacker that tries to guess the password by brute force will have to perform the same number of iterations for every tried password in order to derive the resulting hash.

We use crypto_data_hkdf/3 to derive the key and initialization vector (IV) from the computed hash. These parameters are used to encrypt the plain text, using the ChaCha20 stream cipher. This very efficient and elegant encryption method is considered to be very secure. It is combined with the Poly1305 authenticator so that any changes in the encrypted text are reliably detected, causing a subsequent decryption attempt to fail entirely and in a way that does not reveal any additional information about the password, the used key or the plaintext under any circumstance.

The resulting encrypted text is a list of characters whose Unicode code points denote the value of the byte at the respective position. Using chars_base64/3, this list of characters is Base64-encoded to make it amenable to printing on paper, sending it as ASCII text, manual transcription, reading it aloud over an audio connection, Morse code transmission etc. The resulting tag Ts ensures integrity and authenticity of the encrypted content, and it must be specified for successful decryption. The tag need not be kept secret, and is represented as a list of bytes, i.e., integers between 0 and 255. An attacker that modifies the encrypted content must make a corresponding, fitting change in the tag to make a subsequent decryption successful. Such a change is extremely improbable without knowledge of the password or key.

Finally, a concatenation of DCG nonterminals specifies the list of characters described by encrypt_string_//2:
        string_lines(B64, 72),
Hence, the described result comprises the used Cost parameter, the randomly chosen salt, the computed tag, and finally the Base64-encoded encrypted text, all specified as Prolog facts that allow convenient processing and reasoning. The Base64-encoded text is broken into individual lines that each contain at most 72 characters, using the DCG nonterminal string_lines//2:
string_lines(String, Split) -->
        { length(First, Split) },
        (   { append(First, Remainder, String) } ->
            string_lines(Remainder, Split)
        ;   String
It is safe to share the described text in any way: Knowledge of all these parameters is not considered helpful to deduce any properties of the plain text, the used key or the password. If there is any cryptographic fault in the encryption mechanism of enscryerypt.pl, it must be in one of the snippets shown above or in the underlying cryptographic primitives.

More about Prolog: The Power of Prolog

In particular: Cryptography

Main page