elasticdog / transcrypt

transparently encrypt files within a git repository
MIT License
1.43k stars 102 forks source link

# [RFC] Implement Authenticated Encryption using libsodium #113

Open tychota opened 3 years ago

tychota commented 3 years ago

[RFC] Use libsodium instead of OpenSSL

Summary

This RFC conceptualise the implementation and migration of libsodium instead of OpenSSL to power transcrypt. It will increase security and usability at a minor cost of installation ease and while supporting a lot of platforms.

Motivation

Expected outcome is to have better security and usability, while still supporting a wide range of platforms and being easy to install

Malleability issue

The main motivation is the malleability issues described in https://github.com/elasticdog/transcrypt#cipher-selection

Indeed aes-256-cbc is a cipher without authentication which means that transcrypt won't crash if the key is wrong or undefined. Today in one of our project we spend a few hours debugging an issue with a new CI were the password wasn't set and transcrypt was doing. (This issue happened twice for me in two years so I think it is worth considering it.)

transcrypt -c aes-256-cbc -p 'undefined'

Ultimately this should not have happened and the step were transcrypt was executed should have failed with proper authentication.

Open SSL platform variability

As specified in the readme, the different platforms have very different version of OpenSSL.

I didn't find a marketshare of openssl version but I tend to agree that it is hard to move forward with new openSSL algorithms, as old platform may not support them.

Guide-level explanation

Phase 1

With the new transcrypt people will be able to execute the same worklow than today.

transcrypt
transcrypt -c aes-256-cbc -p 'the-new-password'

but they would get a hint in CLI to opt in and use the new backend "modern"

transcrypt -b modern
transcrypt -b modern -p 'the-new-password'

When using this modern backend this will add a step that check for a CLI wrapper to libsodium. Eg:

We can start by just implementing python backend, as it is widely stared, support old python version, and python itself as a really great platform support, is preinstalled in almost every UNIX system

Phase 2

Now three would be a depreciation warning. People can use the provided utility to migrate to new backend

transcrypt -m modern

New project are done with backend modern but can use

transcrypt -b legacy
transcrypt -b legacy -c aes-256-cbc -p 'the-new-password'

to use old backend

Phase 3

(Probably in a very long time)

We remove support for legacy backend.

Reference-level explanation

We will use libsodium secretbox constructions.

(Maybe we should use the file specific operation to work on a stream: libsodium secretstream)

How to chose between secretbox and secretstream.

image

Key derivation function

Libsodium already implement keygen function so manual steps are not needed. We just need to use the high level equivalent for:

unsigned char key[crypto_secretbox_KEYBYTES];
crypto_secretbox_keygen(key);

eg in pynacl

import nacl.secret
import nacl.utils

# This must be kept secret, this is the combination to your safe
key = nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)

and store it.

Encryption and authentication

(Note the algorithm provides encryption and authentication in the same time, not encrypt then mac).

The algorithm is:

Encryption: XSalsa20 stream cipher
Authentication: Poly1305 MAC

It is:

We just need to use the high level equivalent for:

#define MESSAGE ((const unsigned char *) "test")
#define MESSAGE_LEN 4
#define CIPHERTEXT_LEN (crypto_secretbox_MACBYTES + MESSAGE_LEN)

unsigned char nonce[crypto_secretbox_NONCEBYTES];
unsigned char ciphertext[CIPHERTEXT_LEN];

randombytes_buf(nonce, sizeof nonce);
crypto_secretbox_easy(ciphertext, MESSAGE, MESSAGE_LEN, nonce, key);

eg in pynacl

import nacl.secret

key = "previously define key"

# This is your safe, you can use it to encrypt or decrypt messages
box = nacl.secret.SecretBox(key)

# This is our message to send, it must be a bytestring as SecretBox will
#   treat it as just a binary blob of data.
message = b"test"

# Encrypt our message, it will be exactly 40 bytes longer than the
#   original message as it stores authentication information and the
#   nonce alongside it.
encrypted = box.encrypt(message)

to cipher

and

# Decrypt our message, an exception will be raised if the encryption was
#   tampered with or there was otherwise an error.
plaintext = box.decrypt(encrypted)
print(plaintext.decode('utf-8'))

to decrypt.

Drawbacks

Rationale and alternatives

Chacah20Poly1305 looks a compelling cipher to use. It increases the security by preventing malleability attacks. It is standard, and high security. Two alternatives are:

References

tychota commented 3 years ago

Hello there.

Thank for the library. It is widely used at https://www.bam.tech/bam-agence-experte-design-et-d%C3%A9veloppement-mobile I opened this RFC as I was encountering malleability issues and lost a bunch of time today.

I'm proposing some design change for the lib, as I notice the subject isn't present in other issue.

I'm open for discussion and I will be updating the RFC as discussion arise.