adiantum
go get lukechampine.com/adiantum
This repo contains an implementation of Adiantum, a tweakable and length-preserving
encryption cipher.
Adiantum is an instance of HBSH, an encryption mode designed for disk
encryption. In addition to being tweakable and length-preserving, HBSH is a
"super-pseudorandom permutation", meaning that changing a single bit of the
plaintext scrambles the entire ciphertext; this is in contrast to the most
common disk encryption mode, XTS, where one bitflip scrambles only 16 bytes of
the ciphertext.
HBSH is a construction, not a primitive. Specifically, it is built from a stream
cipher, a block cipher, and a hash function. The original paper provides a proof
that this construction is secure if the underlying primitives are secure.
Adiantum uses XChaCha12 for its stream cipher, AES for its block cipher, and NH
and Poly1305 for hashing. The paper also describes a closely-related instance of
HBSH called HPolyC, which is slower on large messages, but more key-agile and
simpler to implement.
This repo currently contains implementations of Adiantum and HPolyC, with 8, 12,
and 20-round variants. (12 rounds is the standard variant.) You can also
implement your own HBSH variants using the hbsh
package.
Usage
import "lukechampine.com/adiantum"
func main() {
key := make([]byte, 32) // in practice, read this from crypto/rand
cipher := adiantum.New(key)
tweak := make([]byte, 12) // can be any length
plaintext := []byte("Hello, world!")
ciphertext := cipher.Encrypt(plaintext, tweak)
recovered := cipher.Decrypt(ciphertext, tweak)
println(string(recovered)) // Hello, world!
}
To use Adiantum for disk encryption, simply set the tweak equal to the disk
sector index. For example, to encrypt n consecutive 4096-byte sectors,
increment the tweak by 1 after encrypting each sector.
It is important to understand the threat model for disk encryption.
Specifically, disk encryption is most effective when the attacker only sees one
version of the disk contents. It is less effective when the attacker can sample
the contents at will. This is because writing the same sector to the same
location will result in the same ciphertext. As such, an attacker with multiple
samples can detect if you "undo" a disk write by overwriting a sector with a
previous version of that sector. Worse, an attacker can replace a sector with a
previously-written sector, and it will decrypt just fine. See
here for a more
detailed critique of disk encryption and some recommended alternatives.
Benchmarks
Tested on an i5-7600K @ 3.80GHz. Results will likely be slower on non-amd64
architectures.
BenchmarkAdiantum/XChaCha8_Encrypt-4 1479 ns/op 2768.92 MB/s 0 allocs/op
BenchmarkAdiantum/XChaCha8_Decrypt-4 1477 ns/op 2772.76 MB/s 0 allocs/op
BenchmarkAdiantum/XChaCha12_Encrypt-4 1748 ns/op 2341.98 MB/s 0 allocs/op
BenchmarkAdiantum/XChaCha12_Decrypt-4 1748 ns/op 2342.57 MB/s 0 allocs/op
BenchmarkAdiantum/XChaCha20_Encrypt-4 2288 ns/op 1789.87 MB/s 0 allocs/op
BenchmarkAdiantum/XChaCha20_Decrypt-4 2283 ns/op 1793.88 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha8_Encrypt-4 3448 ns/op 1285.53 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha8_Decrypt-4 3437 ns/op 1289.96 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha12_Encrypt-4 3719 ns/op 1186.35 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha12_Decrypt-4 3709 ns/op 1184.61 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha20_Encrypt-4 4258 ns/op 1026.22 MB/s 0 allocs/op
BenchmarkHPolyC/XChaCha20_Decrypt-4 4245 ns/op 1028.97 MB/s 0 allocs/op