From 61fe552ac492ccbff62391ca0842d641475630be Mon Sep 17 00:00:00 2001 From: Milos Gajdos Date: Mon, 25 Nov 2019 14:58:12 +0000 Subject: [PATCH] First commit: Outline of tunnel encryption code --- tunnel/crypto.go | 72 +++++++++++++++++++++++++++++++++++++++++++ tunnel/crypto_test.go | 41 ++++++++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 tunnel/crypto.go create mode 100644 tunnel/crypto_test.go diff --git a/tunnel/crypto.go b/tunnel/crypto.go new file mode 100644 index 00000000..89a0bdbf --- /dev/null +++ b/tunnel/crypto.go @@ -0,0 +1,72 @@ +package tunnel + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/sha256" + "io" +) + +// Encrypt encrypts data and returns encrypted payload +func Encrypt(data []byte, key string) ([]byte, error) { + // generate a new AES cipher using our 32 byte key + c, err := aes.NewCipher(hash(key)) + if err != nil { + return nil, err + } + + // gcm or Galois/Counter Mode, is a mode of operation + // for symmetric key cryptographic block ciphers + // - https://en.wikipedia.org/wiki/Galois/Counter_Mode + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + // create a new byte array the size of the nonce + // NOTE: we might use smaller nonce size in the future + nonce := make([]byte, gcm.NonceSize()) + if _, err = io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + // NOTE: we prepend the nonce to the payload + // we need to do this as we need the same nonce + // to decrypt the payload when receiving it + return gcm.Seal(nonce, nonce, data, nil), nil +} + +// Decrypt decrypts the payload and returns decrypted data +func Decrypt(data []byte, key string) ([]byte, error) { + // generate AES cipher for decrypting the message + c, err := aes.NewCipher(hash(key)) + if err != nil { + return nil, err + } + + // we use GCM to encrypt the payload + gcm, err := cipher.NewGCM(c) + if err != nil { + return nil, err + } + + nonceSize := gcm.NonceSize() + // NOTE: we need to parse out nonce from the payload + // we prepend the nonce to every encrypted payload + nonce, ciphertext := data[:nonceSize], data[nonceSize:] + plaintext, err := gcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} + +// hash hahes the data into 32 bytes key and returns it +// hash uses sha256 to hash the passed in string. +func hash(key string) []byte { + hasher := sha256.New() + hasher.Write([]byte(key)) + return hasher.Sum(nil) +} diff --git a/tunnel/crypto_test.go b/tunnel/crypto_test.go new file mode 100644 index 00000000..6f8498b9 --- /dev/null +++ b/tunnel/crypto_test.go @@ -0,0 +1,41 @@ +package tunnel + +import ( + "bytes" + "testing" +) + +func TestEncrypt(t *testing.T) { + key := "tokenpassphrase" + data := []byte("supersecret") + + cipherText, err := Encrypt(data, key) + if err != nil { + t.Errorf("failed to encrypt data: %v", err) + } + + // verify the cipherText is not the same as data + if bytes.Equal(data, cipherText) { + t.Error("encrypted data are the same as plaintext") + } +} + +func TestDecrypt(t *testing.T) { + key := "tokenpassphrase" + data := []byte("supersecret") + + cipherText, err := Encrypt(data, key) + if err != nil { + t.Errorf("failed to encrypt data: %v", err) + } + + plainText, err := Decrypt(cipherText, key) + if err != nil { + t.Errorf("failed to decrypt data: %v", err) + } + + // verify the plainText is the same as data + if !bytes.Equal(data, plainText) { + t.Error("decrypted data not the same as plaintext") + } +}