diff --git a/crypto/stream/chacha20/crypto_stream_chacha20.go b/crypto/stream/chacha20/crypto_stream_chacha20.go new file mode 100644 index 0000000..f9ca00f --- /dev/null +++ b/crypto/stream/chacha20/crypto_stream_chacha20.go @@ -0,0 +1,87 @@ +// Package chacha20 contains the libsodium bindings for the ChaCha20 stream cipher. +package chacha20 + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_chacha20_KEYBYTES + NonceBytes = C.crypto_stream_chacha20_NONCEBYTES + MessageBytesMax = C.crypto_stream_chacha20_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStreamIC encrypts a message `m` using a nonce `n` and a secret key `k`, +// but with a block counter starting at `ic`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStreamIC(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte, ic uint64) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20_xor_ic( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (C.uint64_t)(ic), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_chacha20_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/chacha20/crypto_stream_chacha20_test.go b/crypto/stream/chacha20/crypto_stream_chacha20_test.go new file mode 100644 index 0000000..8049814 --- /dev/null +++ b/crypto/stream/chacha20/crypto_stream_chacha20_test.go @@ -0,0 +1,72 @@ +package chacha20 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestChaCha20(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Generate one with IC + XORKeyStreamIC(c, m, n, k, 0) + if !bytes.Equal(c, d) { + t.Errorf("Encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check again with IC + XORKeyStreamIC(m, m, n, k, 0) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf.go b/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf.go new file mode 100644 index 0000000..58db5c5 --- /dev/null +++ b/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf.go @@ -0,0 +1,87 @@ +// Package chacha20ietf contains the libsodium bindings for the IETF variant of the ChaCha20 stream cipher. +package chacha20ietf + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_chacha20_ietf_KEYBYTES + NonceBytes = C.crypto_stream_chacha20_ietf_NONCEBYTES + MessageBytesMax = C.crypto_stream_chacha20_ietf_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20_ietf( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20_ietf_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStreamIC encrypts a message `m` using a nonce `n` and a secret key `k`, +// but with a block counter starting at `ic`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStreamIC(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte, ic uint64) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_chacha20_ietf_xor_ic( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (C.uint32_t)(ic), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_chacha20_ietf_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf_test.go b/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf_test.go new file mode 100644 index 0000000..25af781 --- /dev/null +++ b/crypto/stream/chacha20ietf/crypto_stream_chacha20_ietf_test.go @@ -0,0 +1,72 @@ +package chacha20ietf + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestChaCha20IETF(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Generate one with IC + XORKeyStreamIC(c, m, n, k, 0) + if !bytes.Equal(c, d) { + t.Errorf("Encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check again with IC + XORKeyStreamIC(m, m, n, k, 0) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/crypto_stream.go b/crypto/stream/crypto_stream.go new file mode 100644 index 0000000..1298dae --- /dev/null +++ b/crypto/stream/crypto_stream.go @@ -0,0 +1,66 @@ +// Package stream contains the libsodium bindings for the XSalsa20 stream cipher. +package stream + +// #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() +} + +// Stream byte lengths and algorithm name +const ( + KeyBytes = C.crypto_stream_KEYBYTES // Length of a secret key + NonceBytes = C.crypto_stream_NONCEBYTES // Length of a nonce + MessageBytesMax = C.crypto_stream_MESSAGEBYTES_MAX // Maximum length of a message + Primitive = C.crypto_stream_PRIMITIVE // Name of the used algorithm +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + c := new([KeyBytes]byte) + + C.crypto_stream_keygen((*C.uchar)(&c[0])) + + return c +} diff --git a/crypto/stream/crypto_stream_test.go b/crypto/stream/crypto_stream_test.go new file mode 100644 index 0000000..71f0844 --- /dev/null +++ b/crypto/stream/crypto_stream_test.go @@ -0,0 +1,61 @@ +package stream + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestStream(t *testing.T) { + // Check the primitive + if Primitive != "xsalsa20" { + t.Errorf("Incorrect primitive: %x", Primitive) + } + + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + XORKeyStream(m, m, n, k) + if !bytes.Equal(c, m) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/salsa20/crypto_stream_salsa20.go b/crypto/stream/salsa20/crypto_stream_salsa20.go new file mode 100644 index 0000000..bedb114 --- /dev/null +++ b/crypto/stream/salsa20/crypto_stream_salsa20.go @@ -0,0 +1,87 @@ +// Package salsa20 contains the libsodium bindings for the Salsa20 stream cipher. +package salsa20 + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_salsa20_KEYBYTES + NonceBytes = C.crypto_stream_salsa20_NONCEBYTES + MessageBytesMax = C.crypto_stream_salsa20_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_salsa20( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_salsa20_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStreamIC encrypts a message `m` using a nonce `n` and a secret key `k`, +// but with a block counter starting at `ic`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStreamIC(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte, ic uint64) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_salsa20_xor_ic( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (C.uint64_t)(ic), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_salsa20_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/salsa20/crypto_stream_salsa20_test.go b/crypto/stream/salsa20/crypto_stream_salsa20_test.go new file mode 100644 index 0000000..c3a15c7 --- /dev/null +++ b/crypto/stream/salsa20/crypto_stream_salsa20_test.go @@ -0,0 +1,72 @@ +package salsa20 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestSalsa20(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Generate one with IC + XORKeyStreamIC(c, m, n, k, 0) + if !bytes.Equal(c, d) { + t.Errorf("Encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check again with IC + XORKeyStreamIC(m, m, n, k, 0) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/salsa2012/crypto_stream_salsa2012.go b/crypto/stream/salsa2012/crypto_stream_salsa2012.go new file mode 100644 index 0000000..628ee31 --- /dev/null +++ b/crypto/stream/salsa2012/crypto_stream_salsa2012.go @@ -0,0 +1,65 @@ +// Package salsa2012 contains the libsodium bindings for the Salsa20 stream cipher reduced to 12 rounds. +package salsa2012 + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_salsa2012_KEYBYTES + NonceBytes = C.crypto_stream_salsa2012_NONCEBYTES + MessageBytesMax = C.crypto_stream_salsa2012_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_salsa2012( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_salsa2012_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_salsa2012_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/salsa2012/crypto_stream_salsa2012_test.go b/crypto/stream/salsa2012/crypto_stream_salsa2012_test.go new file mode 100644 index 0000000..d41435a --- /dev/null +++ b/crypto/stream/salsa2012/crypto_stream_salsa2012_test.go @@ -0,0 +1,58 @@ +package salsa2012 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestSalsa2012(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/xchacha20/crypto_stream_xchacha20.go b/crypto/stream/xchacha20/crypto_stream_xchacha20.go new file mode 100644 index 0000000..c90c4c3 --- /dev/null +++ b/crypto/stream/xchacha20/crypto_stream_xchacha20.go @@ -0,0 +1,87 @@ +// Package xchacha20 contains the libsodium bindings for the XChaCha20 stream cipher. +package xchacha20 + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_xchacha20_KEYBYTES + NonceBytes = C.crypto_stream_xchacha20_NONCEBYTES + MessageBytesMax = C.crypto_stream_xchacha20_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_xchacha20( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_xchacha20_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStreamIC encrypts a message `m` using a nonce `n` and a secret key `k`, +// but with a block counter starting at `ic`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStreamIC(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte, ic uint64) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_xchacha20_xor_ic( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (C.uint64_t)(ic), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_xchacha20_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/xchacha20/crypto_stream_xchacha20_test.go b/crypto/stream/xchacha20/crypto_stream_xchacha20_test.go new file mode 100644 index 0000000..1326079 --- /dev/null +++ b/crypto/stream/xchacha20/crypto_stream_xchacha20_test.go @@ -0,0 +1,72 @@ +package xchacha20 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestXChaCha20(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Generate one with IC + XORKeyStreamIC(c, m, n, k, 0) + if !bytes.Equal(c, d) { + t.Errorf("Encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check again with IC + XORKeyStreamIC(m, m, n, k, 0) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/crypto/stream/xsalsa20/crypto_stream_xsalsa20.go b/crypto/stream/xsalsa20/crypto_stream_xsalsa20.go new file mode 100644 index 0000000..3e1b0d8 --- /dev/null +++ b/crypto/stream/xsalsa20/crypto_stream_xsalsa20.go @@ -0,0 +1,87 @@ +// Package xsalsa20 contains the libsodium bindings for the XSalsa20 stream cipher. +package xsalsa20 + +// #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() +} + +// Required length of secret key and nonce +const ( + KeyBytes = C.crypto_stream_xsalsa20_KEYBYTES + NonceBytes = C.crypto_stream_xsalsa20_NONCEBYTES + MessageBytesMax = C.crypto_stream_xsalsa20_MESSAGEBYTES_MAX +) + +// KeyStream fills an output buffer `c` with pseudo random bytes using a nonce `n` and a secret key `k`. +func KeyStream(c []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + + if len(c) == 0 { + return + } + + C.crypto_stream_xsalsa20( + (*C.uchar)(&c[0]), + (C.ulonglong)(len(c)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStream encrypts a message `m` using a nonce `n` and a secret key `k` and puts the resulting ciphertext into `c`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStream(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_xsalsa20_xor( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (*C.uchar)(&k[0])) +} + +// XORKeyStreamIC encrypts a message `m` using a nonce `n` and a secret key `k`, +// but with a block counter starting at `ic`. +// If `m` and `c` are the same slice, in-place encryption is performed. +func XORKeyStreamIC(c, m []byte, n *[NonceBytes]byte, k *[KeyBytes]byte, ic uint64) { + support.CheckSizeMax(m, MessageBytesMax, "message") + support.NilPanic(n == nil, "nonce") + support.NilPanic(k == nil, "key") + support.CheckSizeGreaterOrEqual(c, m, "output", "input") + + if len(c) == 0 { + return + } + + C.crypto_stream_xsalsa20_xor_ic( + (*C.uchar)(&c[0]), + (*C.uchar)(&m[0]), + (C.ulonglong)(len(m)), + (*C.uchar)(&n[0]), + (C.uint64_t)(ic), + (*C.uchar)(&k[0])) +} + +// GenerateKey generates a secret key +func GenerateKey() *[KeyBytes]byte { + k := new([KeyBytes]byte) + + C.crypto_stream_xsalsa20_keygen((*C.uchar)(&k[0])) + + return k +} diff --git a/crypto/stream/xsalsa20/crypto_stream_xsalsa20_test.go b/crypto/stream/xsalsa20/crypto_stream_xsalsa20_test.go new file mode 100644 index 0000000..f1bf7a1 --- /dev/null +++ b/crypto/stream/xsalsa20/crypto_stream_xsalsa20_test.go @@ -0,0 +1,72 @@ +package xsalsa20 + +import ( + "bytes" + "github.com/google/gofuzz" + "testing" +) + +var TestCount = 100000 + +func TestXSalsa20(t *testing.T) { + // Test the key generation + if *GenerateKey() == ([KeyBytes]byte{}) { + t.Error("Generated key is zero") + } + + // Fuzzing + fm := fuzz.New() + fn := fuzz.New().NilChance(0) + + // Run tests + for i := 0; i < TestCount; i++ { + var c, m, m2, r, d []byte + n := new([NonceBytes]byte) + + // Generate random data + fm.Fuzz(&m) + fn.Fuzz(&n) + k := GenerateKey() + + // Generate pseudo-random data + r = make([]byte, len(m)) + KeyStream(r, n, k) + + // Perform XOR + d = make([]byte, len(m)) + for i := range r { + d[i] = r[i] ^ m[i] + } + + // Generate a ciphertext + c = make([]byte, len(m)) + XORKeyStream(c, m, n, k) + if !bytes.Equal(c, d) { + t.Errorf("Encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Generate one with IC + XORKeyStreamIC(c, m, n, k, 0) + if !bytes.Equal(c, d) { + t.Errorf("Encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check if in-place encryption works + m2 = make([]byte, len(m)) + copy(m2, m) + XORKeyStream(m2, m2, n, k) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + + // Check again with IC + XORKeyStreamIC(m, m, n, k, 0) + if !bytes.Equal(c, m2) { + t.Errorf("In place encryption with IC failed for m: %x, n: %x, k: %x", m, n, k) + t.FailNow() + } + } +} diff --git a/support/support.go b/support/support.go index a3eeb90..886ab2c 100644 --- a/support/support.go +++ b/support/support.go @@ -18,7 +18,15 @@ func CheckSize(buf []byte, expected int, descrip string) { // and panics when this is not the case. func CheckSizeMin(buf []byte, min int, descrip string) { if len(buf) < min { - panic(fmt.Sprintf("Incorrect %s buffer size, expected (>%d), got (%d).", descrip, min, len(buf))) + panic(fmt.Sprintf("Incorrect %s buffer size, expected (>=%d), got (%d).", descrip, min, len(buf))) + } +} + +// CheckSizeMax checks if the length of a byte slice is less or equal than a minimum length, +// and panics when this is not the case. +func CheckSizeMax(buf []byte, max uint64, descrip string) { + if uint64(len(buf)) > max { + panic(fmt.Sprintf("Incorrect %s buffer size, expected (<=%d), got (%d).", descrip, max, len(buf))) } }