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.
Installation
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.
Usage
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).
tag([123,106,248,34,91,112,112,177,112,249,212,139,56,161,61,198]).
salt([238,75,19,242,29,184,153,173,252,111,211,66,211,36,161,187]).
ciphertext("\
0u1B8eEKuxhEJyhgziMxSC+OW0oY9kwEvPUROPRaY4AOJ+zra0IgKeZ/9GRdqNppqMSlT/ae\
1KwqvhK/Opep3aeZotJIR3XngFXg2fOfT9odqMvLxFtJLW7l8cXCxyfPJWKH6WRm3ktqRqXy\
+i2n/pFsB49XmfcKgt1/vifW+eWHe02zwRisvaBdCr/frwMBSzdAiEvZXeFkrYWbWFMVbq4D\
8d90I/U38W2EvG/zCsptkWbUe0O5wx7gj3QBZFF7sD59hJTXUPpyWsDFgY2leU4H87dcyqAs\
SSDegxbBZMjYtnhPhVrlbg8yWXrEF6tovdl51uExmISIcsVTsXy+Pr5GqHMJRELK/hRtwdE+\
etc.
").
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:
- enscryerypt-encrypt-file
- enscryerypt-encrypt-buffer
- enscryerypt-decrypt-file
- enscryerypt-decrypt-buffer
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:
SPC | scroll-up-command |
backspace | scroll-down-command |
q | erase buffer and close it immediately |
e | switch 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:
cost(19).
tag([27,56,238,135,182,31,117,110,223,205,71,186,99,137,211,136]).
salt([81,81,158,43,51,38,46,182,81,231,191,103,41,211,46,21]).
ciphertext("\
Dm1G2he8mVWpQXn8cPBJ1UEgRgfOJmDY1zeAuuDtjBEydblPSZM8raeJiKCcQGAyIWoKPWOK\
eDRkXdaM5vWCDY6JvyoKUe0X1hcjWnpJVxq2VIuGjZpJkqj+IEZ0wnPCO9BwI51jxA+L/mue\
OnJ5awhMsU1FhqW8TBPjAh1VbjTqvM6QXyDv/3Zao8VeXCayt2K45yKL68JiClnAqY7GCq6D\
CZOEl7iuKYVffoDSxRsJN2rtg+ADn88LE7Q0LA==").
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'),
cost(Cost),
salt(Salt)]),
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, []) },
portray_clause_(cost(Cost)),
portray_clause_(tag(Ts)),
portray_clause_(salt(Salt)),
"ciphertext(\"\\\n",
string_lines(B64, 72),
"\").\n".
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:
portray_clause_(cost(Cost)),
portray_clause_(tag(Ts)),
portray_clause_(salt(Salt)),
"ciphertext(\"\\\n",
string_lines(B64, 72),
"\").\n".
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) } ->
First,
"\\\n",
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