Skip to content

Database Encryption

Vyacheslav Lukianov edited this page May 3, 2020 · 8 revisions

Overview
Working with an Encrypted Database
Encrypting an Existing Database
Decrypting an Encrypted Database
Custom Cipher Implementations

Overview

From version 1.2.0, Xodus supports database encryption.

Xodus supports two algorithms out of the box: Salsa20 and ChaCha20. Salsa20 is one of the two best algorithms (along with Rabbit) submitted to the eSTREAM project in "profile 1" (Stream ciphers for software applications with high throughput requirements).

ChaCha20 is a variant of Salsa20 which tends to be even more secure while achieving the same or slightly better performance. There are several major implementations of ChaCha20, including Google's selection of ChaCha20 as a replacement for RC4 in TLS and its inclusion in OpenSSH.

Both algorithms (Salsa20 and ChaCha20) have built-in implementations provided by the Legion of the Bouncy Castle. In addition, ChaCha20 has a built-in implementation in Kotlin provided by JetBrains. It is identical to Bouncy Castle's implementation in terms of ciphering and, according to benchmark results, is 13% faster than Bouncy Castle's implementation:

Benchmark                                      Mode  Cnt    Score   Error   Units
JMHStreamCipherBenchmarks.chaChaCrypt         thrpt   12   97.439 ± 1.069  ops/us
JMHStreamCipherBenchmarks.jbChaChaCrypt       thrpt   12  110.342 ± 0.543  ops/us

You can also plug in an implementation for a custom algorithm as long as you use a symmetric stream cipher.

Working with an Encrypted Database

If you want to use either Salsa20 or ChaCha20, add a dependency for the xodus-crypto jar to your application. Otherwise, you have to provide your own cipher implementation (see Custom Cipher Implementations). When opening or creating a database, your application should configure the cipher ID, cipher key and cipher basic IV (initialization vector). Opening a database can look as follows:

final EnvironmentConfig config = new EnvironmentConfig();
// set "jetbrains.exodus.crypto.streamciphers.JBChaChaStreamCipherProvider" to use JetBrains' implementation of ChaCha20
config.setCipherId("jetbrains.exodus.crypto.streamciphers.ChaChaStreamCipherProvider");
config.setCipherKey("000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f");
config.setCipherBasicIV(314159262718281828);
try (Environment environment = Environments.newInstance("/Users/me/.myAppData", config)) {
    // ...
}

You can also pass the cipher ID, cipher key, and cipher basic IV to the application using the corresponding system properties exodus.cipherId, exodus.cipherKey, and exodus.cipherBasicIV.

The cipher ID defines the stream cipher type (algorithm). The ID is an arbitrary string, but it's recommended to use the fully qualified name of the StreamCipherProvider implementation. StreamCipherProvider is used internally to create instances of StreamCipher.

StreamCipher instances are initialized with the cipher key and cipher basic IV. The key is expected to be a hex string representing a byte array which is passed to StreamCipher.init(byte[], long). The length of the key depends on the algorithm (cipher ID). Salsa20 accepts both 128-bit and 256-bit keys, whereas ChaCha20 accepts only 256-bit keys. The basic IV is expected to be a random (pseudo-random) and unique long value. Basic IV is used to calculate relative IVs which are passed to StreamCipher.init(byte[], long).

To work with an encrypted Entity Store or Virtual File System, nothing special is needed. Just use an instance of Environment that is opened with cipher parameters as in the sample above to create an instance of PersistentEntityStore or VirtualFileSystem. Blob files will be encrypted as well as .xd files.

The cipher parameters cannot be changed during the life of the database. If your application opens a plain (unencrypted) database using cipher parameters, opens an encrypted database without cipher parameters, or opens an encrypted database with different cipher parameters, an InvalidCipherParametersException exception is thrown. The database is not affected.

Encrypting an Existing Database

You can encrypt or decrypt an existing database using the Scytale tool. Download the xodus-tools jar for version 1.2.0 or later and run:

./java -jar xodus-tools.jar scytale

You see a usage message, which is self-explanatory:

Usage: Scytale [options] source target key basicIV [cipher]
Source can be archive or folder
Cipher can be 'Salsa' or 'ChaCha', 'ChaCha' is default
Options:
  -g              use gzip compression when opening archive
  -z              make target an archive
  -o              overwrite target archive or folder

For example, to encrypt a database located at /Users/me/.myAppData using the ChaCha20 cipher and the same parameters as in the sample above, run:

./java -jar xodus-tools.jar scytale /Users/me/.myAppData /Users/me/.myAppData/encrypted 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f 314159262718281828

The encrypted database is created in /Users/me/.myAppData/encrypted.

Decrypting an Encrypted Database

The Scytale tool can also be used to decrypt an encrypted database:

./java -jar xodus-tools.jar scytale /Users/me/.myAppData/encrypted /Users/me/.myAppData 000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f 314159262718281828

The decrypted database is created in /Users/me/.myAppData. The database would not be decrypted if it is encrypted with cipher parameters different from specified. In that case, the tool would fail with InvalidCipherParametersException.

Custom Cipher Implementations

In terms of Service Provider Interfaces, custom cipher implementations are services. A custom cipher implementation should define implementations of the StreamCipherProvider abstract class and the StreamCipher interface. StreamCipherProvider is used to create instances of StreamCipher initialized with a key and IV. Any StreamCipherProvider implementation is discoverable by its ID. This ID can be an arbitrary string, but it's recommended to use the fully qualified name of the StreamCipherProvider implementation as the ID.

To plug in your custom cipher, list the fully-qualified name of the StreamCipherProvider implementation in the META-INF/services/jetbrains.exodus.crypto.StreamCipherProvider file in your application jar or any jar in its CLASSPATH. For example, the META-INF/services/jetbrains.exodus.crypto.StreamCipherProvider file in xodus-crypto.jar contains the following references:

jetbrains.exodus.crypto.streamciphers.Salsa20StreamCipherProvider
jetbrains.exodus.crypto.streamciphers.ChaChaStreamCipherProvider
jetbrains.exodus.crypto.streamciphers.JBChaChaStreamCipherProvider

So if your application depends on xodus-crypto.jar, it will be able to use Salsa20 or ChaCha20 ciphers for database encryption.