From 95e0a77c4978310736fd787973600094c9a43223 Mon Sep 17 00:00:00 2001 From: Silke Date: Wed, 22 Mar 2017 14:30:40 +0100 Subject: [PATCH] Wrap all AEAD functions in new directory structure Includes a significant refactor: - Return error instead of integer - Replace getter functions with constants - Move prepared encryption to cipher.AEAD compatible structure - Add tests --- .../aead/aes256gcm/crypto_aead_aes256gcm.go | 138 ++++++++++++++++++ .../aes256gcm/crypto_aead_aes256gcm_test.go | 87 +++++++++++ .../crypto_aead_chacha20poly1305.go | 133 +++++++++++++++++ .../crypto_aead_chacha20poly1305_test.go | 82 +++++++++++ .../crypto_aead_chacha20poly1305_ietf.go | 133 +++++++++++++++++ .../crypto_aead_chacha20poly1305_ietf_test.go | 82 +++++++++++ crypto/aead/crypto_aead.go | 48 ++++++ crypto/aead/crypto_aead_aes256gcm.go | 132 +++++++++++++++++ crypto/aead/crypto_aead_aes256gcm_test.go | 95 ++++++++++++ .../crypto_aead_xchacha20poly1305_ietf.go | 133 +++++++++++++++++ ...crypto_aead_xchacha20poly1305_ietf_test.go | 82 +++++++++++ 11 files changed, 1145 insertions(+) create mode 100644 crypto/aead/aes256gcm/crypto_aead_aes256gcm.go create mode 100644 crypto/aead/aes256gcm/crypto_aead_aes256gcm_test.go create mode 100644 crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305.go create mode 100644 crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305_test.go create mode 100644 crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf.go create mode 100644 crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf_test.go create mode 100644 crypto/aead/crypto_aead.go create mode 100644 crypto/aead/crypto_aead_aes256gcm.go create mode 100644 crypto/aead/crypto_aead_aes256gcm_test.go create mode 100644 crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf.go create mode 100644 crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf_test.go diff --git a/crypto/aead/aes256gcm/crypto_aead_aes256gcm.go b/crypto/aead/aes256gcm/crypto_aead_aes256gcm.go new file mode 100644 index 0000000..6d612cc --- /dev/null +++ b/crypto/aead/aes256gcm/crypto_aead_aes256gcm.go @@ -0,0 +1,138 @@ +// Package aes256gcm contains the libsodium bindings for AES256-GCM. +package aes256gcm + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import "github.com/GoKillers/libsodium-go/support" + +// Sodium should always be initialised +func init() { + C.sodium_init() +} + +// Sizes of nonces, key and mac. +const ( + KeyBytes int = C.crypto_aead_aes256gcm_KEYBYTES // Size of a secret key in bytes + NSecBytes int = C.crypto_aead_aes256gcm_NSECBYTES // Size of a secret nonce in bytes + NonceBytes int = C.crypto_aead_aes256gcm_NPUBBYTES // Size of a nonce in bytes + ABytes int = C.crypto_aead_aes256gcm_ABYTES // Size of an authentication tag in bytes +) + +// Key represents a secret key +type Key [KeyBytes]byte + +// IsAvailable returns true if AES256 is available on the current CPU +func IsAvailable() bool { + return C.crypto_aead_aes256gcm_is_available() != 0 +} + +// GenerateKey generates a secret key +func GenerateKey() *Key { + k := new(Key) + C.crypto_aead_aes256gcm_keygen((*C.uchar)(&k[0])) + return k +} + +// Encrypt a message `m` with additional data `ad` using a nonce `npub` and a secret key `k`. +// A ciphertext (including authentication tag) and encryption status are returned. +func Encrypt(m, ad, nonce, k []byte) (c []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)+ABytes) + + C.crypto_aead_aes256gcm_encrypt( + (*C.uchar)(support.BytePointer(c)), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// Decrypt and verify a ciphertext `c` using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func Decrypt(c, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSizeMin(c, ABytes, "ciphertext") + + m = make([]byte, len(c)-ABytes) + + exit := C.crypto_aead_aes256gcm_decrypt( + (*C.uchar)(support.BytePointer(m)), + (*C.ulonglong)(nil), + (*C.uchar)(nil), + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} + +// EncryptDetached encrypts a message `m` with additional data `ad` using +// a nonce `npub` and a secret key `k`. +// A ciphertext, authentication tag and encryption status are returned. +func EncryptDetached(m, ad, nonce, k []byte) (c, mac []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)) + mac = make([]byte, ABytes) + + C.crypto_aead_aes256gcm_encrypt_detached( + (*C.uchar)(support.BytePointer(c)), + (*C.uchar)(&mac[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// DecryptDetached decrypts and verifies a ciphertext `c` with authentication tag `mac` +// using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func DecryptDetached(c, mac, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSize(mac, ABytes, "mac") + + m = make([]byte, len(c)) + + exit := C.crypto_aead_aes256gcm_decrypt_detached( + (*C.uchar)(support.BytePointer(m)), + (*C.uchar)(nil), + (*C.uchar)(support.BytePointer(c)), + (C.ulonglong)(len(c)), + (*C.uchar)(&mac[0]), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} diff --git a/crypto/aead/aes256gcm/crypto_aead_aes256gcm_test.go b/crypto/aead/aes256gcm/crypto_aead_aes256gcm_test.go new file mode 100644 index 0000000..4cd150f --- /dev/null +++ b/crypto/aead/aes256gcm/crypto_aead_aes256gcm_test.go @@ -0,0 +1,87 @@ +package aes256gcm + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var testCount = 100000 + +type TestData struct { + Message []byte + Ad []byte + Key Key + Nonce [NonceBytes]byte +} + +func Test(t *testing.T) { + // Skip the test if unsupported on this platform + if !IsAvailable() { + t.Skip("The CPU does not support this implementation of AES256GCM.") + } + + // Test the key generation + if *GenerateKey() == (Key{}) { + t.Error("Generated key is zero") + } + + // Test the length of NSecBytes + if NSecBytes != 0 { + t.Errorf("NSecBytes is %v but should be %v", NSecBytes, 0) + } + + // Fuzzing + f := fuzz.New() + + // Run tests + for i := 0; i < testCount; i++ { + var c, m, ec, mac []byte + var err error + var test TestData + + // Fuzz the test struct + f.Fuzz(&test) + + // Detached encryption test + c, mac = EncryptDetached(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + + // Encryption test + ec = Encrypt(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + if !bytes.Equal(ec, append(c, mac...)) { + t.Errorf("Encryption failed for %+v", test) + t.FailNow() + } + + // Detached decryption test + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Detached decryption failed for %+v", test) + t.FailNow() + } + + // Decryption test + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Decryption failed for %+v", test) + t.FailNow() + } + + // Failed detached decryption test + mac = make([]byte, ABytes) + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Detached decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + + // Failed decryption test + copy(ec[len(m):], mac) + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + } + t.Logf("Completed %v tests", testCount) +} diff --git a/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305.go b/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305.go new file mode 100644 index 0000000..e1f570e --- /dev/null +++ b/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305.go @@ -0,0 +1,133 @@ +// Package chacha20poly1305 contains the libsodium bindings for ChaCha20-Poly1305. +package chacha20poly1305 + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import "github.com/GoKillers/libsodium-go/support" + +// Sodium should always be initialised +func init() { + C.sodium_init() +} + +// Sizes of nonces, key and mac. +const ( + KeyBytes int = C.crypto_aead_chacha20poly1305_KEYBYTES // Size of a secret key in bytes + NSecBytes int = C.crypto_aead_chacha20poly1305_NSECBYTES // Size of a secret nonce in bytes + NonceBytes int = C.crypto_aead_chacha20poly1305_NPUBBYTES // Size of a nonce in bytes + ABytes int = C.crypto_aead_chacha20poly1305_ABYTES // Size of an authentication tag in bytes +) + +// Key represents a secret key +type Key [KeyBytes]byte + +// GenerateKey generates a secret key +func GenerateKey() *Key { + k := new(Key) + C.crypto_aead_chacha20poly1305_keygen((*C.uchar)(&k[0])) + return k +} + +// Encrypt a message `m` with additional data `ad` using a nonce `npub` and a secret key `k`. +// A ciphertext (including authentication tag) and encryption status are returned. +func Encrypt(m, ad, nonce, k []byte) (c []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)+ABytes) + + C.crypto_aead_chacha20poly1305_encrypt( + (*C.uchar)(support.BytePointer(c)), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// Decrypt and verify a ciphertext `c` using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func Decrypt(c, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSizeMin(c, ABytes, "ciphertext") + + m = make([]byte, len(c)-ABytes) + + exit := C.crypto_aead_chacha20poly1305_decrypt( + (*C.uchar)(support.BytePointer(m)), + (*C.ulonglong)(nil), + (*C.uchar)(nil), + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} + +// EncryptDetached encrypts a message `m` with additional data `ad` using +// a nonce `npub` and a secret key `k`. +// A ciphertext, authentication tag and encryption status are returned. +func EncryptDetached(m, ad, nonce, k []byte) (c, mac []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)) + mac = make([]byte, ABytes) + + C.crypto_aead_chacha20poly1305_encrypt_detached( + (*C.uchar)(support.BytePointer(c)), + (*C.uchar)(&mac[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// DecryptDetached decrypts and verifies a ciphertext `c` with authentication tag `mac` +// using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func DecryptDetached(c, mac, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSize(mac, ABytes, "mac") + + m = make([]byte, len(c)) + + exit := C.crypto_aead_chacha20poly1305_decrypt_detached( + (*C.uchar)(support.BytePointer(m)), + (*C.uchar)(nil), + (*C.uchar)(support.BytePointer(c)), + (C.ulonglong)(len(c)), + (*C.uchar)(&mac[0]), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} diff --git a/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305_test.go b/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305_test.go new file mode 100644 index 0000000..399ee3b --- /dev/null +++ b/crypto/aead/chacha20poly1305/crypto_aead_chacha20poly1305_test.go @@ -0,0 +1,82 @@ +package chacha20poly1305 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var testCount = 100000 + +type TestData struct { + Message []byte + Ad []byte + Key Key + Nonce [NonceBytes]byte +} + +func Test(t *testing.T) { + // Test the key generation + if *GenerateKey() == (Key{}) { + t.Error("Generated key is zero") + } + + // Test the length of NSecBytes + if NSecBytes != 0 { + t.Errorf("NSecBytes is %v but should be %v", NSecBytes, 0) + } + + // Fuzzing + f := fuzz.New() + + // Run tests + for i := 0; i < testCount; i++ { + var c, m, ec, mac []byte + var err error + var test TestData + + // Fuzz the test struct + f.Fuzz(&test) + + // Detached encryption test + c, mac = EncryptDetached(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + + // Encryption test + ec = Encrypt(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + if !bytes.Equal(ec, append(c, mac...)) { + t.Errorf("Encryption failed for %+v", test) + t.FailNow() + } + + // Detached decryption test + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Detached decryption failed for %+v", test) + t.FailNow() + } + + // Decryption test + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Decryption failed for %+v", test) + t.FailNow() + } + + // Failed detached decryption test + mac = make([]byte, ABytes) + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Detached decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + + // Failed decryption test + copy(ec[len(m):], mac) + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + } + t.Logf("Completed %v tests", testCount) +} diff --git a/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf.go b/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf.go new file mode 100644 index 0000000..03af6cd --- /dev/null +++ b/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf.go @@ -0,0 +1,133 @@ +// Package chacha20poly1305ietf contains the libsodium bindings for the IETF variant of ChaCha20-Poly1305. +package chacha20poly1305ietf + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import "github.com/GoKillers/libsodium-go/support" + +// Sodium should always be initialised +func init() { + C.sodium_init() +} + +// Sizes of nonces, key and mac. +const ( + KeyBytes int = C.crypto_aead_chacha20poly1305_ietf_KEYBYTES // Size of a secret key in bytes + NSecBytes int = C.crypto_aead_chacha20poly1305_ietf_NSECBYTES // Size of a secret nonce in bytes + NonceBytes int = C.crypto_aead_chacha20poly1305_ietf_NPUBBYTES // Size of a nonce in bytes + ABytes int = C.crypto_aead_chacha20poly1305_ietf_ABYTES // Size of an authentication tag in bytes +) + +// Key represents a secret key +type Key [KeyBytes]byte + +// GenerateKey generates a secret key +func GenerateKey() *Key { + k := new(Key) + C.crypto_aead_chacha20poly1305_ietf_keygen((*C.uchar)(&k[0])) + return k +} + +// Encrypt a message `m` with additional data `ad` using a nonce `npub` and a secret key `k`. +// A ciphertext (including authentication tag) and encryption status are returned. +func Encrypt(m, ad, nonce, k []byte) (c []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)+ABytes) + + C.crypto_aead_chacha20poly1305_ietf_encrypt( + (*C.uchar)(support.BytePointer(c)), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// Decrypt and verify a ciphertext `c` using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func Decrypt(c, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSizeMin(c, ABytes, "ciphertext") + + m = make([]byte, len(c)-ABytes) + + exit := C.crypto_aead_chacha20poly1305_ietf_decrypt( + (*C.uchar)(support.BytePointer(m)), + (*C.ulonglong)(nil), + (*C.uchar)(nil), + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} + +// EncryptDetached encrypts a message `m` with additional data `ad` using +// a nonce `npub` and a secret key `k`. +// A ciphertext, authentication tag and encryption status are returned. +func EncryptDetached(m, ad, nonce, k []byte) (c, mac []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)) + mac = make([]byte, ABytes) + + C.crypto_aead_chacha20poly1305_ietf_encrypt_detached( + (*C.uchar)(support.BytePointer(c)), + (*C.uchar)(&mac[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// DecryptDetached decrypts and verifies a ciphertext `c` with authentication tag `mac` +// using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func DecryptDetached(c, mac, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSize(mac, ABytes, "mac") + + m = make([]byte, len(c)) + + exit := C.crypto_aead_chacha20poly1305_ietf_decrypt_detached( + (*C.uchar)(support.BytePointer(m)), + (*C.uchar)(nil), + (*C.uchar)(support.BytePointer(c)), + (C.ulonglong)(len(c)), + (*C.uchar)(&mac[0]), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} diff --git a/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf_test.go b/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf_test.go new file mode 100644 index 0000000..c4da073 --- /dev/null +++ b/crypto/aead/chacha20poly1305ietf/crypto_aead_chacha20poly1305_ietf_test.go @@ -0,0 +1,82 @@ +package chacha20poly1305ietf + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var testCount = 100000 + +type TestData struct { + Message []byte + Ad []byte + Key Key + Nonce [NonceBytes]byte +} + +func Test(t *testing.T) { + // Test the key generation + if *GenerateKey() == (Key{}) { + t.Error("Generated key is zero") + } + + // Test the length of NSecBytes + if NSecBytes != 0 { + t.Errorf("NSecBytes is %v but should be %v", NSecBytes, 0) + } + + // Fuzzing + f := fuzz.New() + + // Run tests + for i := 0; i < testCount; i++ { + var c, m, ec, mac []byte + var err error + var test TestData + + // Fuzz the test struct + f.Fuzz(&test) + + // Detached encryption test + c, mac = EncryptDetached(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + + // Encryption test + ec = Encrypt(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + if !bytes.Equal(ec, append(c, mac...)) { + t.Errorf("Encryption failed for %+v", test) + t.FailNow() + } + + // Detached decryption test + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Detached decryption failed for %+v", test) + t.FailNow() + } + + // Decryption test + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Decryption failed for %+v", test) + t.FailNow() + } + + // Failed detached decryption test + mac = make([]byte, ABytes) + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Detached decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + + // Failed decryption test + copy(ec[len(m):], mac) + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + } + t.Logf("Completed %v tests", testCount) +} diff --git a/crypto/aead/crypto_aead.go b/crypto/aead/crypto_aead.go new file mode 100644 index 0000000..bdff214 --- /dev/null +++ b/crypto/aead/crypto_aead.go @@ -0,0 +1,48 @@ +// Package aead contains bindings for authenticated encryption with additional data. +package aead + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import "crypto/cipher" + +// Sodium should always be initialised +func init() { + C.sodium_init() +} + +// AEAD is and extended version of cipher.AEAD +type AEAD interface { + cipher.AEAD + + // SealDetached encrypts and authenticates plaintext, authenticates the + // additional data and appends the result to dst, returning the updated + // slice and the authentication code (mac) separately. + // The nonce must be NonceSize() bytes long and unique for all time, for a given key. + // The mac is Overhead() bytes long. + // + // The plaintext and dst may alias exactly or not at all. To reuse + // plaintext's storage for the encrypted output, use plaintext[:0] as dst. + SealDetached(dst, nonce, plaintext, additionalData []byte) ([]byte, []byte) + + // OpenDetached decrypts a ciphertext, authenticates the additional data using + // the autentication code (mac) and, if successful, appends the resulting plaintext + // to dst, returning the updated slice. The nonce must be NonceSize() + // bytes long and both it and the additional data must match the + // value passed to Seal. + // + // The ciphertext and dst may alias exactly or not at all. To reuse + // ciphertext's storage for the decrypted output, use ciphertext[:0] as dst. + // + // Even if the function fails, the contents of dst, up to its capacity, + // may be overwritten. + OpenDetached(dst, nonce, ciphertext, mac, additionalData []byte) ([]byte, error) +} + +// appendSlices appends a slice with a number of empty bytes and +// returns the new slice and a slice pointing to the empty data. +func appendSlices(in []byte, n int) ([]byte, []byte) { + slice := append(in, make([]byte, n)...) + return slice, slice[len(in):] +} diff --git a/crypto/aead/crypto_aead_aes256gcm.go b/crypto/aead/crypto_aead_aes256gcm.go new file mode 100644 index 0000000..3e853de --- /dev/null +++ b/crypto/aead/crypto_aead_aes256gcm.go @@ -0,0 +1,132 @@ +package aead + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import ( + "github.com/GoKillers/libsodium-go/crypto/aead/aes256gcm" + "github.com/GoKillers/libsodium-go/support" +) + +// AES256GCM represents the cryptographic state for the AES256 GCM cipher +type AES256GCM C.crypto_aead_aes256gcm_state + +// NewAES256GCM returns a AES256GCM cipher for an AES256 key. +func NewAES256GCM(k *aes256gcm.Key) AEAD { + support.NilPanic(k == nil, "key") + + ctx := new(AES256GCM) + + C.crypto_aead_aes256gcm_beforenm( + (*C.crypto_aead_aes256gcm_state)(ctx), + (*C.uchar)(&k[0])) + + return ctx +} + +// NonceSize returns the size of the nonce for Seal() and Open() +func (a *AES256GCM) NonceSize() int { + return aes256gcm.NonceBytes +} + +// Overhead returns the size of the MAC overhead for Seal() and Open() +func (a *AES256GCM) Overhead() int { + return aes256gcm.ABytes +} + +// Seal encrypts plaintext using nonce and additional data and appends it to a destination. +// See aead.AEAD for details. +func (a *AES256GCM) Seal(dst, nonce, plaintext, additionalData []byte) (ret []byte) { + support.CheckSize(nonce, a.NonceSize(), "nonce") + + ret, c := appendSlices(dst, len(plaintext)+a.Overhead()) + + C.crypto_aead_aes256gcm_encrypt_afternm( + (*C.uchar)(&c[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(plaintext)), + (C.ulonglong)(len(plaintext)), + (*C.uchar)(support.BytePointer(additionalData)), + (C.ulonglong)(len(additionalData)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.crypto_aead_aes256gcm_state)(a)) + + return +} + +// Open decrypts a ciphertext using a nonce and additional data and appends the result to a destination. +// See aead.AEAD for details. +func (a *AES256GCM) Open(dst, nonce, ciphertext, additionalData []byte) (ret []byte, err error) { + support.CheckSize(nonce, a.NonceSize(), "nonce") + support.CheckSizeMin(ciphertext, a.Overhead(), "ciphertext") + + ret, m := appendSlices(dst, len(ciphertext)-a.Overhead()) + + exit := C.crypto_aead_aes256gcm_decrypt_afternm( + (*C.uchar)(support.BytePointer(m)), + (*C.ulonglong)(nil), + (*C.uchar)(nil), + (*C.uchar)(&ciphertext[0]), + (C.ulonglong)(len(ciphertext)), + (*C.uchar)(support.BytePointer(additionalData)), + (C.ulonglong)(len(additionalData)), + (*C.uchar)(&nonce[0]), + (*C.crypto_aead_aes256gcm_state)(a)) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} + +// SealDetached encrypts plaintext using nonce and additional data and appends it to a destination. +// See aead.AEAD for details. +func (a *AES256GCM) SealDetached(dst, nonce, plaintext, additionalData []byte) (ret, mac []byte) { + support.CheckSize(nonce, a.NonceSize(), "nonce") + + ret, c := appendSlices(dst, len(plaintext)) + mac = make([]byte, a.Overhead()) + + C.crypto_aead_aes256gcm_encrypt_detached_afternm( + (*C.uchar)(support.BytePointer(c)), + (*C.uchar)(&mac[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(plaintext)), + (C.ulonglong)(len(plaintext)), + (*C.uchar)(support.BytePointer(additionalData)), + (C.ulonglong)(len(additionalData)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.crypto_aead_aes256gcm_state)(a)) + + return +} + +// OpenDetached decrypts a ciphertext using a nonce, mac and additional data and appends the result to a destination. +// See aead.AEAD for details. +func (a *AES256GCM) OpenDetached(dst, nonce, ciphertext, mac, additionalData []byte) (ret []byte, err error) { + support.CheckSize(nonce, a.NonceSize(), "nonce") + support.CheckSize(mac, a.Overhead(), "mac") + + ret, m := appendSlices(dst, len(ciphertext)) + + exit := C.crypto_aead_aes256gcm_decrypt_detached_afternm( + (*C.uchar)(support.BytePointer(m)), + (*C.uchar)(nil), + (*C.uchar)(support.BytePointer(ciphertext)), + (C.ulonglong)(len(ciphertext)), + (*C.uchar)(&mac[0]), + (*C.uchar)(support.BytePointer(additionalData)), + (C.ulonglong)(len(additionalData)), + (*C.uchar)(&nonce[0]), + (*C.crypto_aead_aes256gcm_state)(a)) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} diff --git a/crypto/aead/crypto_aead_aes256gcm_test.go b/crypto/aead/crypto_aead_aes256gcm_test.go new file mode 100644 index 0000000..1686a04 --- /dev/null +++ b/crypto/aead/crypto_aead_aes256gcm_test.go @@ -0,0 +1,95 @@ +package aead + +import ( + "bytes" + "github.com/GoKillers/libsodium-go/crypto/aead/aes256gcm" + "github.com/google/gofuzz" + "testing" +) + +var testCount = 100000 + +type TestData struct { + Message []byte + Ad []byte + Dst []byte + Key aes256gcm.Key + Nonce [aes256gcm.NonceBytes]byte +} + +func Test(t *testing.T) { + // Skip the test if unsupported on this platform + if !aes256gcm.IsAvailable() { + t.Skip("The CPU does not support this implementation of AES256GCM.") + } + + // Fuzzing + f := fuzz.New() + + // Run tests + for i := 0; i < testCount; i++ { + var c, m, ec, mac []byte + var err error + var test TestData + + // Fuzz the test struct + f.Fuzz(&test) + + // Create a key context + ctx := NewAES256GCM(&test.Key) + + // Detached encryption test + c, mac = ctx.SealDetached(test.Dst, test.Nonce[:], test.Message, test.Ad) + + // Check if dst was prepended + if !bytes.Equal(c[:len(test.Dst)], test.Dst) { + t.Error("dst was not prepended") + t.FailNow() + } + + // Encryption test + ec = ctx.Seal(test.Dst, test.Nonce[:], test.Message, test.Ad) + if !bytes.Equal(ec, append(c, mac...)) { + t.Errorf("Encryption failed for %+v", test) + t.FailNow() + } + + // Detached decryption test + m, err = ctx.OpenDetached(test.Dst, test.Nonce[:], c[len(test.Dst):], mac, test.Ad) + if err != nil || !bytes.Equal(m[len(test.Dst):], test.Message) { + t.Errorf("Detached decryption failed for %+v", test) + t.FailNow() + } + + // Check if dst was prepended + if !bytes.Equal(m[:len(test.Dst)], test.Dst) { + t.Error("dst was not prepended") + t.FailNow() + } + + // Decryption test + m, err = ctx.Open(test.Dst, test.Nonce[:], ec[len(test.Dst):], test.Ad) + if err != nil || !bytes.Equal(m[len(test.Dst):], test.Message) { + t.Errorf("Decryption failed for %+v", test) + t.FailNow() + } + + // Failed detached decryption test + mac = make([]byte, ctx.Overhead()) + m, err = ctx.OpenDetached(test.Dst, test.Nonce[:], c[len(test.Dst):], mac, test.Ad) + if err == nil { + t.Errorf("Detached decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + + // Failed decryption test + copy(ec[len(test.Dst)+len(m):], mac) + m, err = ctx.Open(test.Dst, test.Nonce[:], ec[len(test.Dst):], test.Ad) + if err == nil { + t.Errorf("Decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + } + + t.Logf("Completed %v tests", testCount) +} diff --git a/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf.go b/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf.go new file mode 100644 index 0000000..17984ae --- /dev/null +++ b/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf.go @@ -0,0 +1,133 @@ +// Package xchacha20poly1305ietf contains the libsodium bindings for the IETF variant of XChaCha20-Poly1305. +package xchacha20poly1305ietf + +// #cgo pkg-config: libsodium +// #include +// #include +import "C" +import "github.com/GoKillers/libsodium-go/support" + +// Sodium should always be initialised +func init() { + C.sodium_init() +} + +// Sizes of nonces, key and mac. +const ( + KeyBytes int = C.crypto_aead_xchacha20poly1305_ietf_KEYBYTES // Size of a secret key in bytes + NSecBytes int = C.crypto_aead_xchacha20poly1305_ietf_NSECBYTES // Size of a secret nonce in bytes + NonceBytes int = C.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES // Size of a nonce in bytes + ABytes int = C.crypto_aead_xchacha20poly1305_ietf_ABYTES // Size of an authentication tag in bytes +) + +// Key represents a secret key +type Key [KeyBytes]byte + +// GenerateKey generates a secret key +func GenerateKey() *Key { + k := new(Key) + C.crypto_aead_xchacha20poly1305_ietf_keygen((*C.uchar)(&k[0])) + return k +} + +// Encrypt a message `m` with additional data `ad` using a nonce `npub` and a secret key `k`. +// A ciphertext (including authentication tag) and encryption status are returned. +func Encrypt(m, ad, nonce, k []byte) (c []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)+ABytes) + + C.crypto_aead_xchacha20poly1305_ietf_encrypt( + (*C.uchar)(support.BytePointer(c)), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// Decrypt and verify a ciphertext `c` using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func Decrypt(c, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSizeMin(c, ABytes, "ciphertext") + + m = make([]byte, len(c)-ABytes) + + exit := C.crypto_aead_xchacha20poly1305_ietf_decrypt( + (*C.uchar)(support.BytePointer(m)), + (*C.ulonglong)(nil), + (*C.uchar)(nil), + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} + +// EncryptDetached encrypts a message `m` with additional data `ad` using +// a nonce `npub` and a secret key `k`. +// A ciphertext, authentication tag and encryption status are returned. +func EncryptDetached(m, ad, nonce, k []byte) (c, mac []byte) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + + c = make([]byte, len(m)) + mac = make([]byte, ABytes) + + C.crypto_aead_xchacha20poly1305_ietf_encrypt_detached( + (*C.uchar)(support.BytePointer(c)), + (*C.uchar)(&mac[0]), + (*C.ulonglong)(nil), + (*C.uchar)(support.BytePointer(m)), + (C.ulonglong)(len(m)), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(nil), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + return +} + +// DecryptDetached decrypts and verifies a ciphertext `c` with authentication tag `mac` +// using additional data `ad`, nonce `npub` and secret key `k`. +// Returns the decrypted message and verification status. +func DecryptDetached(c, mac, ad, nonce, k []byte) (m []byte, err error) { + support.CheckSize(k, KeyBytes, "secret key") + support.CheckSize(nonce, NonceBytes, "public nonce") + support.CheckSize(mac, ABytes, "mac") + + m = make([]byte, len(c)) + + exit := C.crypto_aead_xchacha20poly1305_ietf_decrypt_detached( + (*C.uchar)(support.BytePointer(m)), + (*C.uchar)(nil), + (*C.uchar)(support.BytePointer(c)), + (C.ulonglong)(len(c)), + (*C.uchar)(&mac[0]), + (*C.uchar)(support.BytePointer(ad)), + (C.ulonglong)(len(ad)), + (*C.uchar)(&nonce[0]), + (*C.uchar)(&k[0])) + + if exit != 0 { + err = &support.VerificationError{} + } + + return +} diff --git a/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf_test.go b/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf_test.go new file mode 100644 index 0000000..e87dae2 --- /dev/null +++ b/crypto/aead/xchacha20poly1305ietf/crypto_aead_xchacha20poly1305_ietf_test.go @@ -0,0 +1,82 @@ +package xchacha20poly1305ietf + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var testCount = 100000 + +type TestData struct { + Message []byte + Ad []byte + Key Key + Nonce [NonceBytes]byte +} + +func Test(t *testing.T) { + // Test the key generation + if *GenerateKey() == (Key{}) { + t.Error("Generated key is zero") + } + + // Test the length of NSecBytes + if NSecBytes != 0 { + t.Errorf("NSecBytes is %v but should be %v", NSecBytes, 0) + } + + // Fuzzing + f := fuzz.New() + + // Run tests + for i := 0; i < testCount; i++ { + var c, m, ec, mac []byte + var err error + var test TestData + + // Fuzz the test struct + f.Fuzz(&test) + + // Detached encryption test + c, mac = EncryptDetached(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + + // Encryption test + ec = Encrypt(test.Message, test.Ad, test.Nonce[:], test.Key[:]) + if !bytes.Equal(ec, append(c, mac...)) { + t.Errorf("Encryption failed for %+v", test) + t.FailNow() + } + + // Detached decryption test + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Detached decryption failed for %+v", test) + t.FailNow() + } + + // Decryption test + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err != nil || !bytes.Equal(m, test.Message) { + t.Errorf("Decryption failed for %+v", test) + t.FailNow() + } + + // Failed detached decryption test + mac = make([]byte, ABytes) + m, err = DecryptDetached(c, mac, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Detached decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + + // Failed decryption test + copy(ec[len(m):], mac) + m, err = Decrypt(ec, test.Ad, test.Nonce[:], test.Key[:]) + if err == nil { + t.Errorf("Decryption unexpectedly succeeded for %+v", test) + t.FailNow() + } + } + t.Logf("Completed %v tests", testCount) +}