diff --git a/internal/siv_aead/benchmark.bash b/internal/siv_aead/benchmark.bash new file mode 100755 index 0000000..40b57b3 --- /dev/null +++ b/internal/siv_aead/benchmark.bash @@ -0,0 +1,7 @@ +#!/bin/bash + +set -eu + +cd "$(dirname "$0")" + +../stupidgcm/benchmark.bash diff --git a/internal/siv_aead/correctness_test.go b/internal/siv_aead/correctness_test.go new file mode 100644 index 0000000..c271970 --- /dev/null +++ b/internal/siv_aead/correctness_test.go @@ -0,0 +1,66 @@ +package siv_aead + +import ( + "bytes" + "encoding/hex" + "testing" + + "github.com/jacobsa/crypto/siv" +) + +func TestAll(t *testing.T) { + key := bytes.Repeat([]byte{1}, 32) + nonce := bytes.Repeat([]byte{2}, 16) + plaintext := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9} + aData := make([]byte, 24) + // Compare siv and siv_aead results + sResult, err := siv.Encrypt(nonce, key, plaintext, [][]byte{aData, nonce}) + if err != nil { + t.Fatal(err) + } + a := New(key) + aResult := a.Seal(nonce, nonce, plaintext, aData) + if !bytes.Equal(sResult, aResult) { + t.Errorf("siv and siv_aead produce different results") + } + expectedResult, _ := hex.DecodeString( + "02020202020202020202020202020202ad7a4010649a84d8c1dd5f752e935eed57d45b8b10008f3834") + if !bytes.Equal(aResult, expectedResult) { + t.Errorf(hex.EncodeToString(aResult)) + } + // Verify overhead + overhead := len(aResult) - len(plaintext) - len(nonce) + if overhead != a.Overhead() { + t.Errorf("Overhead() returns a wrong value") + } + // Decrypt + p1, err := a.Open(nil, aResult[:16], aResult[16:], aData) + if err != nil { + t.Error(err) + } + if !bytes.Equal(plaintext, p1) { + t.Errorf("wrong plaintext") + } + // Decrypt and append + dst := []byte{0xaa, 0xbb, 0xcc} + p2, err := a.Open(dst, aResult[:16], aResult[16:], aData) + if err != nil { + t.Error(err) + } + p2e := append(dst, plaintext...) + if !bytes.Equal(p2e, p2) { + t.Errorf("wrong plaintext: %s", hex.EncodeToString(p2)) + } + // Decrypt corrupt + aResult[17] = 0 + _, err = a.Open(nil, aResult[:16], aResult[16:], aData) + if err == nil { + t.Error("should have failed") + } + // Decrypt and append corrupt + aResult[17] = 0 + _, err = a.Open(dst, aResult[:16], aResult[16:], aData) + if err == nil { + t.Error("should have failed") + } +} diff --git a/internal/siv_aead/performance_test.go b/internal/siv_aead/performance_test.go new file mode 100644 index 0000000..626024e --- /dev/null +++ b/internal/siv_aead/performance_test.go @@ -0,0 +1 @@ +package siv_aead diff --git a/internal/siv_aead/siv_aead.go b/internal/siv_aead/siv_aead.go new file mode 100644 index 0000000..21106a5 --- /dev/null +++ b/internal/siv_aead/siv_aead.go @@ -0,0 +1,59 @@ +// Package siv_aead wraps the functions provided by siv +// in a crypto.AEAD interface. +package siv_aead + +import ( + "github.com/jacobsa/crypto/siv" +) + +type sivAead struct { + key []byte +} + +func New(key []byte) *sivAead { + return &sivAead{ + key: key, + } +} + +func (s *sivAead) NonceSize() int { + // SIV supports any nonce size, but in gocryptfs we exclusively use 16. + return 16 +} + +func (s *sivAead) Overhead() int { + // RFC5297: + // [...] the key length used by AES in CTR and S2V is len(K)/2 and will + // each be either 128 bits, 192 bits, or 256 bits. + return len(s.key) / 2 + +} + +// Seal - encrypt "in" using "nonce" and "authData" and append the result to "dst" +func (s *sivAead) Seal(dst, nonce, plaintext, authData []byte) []byte { + if len(nonce) != 16 { + // SIV supports any nonce size, but in gocryptfs we exclusively use 16. + panic("nonce must be 16 bytes long") + } + // https://github.com/jacobsa/crypto/blob/master/siv/encrypt.go#L48: + // As per RFC 5297 section 3, you may use this function for nonce-based + // authenticated encryption by passing a nonce as the last associated + // data element. + associated := [][]byte{authData, nonce} + out, err := siv.Encrypt(dst, s.key, plaintext, associated) + if err != nil { + panic(err) + } + return out +} + +// Open - decrypt "in" using "nonce" and "authData" and append the result to "dst" +func (s *sivAead) Open(dst, nonce, ciphertext, authData []byte) ([]byte, error) { + if len(nonce) != 16 { + // SIV supports any nonce size, but in gocryptfs we exclusively use 16. + panic("nonce must be 16 bytes long") + } + associated := [][]byte{authData, nonce} + dec, err := siv.Decrypt(s.key, ciphertext, associated) + return append(dst, dec...), err +}