├── .travis.yml ├── go.test.sh ├── Attribution ├── LICENSE ├── encerrors └── errors.go ├── generate ├── generate.go └── generate_test.go ├── ssh ├── ssh_test.go └── ssh.go ├── aes ├── cfb │ ├── cfb_test.go │ └── cfb.go ├── ctr │ ├── ctr_test.go │ └── ctr.go ├── cbc │ ├── cbc_test.go │ └── cbc.go └── gcm │ ├── gcm_test.go │ └── gcm.go ├── nacl ├── nacl_test.go └── nacl.go ├── enc_test.go ├── README.md └── enc.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.8 5 | 6 | before_install: 7 | - go get -t -v ./... 8 | 9 | script: 10 | - ./go.test.sh 11 | 12 | after_success: 13 | - bash <(curl -s https://codecov.io/bash) -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -coverprofile=profile.out $d 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done -------------------------------------------------------------------------------- /Attribution: -------------------------------------------------------------------------------- 1 | http://stackoverflow.com/questions/21151714/go-generate-an-ssh-public-key 2 | https://golang.org/pkg/crypto/cipher/ 3 | https://github.com/hashicorp/memberlist/blob/master/security.go 4 | 5 | Much of this work was taken from Practical Cryptography with Go by Kyle Isom 6 | https://leanpub.com/gocrypto/read#leanpub-auto-aes-cbc -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Christopher Cooper 2 | Copyright (c) 2013 Kyle Isom 3 | 4 | Permission to use, copy, modify, and distribute this software for any 5 | purpose with or without fee is hereby granted, provided that the above 6 | copyright notice and this permission notice appear in all copies. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -------------------------------------------------------------------------------- /encerrors/errors.go: -------------------------------------------------------------------------------- 1 | package encerrors 2 | 3 | import "errors" 4 | 5 | var ( 6 | // ErrInvalidKeyLength occurs when a key has been used with an invalid length 7 | ErrInvalidKeyLength = errors.New("goenc: invalid key length") 8 | // ErrInvalidMessageLength occurs when a message doesn't match the expected block size, etc 9 | ErrInvalidMessageLength = errors.New("goenc: invalid message length") 10 | // ErrInvalidSum occurs when a MAC checksum doesn't match 11 | ErrInvalidSum = errors.New("goenc: invalid checksum") 12 | // ErrInvalidPadding occurs when a key/message has been padded improperly 13 | ErrInvalidPadding = errors.New("goenc: invalid badding") 14 | // ErrInvalidMessageID occurs when a message may have been replayed 15 | ErrInvalidMessageID = errors.New("goenc: invalid message id, message may have been replayed") 16 | // ErrInvalidCipherKind occurs when an invalid cipher is selected 17 | ErrInvalidCipherKind = errors.New("goenc: invalid cipher kind") 18 | // ErrNoPadProvided occurs when no pad is given to NaCL 19 | ErrNoPadProvided = errors.New("goenc: no pad provided, this kind of cipher requires a pad") 20 | ) 21 | -------------------------------------------------------------------------------- /generate/generate.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "crypto/rand" 5 | "io" 6 | ) 7 | 8 | const ( 9 | // KeySize is the default key size for most types of encryption 10 | KeySize = 32 11 | // NonceSize is the default NonceSize 12 | NonceSize = 24 13 | // GCMNonceSize is the default gcmnonce size 14 | GCMNonceSize = 12 15 | ) 16 | 17 | // Key creates a new random secret key. 18 | func Key() (*[KeySize]byte, error) { 19 | key := new([KeySize]byte) 20 | _, err := io.ReadFull(rand.Reader, key[:]) 21 | return key, err 22 | } 23 | 24 | // Nonce creates a new random nonce. 25 | func Nonce() (*[NonceSize]byte, error) { 26 | nonce := new([NonceSize]byte) 27 | _, err := io.ReadFull(rand.Reader, nonce[:]) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | return nonce, nil 33 | } 34 | 35 | // GCMNonce returns a nonce with a GCMNonceSize 36 | func GCMNonce() (*[GCMNonceSize]byte, error) { 37 | nonce := new([GCMNonceSize]byte) 38 | _, err := io.ReadFull(rand.Reader, nonce[:]) 39 | if err != nil { 40 | return nil, err 41 | } 42 | return nonce, nil 43 | } 44 | 45 | // RandBytes returns a slice of random bytes of the size given 46 | func RandBytes(size int) ([]byte, error) { 47 | buf := make([]byte, size) 48 | _, err := io.ReadFull(rand.Reader, buf) 49 | return buf, err 50 | } 51 | 52 | // RandString returns a random string with the given length 53 | func RandString(length int) (string, error) { 54 | b, err := RandBytes(length) 55 | return string(b), err 56 | } 57 | -------------------------------------------------------------------------------- /generate/generate_test.go: -------------------------------------------------------------------------------- 1 | package generate 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | . "github.com/smartystreets/goconvey/convey" 8 | ) 9 | 10 | func TestKey(t *testing.T) { 11 | Convey("We can get a random key", t, func() { 12 | k, err := Key() 13 | So(err, ShouldBeNil) 14 | So(len(k[:]), ShouldEqual, KeySize) 15 | k2, err := Key() 16 | So(err, ShouldBeNil) 17 | So(len(k2[:]), ShouldEqual, KeySize) 18 | So(bytes.Equal(k[:], k2[:]), ShouldBeFalse) 19 | }) 20 | } 21 | 22 | func TestNonce(t *testing.T) { 23 | Convey("We can get a random nonce", t, func() { 24 | n, err := Nonce() 25 | So(err, ShouldBeNil) 26 | So(len(n[:]), ShouldEqual, NonceSize) 27 | n2, err := Nonce() 28 | So(err, ShouldBeNil) 29 | So(len(n2[:]), ShouldEqual, NonceSize) 30 | So(bytes.Equal(n[:], n2[:]), ShouldBeFalse) 31 | }) 32 | } 33 | 34 | func TestGCMNonce(t *testing.T) { 35 | Convey("We can get a random nonce", t, func() { 36 | n, err := GCMNonce() 37 | So(err, ShouldBeNil) 38 | So(len(n[:]), ShouldEqual, GCMNonceSize) 39 | n2, err := GCMNonce() 40 | So(err, ShouldBeNil) 41 | So(len(n2[:]), ShouldEqual, GCMNonceSize) 42 | So(bytes.Equal(n[:], n2[:]), ShouldBeFalse) 43 | }) 44 | } 45 | 46 | func TestRandBytes(t *testing.T) { 47 | Convey("We can get random bytes", t, func() { 48 | b, err := RandBytes(KeySize) 49 | So(err, ShouldBeNil) 50 | b2, err := RandBytes(KeySize) 51 | So(err, ShouldBeNil) 52 | So(bytes.Equal(b, b2), ShouldBeFalse) 53 | }) 54 | } 55 | 56 | func TestRandString(t *testing.T) { 57 | Convey("We can get a random string", t, func() { 58 | s, err := RandString(3) 59 | So(err, ShouldBeNil) 60 | s2, err := RandString(3) 61 | So(err, ShouldBeNil) 62 | So(s, ShouldNotEqual, s2) 63 | }) 64 | } 65 | -------------------------------------------------------------------------------- /ssh/ssh_test.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "testing" 5 | 6 | "golang.org/x/crypto/ssh" 7 | 8 | "io/ioutil" 9 | 10 | "os" 11 | 12 | "path/filepath" 13 | "runtime" 14 | 15 | . "github.com/smartystreets/goconvey/convey" 16 | ) 17 | 18 | func TestSSHKeyPair(t *testing.T) { 19 | Convey("We can get private and public ssh key pair bytes", t, func() { 20 | 21 | privBytes, pubBytes, err := PrivateAndPublicKeyBytes(RSA1024) 22 | So(err, ShouldBeNil) 23 | 24 | _, err = ssh.ParsePrivateKey(privBytes) 25 | So(err, ShouldBeNil) 26 | 27 | _, err = ssh.ParsePublicKey(pubBytes) 28 | So(err, ShouldBeNil) 29 | 30 | privBytes, pubBytes, err = LocalKeyPair(RSA1024) 31 | So(err, ShouldBeNil) 32 | 33 | _, err = ssh.ParsePrivateKey(privBytes) 34 | So(err, ShouldBeNil) 35 | 36 | _, _, _, _, err = ssh.ParseAuthorizedKey(pubBytes) 37 | So(err, ShouldBeNil) 38 | }) 39 | } 40 | 41 | func TestSaveNewKeyPair(t *testing.T) { 42 | Convey("We can generate and save keys to local files", t, func() { 43 | var tempDir = "/tmp" 44 | if runtime.GOOS == "windows" { 45 | userProfile := os.Getenv("USERPROFILE") 46 | tempDir = filepath.Join(userProfile, "AppData", "Local", "Temp") 47 | } 48 | 49 | dir, err := ioutil.TempDir(tempDir, "") 50 | So(err, ShouldBeNil) 51 | 52 | t1, err := ioutil.TempFile(dir, "") 53 | So(err, ShouldBeNil) 54 | t2, err := ioutil.TempFile(dir, "") 55 | So(err, ShouldBeNil) 56 | 57 | err = SaveNewKeyPair(t1.Name(), t2.Name(), RSA1024) 58 | So(err, ShouldBeNil) 59 | 60 | _, _, _, _, err = ReadLocalPublicKey(t2.Name()) 61 | So(err, ShouldBeNil) 62 | 63 | err = t1.Close() 64 | So(err, ShouldBeNil) 65 | err = t2.Close() 66 | So(err, ShouldBeNil) 67 | 68 | err = os.RemoveAll(dir) 69 | So(err, ShouldBeNil) 70 | }) 71 | } 72 | 73 | func TestErrors(t *testing.T) { 74 | Convey("We can get errors when we should", t, func() { 75 | _, _, err := LocalKeyPair(1) 76 | So(err, ShouldNotBeNil) 77 | _, _, err = PrivateAndPublicKeyBytes(1) 78 | So(err, ShouldNotBeNil) 79 | 80 | err = SaveNewKeyPair("", "", 1) 81 | So(err, ShouldNotBeNil) 82 | 83 | err = SaveNewKeyPair("", "", RSA1024) 84 | So(err, ShouldNotBeNil) 85 | 86 | _, _, _, _, err = ReadLocalPublicKey("") 87 | So(err, ShouldNotBeNil) 88 | }) 89 | } 90 | -------------------------------------------------------------------------------- /aes/cfb/cfb_test.go: -------------------------------------------------------------------------------- 1 | package cfb 2 | 3 | import ( 4 | "math/rand" 5 | "testing" 6 | 7 | "bytes" 8 | 9 | "github.com/alistanis/goenc/encerrors" 10 | "github.com/alistanis/goenc/generate" 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | func TestEncryptDecrypt(t *testing.T) { 15 | Convey("We can test encrypting and decrpyting bytes and strings", t, func() { 16 | s := "this is a test string to encrypt" 17 | key := make([]byte, 32) 18 | _, err := rand.Read(key) 19 | So(err, ShouldBeNil) 20 | 21 | es, err := EncryptString(string(key), s) 22 | So(err, ShouldBeNil) 23 | So(s, ShouldNotEqual, es) 24 | 25 | ds, err := DecryptString(string(key), es) 26 | So(err, ShouldBeNil) 27 | So(ds, ShouldEqual, s) 28 | }) 29 | 30 | } 31 | 32 | var ( 33 | text = "this is some text to encrypt" 34 | ) 35 | 36 | func TestEncrypt(t *testing.T) { 37 | Convey("We can encrypt and decrypt messages using a key", t, func() { 38 | k, err := generate.Key() 39 | So(err, ShouldBeNil) 40 | key := k[:] 41 | data, err := Encrypt(key, []byte(text)) 42 | So(err, ShouldBeNil) 43 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 44 | 45 | data, err = Decrypt(key, data) 46 | So(err, ShouldBeNil) 47 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 48 | }) 49 | } 50 | 51 | func TestCipher_Encrypt(t *testing.T) { 52 | Convey("We can encrypt and decrypt with the cipher struct", t, func() { 53 | c := New() 54 | So(c.KeySize(), ShouldEqual, KeySize) 55 | k, err := generate.Key() 56 | So(err, ShouldBeNil) 57 | key := k[:] 58 | data, err := c.Encrypt(key, []byte(text)) 59 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 60 | 61 | data, err = c.Decrypt(key, data) 62 | So(err, ShouldBeNil) 63 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 64 | }) 65 | } 66 | 67 | func TestErrors(t *testing.T) { 68 | Convey("We can get the appropriate errors", t, func() { 69 | key, err := generate.RandBytes(30) 70 | So(err, ShouldBeNil) 71 | _, err = Encrypt(key, []byte{}) 72 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 73 | _, err = Decrypt(key, []byte{}) 74 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 75 | 76 | k, err := generate.Key() 77 | So(err, ShouldBeNil) 78 | key = k[:] 79 | 80 | _, err = Decrypt(key, []byte{}) 81 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 82 | 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /aes/ctr/ctr_test.go: -------------------------------------------------------------------------------- 1 | package ctr 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "github.com/alistanis/goenc/encerrors" 8 | "github.com/alistanis/goenc/generate" 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | var ( 13 | text = "this is some text to encrypt" 14 | ) 15 | 16 | func TestEncrypt(t *testing.T) { 17 | Convey("We can encrypt and decrypt messages using a key", t, func() { 18 | k, err := Key() 19 | So(err, ShouldBeNil) 20 | key := k[:] 21 | data, err := Encrypt(key, []byte(text)) 22 | So(err, ShouldBeNil) 23 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 24 | 25 | data, err = Decrypt(key, data) 26 | So(err, ShouldBeNil) 27 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 28 | }) 29 | } 30 | 31 | func TestCipher_Encrypt(t *testing.T) { 32 | Convey("We can encrypt and decrypt messages using a cipher struct", t, func() { 33 | c := New() 34 | So(c.KeySize(), ShouldEqual, KeySize) 35 | k, err := Key() 36 | So(err, ShouldBeNil) 37 | key := k[:] 38 | data, err := c.Encrypt(key, []byte(text)) 39 | So(err, ShouldBeNil) 40 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 41 | 42 | data, err = c.Decrypt(key, data) 43 | So(err, ShouldBeNil) 44 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 45 | }) 46 | } 47 | 48 | func TestErrors(t *testing.T) { 49 | Convey("We can get the appropriate errors", t, func() { 50 | key, err := generate.RandBytes(30) 51 | So(err, ShouldBeNil) 52 | _, err = Encrypt(key, []byte{}) 53 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 54 | _, err = Decrypt(key, []byte{}) 55 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 56 | 57 | k, err := generate.Key() 58 | So(err, ShouldBeNil) 59 | key = k[:] 60 | 61 | _, err = Decrypt(key, []byte{}) 62 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 63 | k2, err := Key() 64 | So(err, ShouldBeNil) 65 | key = k2[:] 66 | _, err = Decrypt(key, []byte{}) 67 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 68 | data, err := generate.RandBytes(NonceSize + MACSize) 69 | So(err, ShouldBeNil) 70 | _, err = Decrypt(key, data) 71 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 72 | 73 | data, err = generate.RandBytes(NonceSize + MACSize + 1) 74 | So(err, ShouldBeNil) 75 | _, err = Decrypt(key, data) 76 | So(err, ShouldEqual, encerrors.ErrInvalidSum) 77 | }) 78 | } 79 | -------------------------------------------------------------------------------- /nacl/nacl_test.go: -------------------------------------------------------------------------------- 1 | package nacl 2 | 3 | import ( 4 | "bytes" 5 | "math/rand" 6 | "testing" 7 | 8 | "github.com/alistanis/goenc/generate" 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | func TestEncryptDecrypt(t *testing.T) { 13 | Convey("We can test encrypting and decrypting bytes using secretbox (NaCL)", t, func() { 14 | pad := make([]byte, 32) 15 | _, err := rand.Read(pad) 16 | So(err, ShouldBeNil) 17 | 18 | b := []byte("This is a message we'd like to encrypt") 19 | k := []byte("super weak key") 20 | 21 | out, err := Encrypt(pad, k, b) 22 | So(err, ShouldBeNil) 23 | So(bytes.Equal(b, out), ShouldBeFalse) 24 | 25 | msg, err := Decrypt(pad, k, out) 26 | So(err, ShouldBeNil) 27 | 28 | So(bytes.Equal(b, msg), ShouldBeTrue) 29 | 30 | pad, out, err = RandomPadEncrypt(k, b) 31 | So(bytes.Equal(b, out), ShouldBeFalse) 32 | msg, err = Decrypt(pad, k, out) 33 | So(err, ShouldBeNil) 34 | So(bytes.Equal(b, msg), ShouldBeTrue) 35 | }) 36 | } 37 | 38 | func TestCipher_Encrypt(t *testing.T) { 39 | Convey("We can test encrypting and decrypting with the Cipher struct", t, func() { 40 | 41 | pad := make([]byte, 32) 42 | _, err := rand.Read(pad) 43 | So(err, ShouldBeNil) 44 | c := &Cipher{Pad: pad} 45 | So(c.KeySize(), ShouldEqual, 32) 46 | b := []byte("This is a message we'd like to encrypt") 47 | k := []byte("super weak key") 48 | 49 | out, err := c.Encrypt(k, b) 50 | So(err, ShouldBeNil) 51 | So(bytes.Equal(b, out), ShouldBeFalse) 52 | 53 | msg, err := c.Decrypt(k, out) 54 | So(err, ShouldBeNil) 55 | 56 | So(bytes.Equal(b, msg), ShouldBeTrue) 57 | 58 | pad, out, err = RandomPadEncrypt(k, b) 59 | So(bytes.Equal(b, out), ShouldBeFalse) 60 | msg, err = Decrypt(pad, k, out) 61 | So(err, ShouldBeNil) 62 | So(bytes.Equal(b, msg), ShouldBeTrue) 63 | }) 64 | } 65 | 66 | func TestErrors(t *testing.T) { 67 | Convey("We can get the appropriate errors", t, func() { 68 | pad := []byte{} 69 | _, err := Encrypt(pad, nil, nil) 70 | So(err, ShouldNotBeNil) 71 | 72 | b := []byte("This is a message we'd like to encrypt") 73 | k := []byte("super weak key") 74 | 75 | pad, err = generate.RandBytes(32) 76 | So(err, ShouldBeNil) 77 | data, err := Encrypt(pad, k, b) 78 | So(err, ShouldBeNil) 79 | 80 | _, err = Decrypt(pad, []byte("Bad key"), data) 81 | So(err.Error(), ShouldEqual, "Decryption failed") 82 | 83 | }) 84 | } 85 | -------------------------------------------------------------------------------- /ssh/ssh.go: -------------------------------------------------------------------------------- 1 | // Package ssh supports generation of key pairs in different formats with as few parameters as possible 2 | package ssh 3 | 4 | import ( 5 | "crypto/rand" 6 | "crypto/rsa" 7 | 8 | "bytes" 9 | "crypto/x509" 10 | "encoding/pem" 11 | 12 | "io/ioutil" 13 | 14 | "golang.org/x/crypto/ssh" 15 | ) 16 | 17 | const ( 18 | // RSA1024 is 1024 bits (should only be used for testing) 19 | RSA1024 = 1 << (10 + iota) 20 | // RSA2048 is 2048 bits 21 | RSA2048 22 | // RSA4096 is 4096 bits 23 | RSA4096 24 | ) 25 | 26 | // LocalKeyPair returns bits formatted for a local ssh key pair (id_rsa, id_rsa.pub - AuthorizedKey format) 27 | func LocalKeyPair(bits int) (private, public []byte, err error) { 28 | privateKey, err := rsa.GenerateKey(rand.Reader, bits) 29 | if err != nil { 30 | return nil, nil, err 31 | } 32 | buf := bytes.NewBuffer([]byte{}) 33 | privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} 34 | if err := pem.Encode(buf, privateKeyPEM); err != nil { 35 | return nil, nil, err 36 | } 37 | pub, err := ssh.NewPublicKey(&privateKey.PublicKey) 38 | if err != nil { 39 | return nil, nil, err 40 | } 41 | return buf.Bytes(), ssh.MarshalAuthorizedKey(pub), nil 42 | } 43 | 44 | // PrivateAndPublicKeyBytes takes a privateKey and returns the private and public key bytes, the public key bytes 45 | // are in the wire format protocol 46 | func PrivateAndPublicKeyBytes(bits int) ([]byte, []byte, error) { 47 | privateKey, err := rsa.GenerateKey(rand.Reader, bits) 48 | if err != nil { 49 | return nil, nil, err 50 | } 51 | buf := bytes.NewBuffer([]byte{}) 52 | privateKeyPEM := &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(privateKey)} 53 | if err := pem.Encode(buf, privateKeyPEM); err != nil { 54 | return nil, nil, err 55 | } 56 | 57 | pub, err := ssh.NewPublicKey(&privateKey.PublicKey) 58 | if err != nil { 59 | return nil, nil, err 60 | } 61 | return buf.Bytes(), pub.Marshal(), err 62 | } 63 | 64 | // SaveNewKeyPair generates a new key and saves private and public keys to a local path with the given bit 65 | func SaveNewKeyPair(privPath, pubPath string, bits int) error { 66 | priv, pub, err := LocalKeyPair(bits) 67 | if err != nil { 68 | return err 69 | } 70 | 71 | err = ioutil.WriteFile(privPath, priv, 0600) 72 | if err != nil { 73 | return err 74 | } 75 | return ioutil.WriteFile(pubPath, pub, 0644) 76 | } 77 | 78 | // ReadLocalPublicKey reads a local path for a public key stored in the AuthorizedKeys format 79 | func ReadLocalPublicKey(pubPath string) (out ssh.PublicKey, comment string, options []string, rest []byte, err error) { 80 | data, err := ioutil.ReadFile(pubPath) 81 | if err != nil { 82 | return nil, "", nil, nil, err 83 | } 84 | return ssh.ParseAuthorizedKey(data) 85 | } 86 | -------------------------------------------------------------------------------- /aes/cbc/cbc_test.go: -------------------------------------------------------------------------------- 1 | package cbc 2 | 3 | import ( 4 | "bytes" 5 | "testing" 6 | 7 | "crypto/aes" 8 | 9 | "github.com/alistanis/goenc/encerrors" 10 | "github.com/alistanis/goenc/generate" 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | var ( 15 | text = "this is some text to encrypt" 16 | ) 17 | 18 | func TestEncrypt(t *testing.T) { 19 | Convey("We can encrypt and decrypt messages using a key", t, func() { 20 | k, err := Key() 21 | So(err, ShouldBeNil) 22 | key := k[:] 23 | data, err := Encrypt(key, []byte(text)) 24 | So(err, ShouldBeNil) 25 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 26 | 27 | data, err = Decrypt(key, data) 28 | So(err, ShouldBeNil) 29 | 30 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 31 | }) 32 | } 33 | 34 | func TestCipher_Encrypt(t *testing.T) { 35 | Convey("We can encrypt using the Cipher struct", t, func() { 36 | c := New() 37 | So(c.KeySize(), ShouldEqual, 64) 38 | k, err := Key() 39 | So(err, ShouldBeNil) 40 | key := k[:] 41 | data, err := c.Encrypt(key, []byte(text)) 42 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 43 | 44 | data, err = c.Decrypt(key, data) 45 | So(err, ShouldBeNil) 46 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 47 | }) 48 | } 49 | 50 | func TestErrors(t *testing.T) { 51 | Convey("We can get the appropriate errors when failing certain conditions", t, func() { 52 | Convey("Unpad:", func() { 53 | b := unpad([]byte{}) 54 | So(b, ShouldBeNil) 55 | 56 | b, err := generate.RandBytes(17) 57 | So(err, ShouldBeNil) 58 | b = unpad(b) 59 | So(b, ShouldBeNil) 60 | 61 | b, err = generate.RandBytes(16) 62 | So(err, ShouldBeNil) 63 | b = unpad(b) 64 | So(b, ShouldBeNil) 65 | 66 | b = []byte{0} 67 | b = unpad(b) 68 | So(b, ShouldBeNil) 69 | }) 70 | 71 | Convey("Encrypt:", func() { 72 | k, err := generate.RandBytes(40) 73 | So(err, ShouldBeNil) 74 | _, err = Encrypt(k, []byte{}) 75 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 76 | }) 77 | 78 | Convey("Decrypt:", func() { 79 | k, err := generate.RandBytes(40) 80 | So(err, ShouldBeNil) 81 | _, err = Decrypt(k, []byte{}) 82 | So(err, ShouldEqual, encerrors.ErrInvalidKeyLength) 83 | ciphertext, err := generate.RandBytes(aes.BlockSize + 1) 84 | So(err, ShouldBeNil) 85 | k, err = generate.RandBytes(KeySize) 86 | So(err, ShouldBeNil) 87 | _, err = Decrypt(k, ciphertext) 88 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 89 | 90 | ciphertext, err = generate.RandBytes(aes.BlockSize) 91 | So(err, ShouldBeNil) 92 | _, err = Decrypt(k, ciphertext) 93 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 94 | 95 | ciphertext, err = generate.RandBytes(aes.BlockSize * 4) 96 | So(err, ShouldBeNil) 97 | _, err = Decrypt(k, ciphertext) 98 | So(err, ShouldEqual, encerrors.ErrInvalidSum) 99 | }) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /aes/ctr/ctr.go: -------------------------------------------------------------------------------- 1 | // Package ctr supports ctr encryption - this implementation is authenticated with crypto/hmac using sha256 2 | package ctr 3 | 4 | // https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29 5 | 6 | import ( 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "crypto/hmac" 10 | "crypto/rand" 11 | "crypto/sha256" 12 | 13 | "io" 14 | 15 | "github.com/alistanis/goenc/encerrors" 16 | "github.com/alistanis/goenc/generate" 17 | ) 18 | 19 | const ( 20 | // NonceSize to use for nonces 21 | NonceSize = aes.BlockSize 22 | // MACSize is the output size of HMAC-SHA-256 23 | MACSize = 32 24 | // CKeySize - Cipher key size - AES-256 25 | CKeySize = 32 26 | // MKeySize - HMAC key size - HMAC-SHA-256 27 | MKeySize = 32 28 | // KeySize to use for keys, 64 bytes 29 | KeySize = CKeySize + MKeySize 30 | ) 31 | 32 | // Cipher to implement the BlockCipher interface 33 | type Cipher struct { 34 | } 35 | 36 | // New returns a new ctr cipher 37 | func New() *Cipher { 38 | return &Cipher{} 39 | } 40 | 41 | // Encrypt implements the BlockCipher interface 42 | func (c *Cipher) Encrypt(key, plaintext []byte) ([]byte, error) { 43 | return Encrypt(key, plaintext) 44 | } 45 | 46 | // Decrypt implements the BlockCipher interface 47 | func (c *Cipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 48 | return Decrypt(key, ciphertext) 49 | } 50 | 51 | // KeySize implements the BlockCipher interface 52 | func (c *Cipher) KeySize() int { 53 | return KeySize 54 | } 55 | 56 | // Key returns a pointer to an array of bytes with the given KeySize 57 | func Key() (*[KeySize]byte, error) { 58 | key := new([KeySize]byte) 59 | _, err := io.ReadFull(rand.Reader, key[:]) 60 | return key, err 61 | } 62 | 63 | // Encrypt encrypts plaintext using the given key with CTR encryption 64 | func Encrypt(key, plaintext []byte) ([]byte, error) { 65 | if len(key) != KeySize { 66 | return nil, encerrors.ErrInvalidKeyLength 67 | } 68 | 69 | nonce, err := generate.RandBytes(NonceSize) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | ct := make([]byte, len(plaintext)) 75 | 76 | // NewCipher only returns an error with an invalid key size, 77 | // but the key size was checked at the beginning of the function. 78 | c, _ := aes.NewCipher(key[:CKeySize]) 79 | ctr := cipher.NewCTR(c, nonce) 80 | ctr.XORKeyStream(ct, plaintext) 81 | 82 | h := hmac.New(sha256.New, key[CKeySize:]) 83 | ct = append(nonce, ct...) 84 | h.Write(ct) 85 | ct = h.Sum(ct) 86 | return ct, nil 87 | } 88 | 89 | // Decrypt decrypts ciphertext using the given key 90 | func Decrypt(key, ciphertext []byte) ([]byte, error) { 91 | if len(key) != KeySize { 92 | return nil, encerrors.ErrInvalidKeyLength 93 | } 94 | 95 | if len(ciphertext) <= (NonceSize + MACSize) { 96 | return nil, encerrors.ErrInvalidMessageLength 97 | } 98 | 99 | macStart := len(ciphertext) - MACSize 100 | tag := ciphertext[macStart:] 101 | out := make([]byte, macStart-NonceSize) 102 | ciphertext = ciphertext[:macStart] 103 | 104 | h := hmac.New(sha256.New, key[CKeySize:]) 105 | h.Write(ciphertext) 106 | mac := h.Sum(nil) 107 | if !hmac.Equal(mac, tag) { 108 | return nil, encerrors.ErrInvalidSum 109 | } 110 | 111 | c, _ := aes.NewCipher(key[:CKeySize]) 112 | ctr := cipher.NewCTR(c, ciphertext[:NonceSize]) 113 | ctr.XORKeyStream(out, ciphertext[NonceSize:]) 114 | return out, nil 115 | } 116 | -------------------------------------------------------------------------------- /nacl/nacl.go: -------------------------------------------------------------------------------- 1 | // Package nacl provides encryption by salting a key with a pad 2 | package nacl 3 | 4 | // https://en.wikipedia.org/wiki/NaCl_(software) 5 | // work is derived from: 6 | // 7 | // https://github.com/andmarios/golang-nacl-secretbox 8 | 9 | import ( 10 | "crypto/rand" 11 | "errors" 12 | "fmt" 13 | 14 | "github.com/alistanis/goenc/generate" 15 | "golang.org/x/crypto/nacl/secretbox" 16 | ) 17 | 18 | const ( 19 | keySize = 32 20 | nonceSize = 24 21 | ) 22 | 23 | // Cipher to implmement the BlockCipher interface 24 | type Cipher struct { 25 | Pad []byte 26 | } 27 | 28 | // Encrypt implements the BlockCipher interface 29 | func (c *Cipher) Encrypt(key, plaintext []byte) ([]byte, error) { 30 | return Encrypt(c.Pad, key, plaintext) 31 | } 32 | 33 | // Decrypt implements the BlockCipher interface 34 | func (c *Cipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 35 | return Decrypt(c.Pad, key, ciphertext) 36 | } 37 | 38 | // KeySize returns the NaCL keysize 39 | func (c *Cipher) KeySize() int { 40 | return keySize 41 | } 42 | 43 | // Encrypt salts a key using pad and encrypts a message 44 | func Encrypt(pad, key, message []byte) (out []byte, err error) { 45 | if len(pad) < 32 { 46 | return nil, fmt.Errorf("pad had a length of %d, it must be at least 32 bytes", len(pad)) 47 | } 48 | // NaCl's key has a constant size of 32 bytes. 49 | // The user provided key probably is less than that. We pad it with 50 | // a long enough string and truncate anything we don't need later on. 51 | key = append(key, pad...) 52 | 53 | // NaCl's key should be of type [32]byte. 54 | // Here we create it and truncate key bytes beyond 32 55 | naclKey := new([keySize]byte) 56 | copy(naclKey[:], key[:keySize]) 57 | 58 | nonce, err := generate.Nonce() 59 | if err != nil { 60 | return nil, err 61 | } 62 | // out will hold the nonce and the encrypted message (ciphertext) 63 | out = make([]byte, nonceSize) 64 | // Copy the nonce to the start of out 65 | copy(out, nonce[:]) 66 | // SimpleEncrypt the message and append it to out, assign the result to out 67 | out = secretbox.Seal(out, message, nonce, naclKey) 68 | return out, err 69 | } 70 | 71 | // Decrypt salts a key using pad and decrypts a message 72 | func Decrypt(pad, key, data []byte) (out []byte, err error) { 73 | key = append(key, pad...) 74 | 75 | // NaCl's key should be of type [32]byte. 76 | // Here we create it and truncate key bytes beyond 32 77 | naclKey := new([keySize]byte) 78 | copy(naclKey[:], key[:keySize]) 79 | 80 | // The nonce is of type [24]byte and part of the data we will receive 81 | nonce := new([nonceSize]byte) 82 | 83 | // Read the nonce from in, it is in the first 24 bytes 84 | copy(nonce[:], data[:nonceSize]) 85 | 86 | // SimpleDecrypt the output of secretbox.Seal which contains the nonce and 87 | // the encrypted message 88 | message, ok := secretbox.Open(nil, data[nonceSize:], nonce, naclKey) 89 | if ok { 90 | return message, nil 91 | } 92 | return nil, errors.New("Decryption failed") 93 | } 94 | 95 | // RandomPadEncrypt generates a random pad and returns the encrypted data, the pad, and an error if any 96 | func RandomPadEncrypt(key, message []byte) (pad, out []byte, err error) { 97 | pad = make([]byte, 32) 98 | _, err = rand.Read(pad) 99 | if err != nil { 100 | return 101 | } 102 | out, err = Encrypt(pad, key, message) 103 | return 104 | } 105 | -------------------------------------------------------------------------------- /aes/cfb/cfb.go: -------------------------------------------------------------------------------- 1 | // Package cfb supports basic cfb encryption with NO HMAC 2 | package cfb 3 | 4 | // https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Feedback_.28CFB.29 5 | 6 | import ( 7 | "crypto/aes" 8 | "crypto/cipher" 9 | 10 | "crypto/hmac" 11 | "crypto/sha256" 12 | 13 | "github.com/alistanis/goenc/encerrors" 14 | "github.com/alistanis/goenc/generate" 15 | ) 16 | 17 | const ( 18 | // KeySize for CFB uses the generic key size 19 | KeySize = generate.KeySize 20 | // CKeySize - Cipher key size - AES-256 21 | CKeySize = 32 22 | // MACSize is the output size of HMAC-SHA-256 23 | MACSize = 32 24 | // MKeySize - HMAC key size - HMAC-SHA-256 25 | MKeySize = 32 26 | // IVSize - 16 for cfb 27 | IVSize = 16 28 | ) 29 | 30 | // Cipher to use for implementing the BlockCipher interface 31 | type Cipher struct { 32 | } 33 | 34 | // New returns a new cfb cipher 35 | func New() *Cipher { 36 | return &Cipher{} 37 | } 38 | 39 | // Encrypt implements the BlockCipher interface 40 | func (c *Cipher) Encrypt(key, plaintext []byte) ([]byte, error) { 41 | return Encrypt(key, plaintext) 42 | } 43 | 44 | // Decrypt implements the BlockCipher interface 45 | func (c *Cipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 46 | return Decrypt(key, ciphertext) 47 | } 48 | 49 | // KeySize implements the BlockCipher interface 50 | func (c *Cipher) KeySize() int { 51 | return KeySize 52 | } 53 | 54 | // Encrypt encrypts plaintext using the given key with CTR encryption 55 | func Encrypt(key, plaintext []byte) ([]byte, error) { 56 | if len(key) != KeySize { 57 | return nil, encerrors.ErrInvalidKeyLength 58 | } 59 | 60 | iv, err := generate.RandBytes(IVSize) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | ct := make([]byte, len(plaintext)) 66 | 67 | // NewCipher only returns an error with an invalid key size, 68 | // but the key size was checked at the beginning of the function. 69 | c, _ := aes.NewCipher(key[:CKeySize]) 70 | cbc := cipher.NewCFBEncrypter(c, iv) 71 | cbc.XORKeyStream(ct, plaintext) 72 | 73 | h := hmac.New(sha256.New, key[CKeySize:]) 74 | ct = append(iv, ct...) 75 | h.Write(ct) 76 | ct = h.Sum(ct) 77 | return ct, nil 78 | } 79 | 80 | // Decrypt decrypts ciphertext using the given key 81 | func Decrypt(key, ciphertext []byte) ([]byte, error) { 82 | if len(key) != KeySize { 83 | return nil, encerrors.ErrInvalidKeyLength 84 | } 85 | 86 | if len(ciphertext) <= (IVSize + MACSize) { 87 | return nil, encerrors.ErrInvalidMessageLength 88 | } 89 | 90 | macStart := len(ciphertext) - MACSize 91 | tag := ciphertext[macStart:] 92 | out := make([]byte, macStart-IVSize) 93 | ciphertext = ciphertext[:macStart] 94 | 95 | h := hmac.New(sha256.New, key[CKeySize:]) 96 | h.Write(ciphertext) 97 | mac := h.Sum(nil) 98 | if !hmac.Equal(mac, tag) { 99 | return nil, encerrors.ErrInvalidSum 100 | } 101 | 102 | c, _ := aes.NewCipher(key[:CKeySize]) 103 | cbc := cipher.NewCFBDecrypter(c, ciphertext[:IVSize]) 104 | cbc.XORKeyStream(out, ciphertext[IVSize:]) 105 | return out, nil 106 | } 107 | 108 | // DecryptString decrypts ciphertext using the given key 109 | func DecryptString(key, ciphertext string) (string, error) { 110 | b, err := Decrypt([]byte(key), []byte(ciphertext)) 111 | return string(b), err 112 | } 113 | 114 | // EncryptString encrypts ciphertext using the given key 115 | func EncryptString(key, plaintext string) (string, error) { 116 | b, err := Encrypt([]byte(key), []byte(plaintext)) 117 | return string(b), err 118 | } 119 | -------------------------------------------------------------------------------- /aes/gcm/gcm_test.go: -------------------------------------------------------------------------------- 1 | package gcm 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "testing" 7 | 8 | "github.com/alistanis/goenc/generate" 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | var ( 13 | text = "this is some text to encrypt" 14 | ) 15 | 16 | func TestEncrypt(t *testing.T) { 17 | Convey("We can encrypt and decrypt messages using a key", t, func() { 18 | k, err := generate.Key() 19 | So(err, ShouldBeNil) 20 | key := k[:] 21 | data, err := Encrypt(key, []byte(text), NonceSize) 22 | So(err, ShouldBeNil) 23 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 24 | 25 | data, err = Decrypt(key, data, NonceSize) 26 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 27 | 28 | s, err := EncryptString(string(key), text, NonceSize) 29 | So(err, ShouldBeNil) 30 | So(s, ShouldNotEqual, text) 31 | 32 | s, err = DecryptString(string(key), s, NonceSize) 33 | So(err, ShouldBeNil) 34 | So(s, ShouldEqual, text) 35 | }) 36 | } 37 | 38 | func TestEncryptWithID(t *testing.T) { 39 | Convey("We can encrypt and decrypt messages using an ID", t, func() { 40 | k, err := generate.Key() 41 | So(err, ShouldBeNil) 42 | key := k[:] 43 | 44 | id := uint32(1) 45 | 46 | keyFunc := func(uint32) ([]byte, error) { 47 | return key, nil 48 | } 49 | h := NewGCMHelper(keyFunc) 50 | data, err := EncryptWithID(key, []byte(text), id, NonceSize) 51 | So(err, ShouldBeNil) 52 | 53 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 54 | 55 | data, err = DecryptWithID(data, h, NonceSize) 56 | So(err, ShouldBeNil) 57 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 58 | s := "You can't read this" 59 | s1, err := EncryptStringWithID(string(key), s, 47, NonceSize) 60 | So(err, ShouldBeNil) 61 | So(s, ShouldNotEqual, s1) 62 | 63 | s1, err = DecryptStringWithID(s1, h, NonceSize) 64 | So(err, ShouldBeNil) 65 | So(s1, ShouldEqual, s) 66 | }) 67 | } 68 | 69 | type kr struct { 70 | f func() ([]byte, error) 71 | } 72 | 73 | func (k *kr) KeyForID(u uint32) ([]byte, error) { 74 | return k.f() 75 | } 76 | 77 | func TestCipher_Encrypt(t *testing.T) { 78 | Convey("We can encrypt and decrypt messages using a cipher struct", t, func() { 79 | c := New() 80 | So(c.KeySize(), ShouldEqual, KeySize) 81 | k, err := generate.Key() 82 | So(err, ShouldBeNil) 83 | key := k[:] 84 | data, err := c.Encrypt(key, []byte(text)) 85 | So(err, ShouldBeNil) 86 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 87 | 88 | data, err = c.Decrypt(key, data) 89 | So(err, ShouldBeNil) 90 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 91 | 92 | data, err = c.EncryptWithID(key, []byte(text), 1) 93 | So(err, ShouldBeNil) 94 | So(bytes.Equal([]byte(text), data), ShouldBeFalse) 95 | kr := &kr{f: func() ([]byte, error) { return key, nil }} 96 | data, err = c.DecryptWithID(data, kr) 97 | So(err, ShouldBeNil) 98 | So(bytes.Equal([]byte(text), data), ShouldBeTrue) 99 | }) 100 | } 101 | 102 | var errTestError = errors.New("test error") 103 | 104 | type R struct{} 105 | 106 | func (r *R) KeyForID(u uint32) ([]byte, error) { 107 | return nil, errTestError 108 | } 109 | 110 | type T struct{} 111 | 112 | func (t *T) KeyForID(u uint32) ([]byte, error) { 113 | return []byte{}, nil 114 | } 115 | 116 | func TestErrors(t *testing.T) { 117 | Convey("We can get appropriate errors", t, func() { 118 | k, err := generate.RandBytes(15) 119 | So(err, ShouldBeNil) 120 | key := k[:] 121 | errText := "crypto/aes: invalid key size 15" 122 | _, err = Encrypt(key, []byte{}, NonceSize) 123 | So(err.Error(), ShouldEqual, errText) 124 | 125 | _, err = Decrypt(key, []byte{}, NonceSize) 126 | So(err, ShouldNotBeNil) 127 | 128 | _, err = EncryptWithID(key, []byte{}, 1, NonceSize) 129 | So(err.Error(), ShouldEqual, errText) 130 | 131 | _, err = DecryptWithID([]byte{}, nil, NonceSize) 132 | So(err, ShouldNotBeNil) 133 | data, err := generate.RandBytes(NonceSize + 5) 134 | So(err, ShouldBeNil) 135 | _, err = DecryptWithID(data, &R{}, NonceSize) 136 | So(err, ShouldEqual, errTestError) 137 | 138 | _, err = DecryptWithID(data, &T{}, NonceSize) 139 | So(err.Error(), ShouldEqual, "crypto/aes: invalid key size 0") 140 | 141 | }) 142 | } 143 | -------------------------------------------------------------------------------- /aes/cbc/cbc.go: -------------------------------------------------------------------------------- 1 | // Package cbc supports cbc encryption - this implementation is authenticated with crypto/hmac using sha256 2 | package cbc 3 | 4 | // https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29 5 | 6 | import ( 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "crypto/hmac" 10 | "crypto/rand" 11 | "crypto/sha256" 12 | 13 | "io" 14 | 15 | "github.com/alistanis/goenc/encerrors" 16 | "github.com/alistanis/goenc/generate" 17 | ) 18 | 19 | const ( 20 | // NonceSize to use for nonces 21 | NonceSize = aes.BlockSize 22 | // MACSize is the output size of HMAC-SHA-256 23 | MACSize = 32 24 | // CKeySize - Cipher key size - AES-256 25 | CKeySize = 32 26 | // MKeySize - HMAC key size - HMAC-SHA-256 27 | MKeySize = 32 28 | // KeySize is the key size for CBC 29 | KeySize = CKeySize + MKeySize 30 | ) 31 | 32 | // pad pads input to match the correct size 33 | func pad(in []byte) []byte { 34 | padding := 16 - (len(in) % 16) 35 | for i := 0; i < padding; i++ { 36 | in = append(in, byte(padding)) 37 | } 38 | return in 39 | } 40 | 41 | // unpad removes unnecessary bytes that were added during initial padding 42 | func unpad(in []byte) []byte { 43 | if len(in) == 0 { 44 | return nil 45 | } 46 | 47 | padding := in[len(in)-1] 48 | if int(padding) > len(in) || padding > aes.BlockSize { 49 | return nil 50 | } else if padding == 0 { 51 | return nil 52 | } 53 | 54 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 55 | if in[i] != padding { 56 | return nil 57 | } 58 | } 59 | return in[:len(in)-int(padding)] 60 | } 61 | 62 | // Cipher implements the BlockCipher interface 63 | type Cipher struct{} 64 | 65 | // Encrypt implements the BlockCipher interface 66 | func (c *Cipher) Encrypt(key, plaintext []byte) ([]byte, error) { 67 | return Encrypt(key, plaintext) 68 | } 69 | 70 | // Decrypt implements the BlockCipher interface 71 | func (c *Cipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 72 | return Decrypt(key, ciphertext) 73 | } 74 | 75 | // KeySize returns CBC KeySize and implements the BlockCipher interface 76 | func (c *Cipher) KeySize() int { 77 | return KeySize 78 | } 79 | 80 | // New returns a new cbc cipher 81 | func New() *Cipher { 82 | return &Cipher{} 83 | } 84 | 85 | // Key returns a random key as a pointer to an array of bytes specified by KeySize 86 | func Key() (*[KeySize]byte, error) { 87 | key := new([KeySize]byte) 88 | _, err := io.ReadFull(rand.Reader, key[:]) 89 | return key, err 90 | } 91 | 92 | // Encrypt encrypts plaintext using the given key with CBC encryption 93 | func Encrypt(key, plaintext []byte) ([]byte, error) { 94 | if len(key) != KeySize { 95 | return nil, encerrors.ErrInvalidKeyLength 96 | } 97 | 98 | iv, err := generate.RandBytes(NonceSize) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | pmessage := pad(plaintext) 104 | ct := make([]byte, len(pmessage)) 105 | 106 | // NewCipher only returns an error with an invalid key size, 107 | // but the key size was checked at the beginning of the function. 108 | c, _ := aes.NewCipher(key[:CKeySize]) 109 | cbc := cipher.NewCBCEncrypter(c, iv) 110 | cbc.CryptBlocks(ct, pmessage) 111 | 112 | h := hmac.New(sha256.New, key[CKeySize:]) 113 | ct = append(iv, ct...) 114 | h.Write(ct) 115 | ct = h.Sum(ct) 116 | return ct, nil 117 | } 118 | 119 | // Decrypt decrypts ciphertext using the given key 120 | func Decrypt(key, ciphertext []byte) ([]byte, error) { 121 | if len(key) != KeySize { 122 | return nil, encerrors.ErrInvalidKeyLength 123 | } 124 | 125 | // HMAC-SHA-256 returns a MAC that is also a multiple of the 126 | // block size. 127 | if (len(ciphertext) % aes.BlockSize) != 0 { 128 | return nil, encerrors.ErrInvalidMessageLength 129 | } 130 | 131 | // A ciphertext must have at least an IV block, a ciphertext block, 132 | // and two blocks of HMAC. 133 | if len(ciphertext) < (4 * aes.BlockSize) { 134 | return nil, encerrors.ErrInvalidMessageLength 135 | } 136 | 137 | macStart := len(ciphertext) - MACSize 138 | tag := ciphertext[macStart:] 139 | out := make([]byte, macStart-NonceSize) 140 | ciphertext = ciphertext[:macStart] 141 | 142 | h := hmac.New(sha256.New, key[CKeySize:]) 143 | h.Write(ciphertext) 144 | mac := h.Sum(nil) 145 | if !hmac.Equal(mac, tag) { 146 | return nil, encerrors.ErrInvalidSum 147 | } 148 | 149 | // NewCipher only returns an error with an invalid key size, 150 | // but the key size was checked at the beginning of the function. 151 | c, _ := aes.NewCipher(key[:CKeySize]) 152 | cbc := cipher.NewCBCDecrypter(c, ciphertext[:NonceSize]) 153 | cbc.CryptBlocks(out, ciphertext[NonceSize:]) 154 | 155 | pt := unpad(out) 156 | if pt == nil { 157 | return nil, encerrors.ErrInvalidPadding 158 | } 159 | 160 | return pt, nil 161 | } 162 | -------------------------------------------------------------------------------- /enc_test.go: -------------------------------------------------------------------------------- 1 | package goenc 2 | 3 | import ( 4 | "bytes" 5 | "crypto/rand" 6 | "io/ioutil" 7 | "os" 8 | "testing" 9 | 10 | "fmt" 11 | 12 | "path/filepath" 13 | "runtime" 14 | 15 | "reflect" 16 | 17 | "github.com/alistanis/goenc/encerrors" 18 | "github.com/alistanis/goenc/generate" 19 | . "github.com/smartystreets/goconvey/convey" 20 | "golang.org/x/crypto/nacl/box" 21 | ) 22 | 23 | var ( 24 | ciphers []*Cipher 25 | ) 26 | 27 | func init() { 28 | cbc, err := NewCipher(CBC, testComplexity) 29 | if err != nil { 30 | fmt.Fprintln(os.Stderr, err) 31 | os.Exit(-1) 32 | } 33 | 34 | cfb, err := NewCipher(CFB, testComplexity) 35 | if err != nil { 36 | fmt.Fprintln(os.Stderr, err) 37 | os.Exit(-1) 38 | } 39 | ctr, err := NewCipher(CTR, testComplexity) 40 | if err != nil { 41 | fmt.Fprintln(os.Stderr, err) 42 | os.Exit(-1) 43 | } 44 | 45 | gcm, err := NewCipher(GCM, testComplexity) 46 | if err != nil { 47 | fmt.Fprintln(os.Stderr, err) 48 | os.Exit(-1) 49 | } 50 | 51 | nacl, err := NewCipher(NaCL, testComplexity, []byte("this is a pad to use for our key mwahahaha 123456789")) 52 | if err != nil { 53 | fmt.Fprintln(os.Stderr, err) 54 | os.Exit(-1) 55 | } 56 | ciphers = []*Cipher{cbc, cfb, ctr, gcm, nacl} 57 | } 58 | 59 | func TestFileIO(t *testing.T) { 60 | 61 | Convey("We can successfully perform file writing and reading using the block cipher interface functions", t, func() { 62 | var tempDir = "/tmp" 63 | if runtime.GOOS == "windows" { 64 | userProfile := os.Getenv("USERPROFILE") 65 | tempDir = filepath.Join(userProfile, "AppData", "Local", "Temp") 66 | } 67 | 68 | bc, err := NewCipher(Mock, testComplexity) 69 | So(err, ShouldBeNil) 70 | d, err := ioutil.TempDir(tempDir, "") 71 | So(err, ShouldBeNil) 72 | defer os.RemoveAll(d) 73 | tf, err := ioutil.TempFile(d, "") 74 | So(err, ShouldBeNil) 75 | 76 | data := []byte("test data we'd like to 'encrypt' and save to file") 77 | key := []byte("test key which is meaningless") 78 | 79 | err = EncryptAndSave(bc, key, data, tf.Name()) 80 | So(err, ShouldBeNil) 81 | 82 | err = tf.Close() 83 | So(err, ShouldBeNil) 84 | 85 | nd, err := ReadEncryptedFile(bc, key, tf.Name()) 86 | So(err, ShouldBeNil) 87 | So(bytes.Equal(data, nd), ShouldBeTrue) 88 | }) 89 | 90 | } 91 | 92 | func TestFileIOErrors(t *testing.T) { 93 | Convey("We can get errors on file io when we should", t, func() { 94 | c, err := NewCipher(GCM, testComplexity) 95 | So(err, ShouldBeNil) 96 | 97 | err = EncryptAndSave(c, []byte{}, []byte{}, "") 98 | So(err, ShouldNotBeNil) 99 | 100 | _, err = ReadEncryptedFile(c, []byte{}, "") 101 | So(err, ShouldNotBeNil) 102 | 103 | err = EncryptAndSaveWithPerms(c, []byte{}, []byte{}, "", 0644) 104 | So(err, ShouldNotBeNil) 105 | }) 106 | } 107 | 108 | func TestGenerateKeys(t *testing.T) { 109 | var err error 110 | 111 | _, _, err = box.GenerateKey(rand.Reader) 112 | if err != nil { 113 | t.Fatalf("%v", err) 114 | } 115 | 116 | _, _, err = box.GenerateKey(rand.Reader) 117 | if err != nil { 118 | t.Fatalf("%v", err) 119 | } 120 | } 121 | 122 | func TestCipher_Encrypt(t *testing.T) { 123 | Convey("We can test encrypting with a cypher with both keys and passwords", t, func() { 124 | c, err := NewCipher(GCM, testComplexity) 125 | So(err, ShouldBeNil) 126 | k, err := generate.Key() 127 | So(err, ShouldBeNil) 128 | key := k[:] 129 | plaintext := []byte("This is some data") 130 | data, err := c.Encrypt(key, plaintext) 131 | So(bytes.Equal(data, plaintext), ShouldBeFalse) 132 | 133 | data, err = c.Decrypt(key, data) 134 | So(err, ShouldBeNil) 135 | So(bytes.Equal(data, plaintext), ShouldBeTrue) 136 | 137 | password := []byte("password") 138 | data, err = c.EncryptWithPassword(password, plaintext) 139 | So(err, ShouldBeNil) 140 | So(bytes.Equal(data, plaintext), ShouldBeFalse) 141 | 142 | data, err = c.DecryptWithPassword(password, data) 143 | So(err, ShouldBeNil) 144 | So(bytes.Equal(data, plaintext), ShouldBeTrue) 145 | }) 146 | } 147 | 148 | func TestDeriveKey(t *testing.T) { 149 | Convey("We can test a derived key in order to encrypt and decrypt text", t, func() { 150 | c, err := NewCipher(GCM, testComplexity) 151 | So(err, ShouldBeNil) 152 | 153 | salt, err := generate.RandBytes(SaltSize) 154 | So(err, ShouldBeNil) 155 | 156 | key, err := DeriveKey([]byte("password"), salt, c.DerivedKeyN, c.BlockCipher.KeySize()) 157 | So(err, ShouldBeNil) 158 | plaintext := []byte("This is some data") 159 | data, err := c.BlockCipher.Encrypt(key, plaintext) 160 | So(err, ShouldBeNil) 161 | 162 | key2, err := DeriveKey([]byte("password"), salt, c.DerivedKeyN, c.BlockCipher.KeySize()) 163 | So(err, ShouldBeNil) 164 | 165 | data, err = c.BlockCipher.Decrypt(key2, data) 166 | So(err, ShouldBeNil) 167 | So(bytes.Equal(plaintext, data), ShouldBeTrue) 168 | }) 169 | } 170 | 171 | func TestDeriveKeyErrors(t *testing.T) { 172 | Convey("We can get derived key errors when we should", t, func() { 173 | 174 | _, err := DeriveKey([]byte{}, []byte{}, 0, 0) 175 | So(err, ShouldNotBeNil) 176 | }) 177 | } 178 | 179 | func TestCiphers(t *testing.T) { 180 | Convey("We can test all ciphers operate properly", t, func() { 181 | for _, c := range ciphers { 182 | k, err := generate.Key() 183 | So(err, ShouldBeNil) 184 | nonce, err := generate.Nonce() 185 | So(err, ShouldBeNil) 186 | key, err := DeriveKey(k[:], nonce[:], testComplexity, c.KeySize()) 187 | So(err, ShouldBeNil) 188 | text := []byte("this is some text to encrypt") 189 | 190 | data, err := c.Encrypt(key, text) 191 | So(err, ShouldBeNil) 192 | So(bytes.Equal(text, data), ShouldBeFalse) 193 | data, err = c.Decrypt(key, data) 194 | So(err, ShouldBeNil) 195 | So(bytes.Equal(text, data), ShouldBeTrue) 196 | } 197 | }) 198 | } 199 | 200 | func TestMessage(t *testing.T) { 201 | Convey("We can marshal and unmarshal messages", t, func() { 202 | m := NewMessage([]byte("Hello!"), 1) 203 | data := m.Marshal() 204 | nm, err := UnmarshalMessage(data) 205 | So(err, ShouldBeNil) 206 | So(reflect.DeepEqual(m, nm), ShouldBeTrue) 207 | 208 | data = []byte{} 209 | 210 | _, err = UnmarshalMessage(data) 211 | So(err, ShouldEqual, encerrors.ErrInvalidMessageLength) 212 | }) 213 | } 214 | -------------------------------------------------------------------------------- /aes/gcm/gcm.go: -------------------------------------------------------------------------------- 1 | // Package gcm supports gcm encryption - gcm is authenticated by default 2 | package gcm 3 | 4 | // https://en.wikipedia.org/wiki/Galois/Counter_Mode 5 | 6 | import ( 7 | "crypto/aes" 8 | "crypto/cipher" 9 | "encoding/binary" 10 | 11 | "github.com/alistanis/goenc/encerrors" 12 | "github.com/alistanis/goenc/generate" 13 | ) 14 | 15 | // NonceSize // generic NonceSize from RFC5084 16 | const NonceSize = 12 // https://tools.ietf.org/html/rfc5084 17 | 18 | // KeySize // generic KeySize 19 | const KeySize = generate.KeySize 20 | 21 | // Cipher to implement the BlockCipher interface 22 | type Cipher struct { 23 | NonceSize int 24 | } 25 | 26 | // New returns a new GCM cipher 27 | func New() *Cipher { 28 | return &Cipher{NonceSize: NonceSize} 29 | } 30 | 31 | // Encrypt implements the BlockCipher interface 32 | func (c *Cipher) Encrypt(key, plaintext []byte) ([]byte, error) { 33 | return Encrypt(key, plaintext, c.NonceSize) 34 | } 35 | 36 | // Decrypt implements the BlockCipher interface 37 | func (c *Cipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 38 | return Decrypt(key, ciphertext, c.NonceSize) 39 | } 40 | 41 | // KeySize returns the GCM key size 42 | func (c *Cipher) KeySize() int { 43 | return KeySize 44 | } 45 | 46 | // EncryptWithID calls the package EncryptWithID and passes c.NonceSize 47 | func (c *Cipher) EncryptWithID(key, plaintext []byte, sender uint32) ([]byte, error) { 48 | return EncryptWithID(key, plaintext, sender, c.NonceSize) 49 | } 50 | 51 | // DecryptWithID calls the package DecryptWithID and passes c.NonceSize 52 | func (c *Cipher) DecryptWithID(message []byte, k KeyRetriever) ([]byte, error) { 53 | return DecryptWithID(message, k, c.NonceSize) 54 | } 55 | 56 | // Encrypt secures a message using AES-GCM. 57 | func Encrypt(key, plaintext []byte, nonceSize int) ([]byte, error) { 58 | c, err := aes.NewCipher(key) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | gcm, err := cipher.NewGCMWithNonceSize(c, nonceSize) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | nonce, err := generate.GCMNonce() 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | // Seal will append the output to the first argument; the usage 74 | // here appends the ciphertext to the nonce. The final parameter 75 | // is any additional data to be authenticated. 76 | out := gcm.Seal(nonce[:], nonce[:], plaintext, nil) 77 | return out, nil 78 | } 79 | 80 | // EncryptString is a convenience function for working with strings 81 | func EncryptString(key, plaintext string, nonceSize int) (string, error) { 82 | data, err := Encrypt([]byte(key), []byte(plaintext), nonceSize) 83 | return string(data), err 84 | } 85 | 86 | // Decrypt decrypts data using AES-GCM 87 | func Decrypt(key, ciphertext []byte, nonceSize int) ([]byte, error) { 88 | if len(ciphertext) == 0 { 89 | return nil, encerrors.ErrInvalidMessageLength 90 | } 91 | // Create the AES cipher 92 | block, err := aes.NewCipher(key) 93 | if err != nil { 94 | return nil, err 95 | } 96 | gcm, err := cipher.NewGCMWithNonceSize(block, nonceSize) 97 | if err != nil { 98 | return nil, err 99 | } 100 | nonce := make([]byte, nonceSize) 101 | copy(nonce, ciphertext) 102 | return gcm.Open(nil, nonce[:], ciphertext[nonceSize:], nil) 103 | } 104 | 105 | // DecryptString is a convenience function for working with strings 106 | func DecryptString(key, ciphertext string, nonceSize int) (string, error) { 107 | data, err := Decrypt([]byte(key), []byte(ciphertext), nonceSize) 108 | return string(data), err 109 | } 110 | 111 | //--------------------------------------------- 112 | // For use with more complex encryption schemes 113 | //--------------------------------------------- 114 | 115 | // EncryptWithID secures a message and prepends a 4-byte sender ID 116 | // to the message. The end bit is tricky, because gcm.Seal modifies buf, and this is necessary 117 | func EncryptWithID(key, message []byte, sender uint32, nonceSize int) ([]byte, error) { 118 | buf := make([]byte, 4) 119 | binary.BigEndian.PutUint32(buf, sender) 120 | 121 | c, err := aes.NewCipher(key) 122 | if err != nil { 123 | return nil, err 124 | } 125 | 126 | gcm, err := cipher.NewGCMWithNonceSize(c, nonceSize) 127 | if err != nil { 128 | return nil, err 129 | } 130 | 131 | nonce, err := generate.GCMNonce() 132 | if err != nil { 133 | return nil, err 134 | } 135 | 136 | buf = append(buf, nonce[:]...) 137 | return gcm.Seal(buf, nonce[:], message, buf[:4]), nil 138 | } 139 | 140 | // EncryptStringWithID is a helper function to work with strings instead of bytes 141 | func EncryptStringWithID(key, message string, sender uint32, nonceSize int) (string, error) { 142 | data, err := EncryptWithID([]byte(key), []byte(message), sender, nonceSize) 143 | return string(data), err 144 | } 145 | 146 | // DecryptWithID takes an encrypted message and a KeyForID function (to get a key from a cache or a database perhaps) 147 | // It checks the first 4 bytes for prepended header data, in this case, a sender ID 148 | func DecryptWithID(message []byte, k KeyRetriever, nonceSize int) ([]byte, error) { 149 | 150 | if len(message) <= nonceSize+4 { 151 | return nil, encerrors.ErrInvalidMessageLength 152 | } 153 | 154 | id := binary.BigEndian.Uint32(message[:4]) 155 | key, err := k.KeyForID(id) 156 | if err != nil { 157 | return nil, err 158 | } 159 | c, err := aes.NewCipher(key) 160 | if err != nil { 161 | return nil, err 162 | } 163 | 164 | gcm, err := cipher.NewGCMWithNonceSize(c, nonceSize) 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | nonce := make([]byte, nonceSize) 170 | copy(nonce, message[4:]) 171 | 172 | ciphertext := message[4+nonceSize:] 173 | 174 | // Decrypt the message, using the sender ID as the additional 175 | // data requiring authentication. 176 | out, err := gcm.Open(nil, nonce, ciphertext, message[:4]) 177 | if err != nil { 178 | return nil, err 179 | } 180 | 181 | return out, nil 182 | } 183 | 184 | // DecryptStringWithID is a helper function to work with strings instead of bytes 185 | func DecryptStringWithID(message string, k KeyRetriever, nonceSize int) (string, error) { 186 | data, err := DecryptWithID([]byte(message), k, nonceSize) 187 | return string(data), err 188 | } 189 | 190 | // KeyRetriever represents a type that should be used in order to retrieve a key from a datastore 191 | type KeyRetriever interface { 192 | KeyForID(uint32) ([]byte, error) 193 | } 194 | 195 | // GCMHelper is designed to make it easy to call EncryptWithID and DecryptWithID by assigning the KeyForIDFunc 196 | // it implements KeyRetriever and provides convenience functions 197 | // It also serves as an example for how to use KeyRetriever 198 | type GCMHelper struct { 199 | KeyForIDFunc func(uint32) ([]byte, error) 200 | } 201 | 202 | // NewGCMHelper returns a new helper 203 | func NewGCMHelper(f func(uint32) ([]byte, error)) *GCMHelper { 204 | return &GCMHelper{f} 205 | } 206 | 207 | // KeyForID implements the KeyRetriever interface, it should be used to get a Key for the given ID 208 | func (h *GCMHelper) KeyForID(u uint32) ([]byte, error) { 209 | return h.KeyForIDFunc(u) 210 | } 211 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goenc 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/alistanis/goenc)](https://goreportcard.com/report/github.com/alistanis/goenc) 3 | [![GoDoc](https://godoc.org/github.com/alistanis/goenc?status.svg)](https://godoc.org/github.com/alistanis/goenc) 4 | [![codecov](https://codecov.io/gh/alistanis/goenc/branch/master/graph/badge.svg)](https://codecov.io/gh/alistanis/goenc) 5 | 6 | Encryption and Decryption functions for Go made easy. Encryption should be as simple as calling Encrypt(key, data) and Decrypt(key, data). 7 | 8 | ###Note: I am in the process of trying to get this reviewed - use at your own risk 9 | 10 | # API 11 | 12 | The API is built around the `BlockCipher` interface and the `Session` struct. 13 | 14 | `BlockCipher` can be used to encrypt simple messages or small files. 15 | 16 | ```go 17 | // BlockCipher represents a cipher that encodes and decodes chunks of data at a time 18 | type BlockCipher interface { 19 | Encrypt(key, plaintext []byte) ([]byte, error) 20 | Decrypt(key, ciphertext []byte) ([]byte, error) 21 | KeySize() int 22 | } 23 | ``` 24 | 25 | `Session` can be used to perform key exchanges and send secure messages over a "channel" (`io.ReadWriter`) 26 | It also natively performs key derivation, can handle key exchanges, and can prevent replay attaacks. // that is a joke 27 | 28 | ###Note: Session has been temporarily removed in order to author a more secure version. 29 | 30 | ```go 31 | // Session represents a session that can be used to pass messages over a secure channel 32 | type Session struct { 33 | Cipher *Cipher 34 | Channel 35 | lastSent uint32 36 | lastRecv uint32 37 | sendKey *[32]byte 38 | recvKey *[32]byte 39 | } 40 | ``` 41 | 42 | All internal packages implement the `BlockCipher` interface with a `Cipher` struct, allowing for flexibility when working with the `BlockCipher` interface. 43 | 44 | Each package can also be used directly, as the `Cipher` struct in each simply wraps public functions. 45 | 46 | This package supports the following types of encryption, all using the Go stdlib with exception for NaCL, which uses [Secretbox from the /x/crypto package](https://godoc.org/golang.org/x/crypto/nacl/secretbox): 47 | 48 | --- 49 | * AES-CBC 50 | * AES-CFB 51 | * AES-CTR 52 | * AES-GCM 53 | * NaCL - with a user provided pad 54 | 55 | 56 | * ####All types support encryption + authentication 57 | 58 | --- 59 | 60 | #####Example Usage of package functions 61 | 62 | ```go 63 | key := []byte("some 32 byte key") // obviously this would fail without being 32 bytes 64 | ciphertext, err := gcm.Encrypt(key, []byte("super secret message")) 65 | if err != nil { 66 | return err 67 | } 68 | plaintext, err := gcm.Decrypt(key, ciphertext) 69 | if err != nil { 70 | return err 71 | } 72 | fmt.Println(plaintext) // super secret message 73 | ``` 74 | #####Example Usage of the main Cipher struct and a BlockCipher interface - this will perform key derivation 75 | 76 | ```go 77 | c, err := goenc.NewCipher(goenc.CBC, goenc.InteractiveComplexity) 78 | if err != nil { 79 | return err 80 | } 81 | ciphertext, err := c.Encrypt(key, []byte("super secret message")) 82 | if err != nil { 83 | return err 84 | } 85 | plaintext, err := c.Decrypt(key, ciphertext) 86 | if err != nil { 87 | return err 88 | } 89 | fmt.Println(plaintext) // super secret message 90 | ``` 91 | 92 | #####Example Usages of Session 93 | 94 | Note: Retries and connection breaking are not shown here 95 | 96 | ######As a server 97 | 98 | ```go 99 | // wait until a client connects and performs a key exchange 100 | s, err := goenc.Listen(readWriter, cipher) 101 | // if exchange is bad or none was given, we return 102 | if err != nil { 103 | return err 104 | } 105 | 106 | // s is now a session on the given readWriter (underlying conn) and can wait to receive messages 107 | for { 108 | msg, err := s.Receive() 109 | is err != nil { 110 | // check for closed connection here and break if it is (not shown) 111 | someErrChan <- err 112 | continue 113 | } 114 | 115 | msg, err := someMsgParsingFunc(msg) 116 | if err != nil { 117 | // garbled message 118 | someErrChan <- err 119 | continue 120 | } 121 | 122 | switch msg.Type { 123 | case SomeCoolThing: 124 | err = s.Send(someConstMessage) 125 | if err != nil { 126 | someErrChan <- err 127 | } 128 | default: 129 | // successfully parsed but we don't know what to do, probably retry parsing 130 | } 131 | } 132 | ``` 133 | 134 | ######As a client 135 | 136 | ```go 137 | // initial connection to underlying conn of readWriter 138 | s, err := goenc.Dial(readWriter, cipher) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | // send an initial message 144 | err := s.Send(someMessage) 145 | is err != nil { 146 | return err 147 | } 148 | for { 149 | 150 | // wait for response 151 | msg, err := s.Receive() 152 | if err != nil { 153 | someErrChan <- err 154 | continue 155 | } 156 | 157 | msg, err = someMsgParsingFunc(msg) 158 | if err != nil { 159 | // garbled message 160 | someErrChan <- err 161 | continue 162 | } 163 | 164 | switch msg.Type { 165 | case SomeCoolThing: 166 | err = s.Send(someConstMessage) 167 | if err != nil { 168 | someErrChan <- err 169 | } 170 | default: 171 | // successfully parsed but we don't know what to do, probably retry parsing 172 | } 173 | } 174 | ``` 175 | 176 | #SSH Package 177 | 178 | The ssh package contains convenience functions for generating and parsing ssh keys. They are a wrapper around the /x/crypto package's ssh package. 179 | 180 | TODO 181 | --- 182 | ``` 183 | 1. [ ] Get project reviewed (if you are a security expert interested in reviewing this, please contact me and let me know if you find anything) 184 | 2. [ ] More complete documentation with examples 185 | * [ ] Document full examples of package functions and the small differences they have 186 | * [ ] Document SenderID functions in the GCM package and give a real world example 187 | 3. [ ] Implement SenderID functions in packages other than GCM 188 | 4. [ ] Give user level control over when/how key derivation takes place 189 | * The way it works now on a session is that the key will be derived for every message - this is slow, but potentially more secure 190 | * If one algo has a flaw in which a prior key is discovered, only that message could be read 191 | * That should still be left up to the user 192 | * [ ] Allow user given salt 193 | ``` 194 | 195 | #Special Thanks 196 | 197 | A very special thanks to [Kyle Isom](https://github.com/kisom), whose book provided a very good jumping off point for starting this library. 198 | 199 | You can find his book here: [Practical Cryptography with Go](https://leanpub.com/gocrypto/) -------------------------------------------------------------------------------- /enc.go: -------------------------------------------------------------------------------- 1 | // Package goenc contains functions for working with encryption 2 | package goenc 3 | 4 | // work is derived from many sources: 5 | // 6 | // http://stackoverflow.com/questions/21151714/go-generate-an-ssh-public-key 7 | // https://golang.org/pkg/crypto/cipher/ 8 | // https://leanpub.com/gocrypto/read#leanpub-auto-aes-cbc 9 | // https://github.com/hashicorp/memberlist/blob/master/security.go 10 | 11 | import ( 12 | "encoding/binary" 13 | 14 | "io/ioutil" 15 | 16 | "os" 17 | 18 | "github.com/alistanis/goenc/aes/cbc" 19 | "github.com/alistanis/goenc/aes/cfb" 20 | "github.com/alistanis/goenc/aes/ctr" 21 | "github.com/alistanis/goenc/aes/gcm" 22 | "github.com/alistanis/goenc/encerrors" 23 | "github.com/alistanis/goenc/generate" 24 | "github.com/alistanis/goenc/nacl" 25 | "golang.org/x/crypto/nacl/secretbox" 26 | "golang.org/x/crypto/scrypt" 27 | ) 28 | 29 | /* 30 | TODO(cmc): Verify this isn't horrifically insecure and have this reviewed by a(n) expert(s) before publishing 31 | */ 32 | 33 | // BlockCipher represents a cipher that encodes and decodes chunks of data at a time 34 | type BlockCipher interface { 35 | Encrypt(key, plaintext []byte) ([]byte, error) 36 | Decrypt(key, ciphertext []byte) ([]byte, error) 37 | KeySize() int 38 | } 39 | 40 | //--------------------------------------------------------------------------- 41 | // BlockCipherInterface Functions - these should not be used with large files 42 | //-------------------------------------------------------------------------------- 43 | 44 | // EncryptAndSaveWithPerms encrypts data and saves it to a file with the given permissions using the given key 45 | func EncryptAndSaveWithPerms(cipher BlockCipher, key, plaintext []byte, path string, perm os.FileMode) error { 46 | data, err := cipher.Encrypt(key, plaintext) 47 | if err != nil { 48 | return err 49 | } 50 | return ioutil.WriteFile(path, data, perm) 51 | } 52 | 53 | // EncryptAndSave encrypts data and saves it to a file with the permissions 0644 54 | func EncryptAndSave(cipher BlockCipher, key, plaintext []byte, path string) error { 55 | return EncryptAndSaveWithPerms(cipher, key, plaintext, path, 0644) 56 | } 57 | 58 | // ReadEncryptedFile reads a file a path and attempts to decrypt the data there with the given key 59 | func ReadEncryptedFile(cipher BlockCipher, key []byte, path string) ([]byte, error) { 60 | ciphertext, err := ioutil.ReadFile(path) 61 | if err != nil { 62 | return nil, err 63 | } 64 | plaintext, err := cipher.Decrypt(key, ciphertext) 65 | return plaintext, err 66 | } 67 | 68 | // CipherKind represents what kind of cipher to use 69 | type CipherKind int 70 | 71 | // CipherKind constants 72 | const ( 73 | CBC CipherKind = iota 74 | CFB 75 | CTR 76 | GCM 77 | NaCL 78 | 79 | Mock 80 | ) 81 | 82 | const ( 83 | // SaltSize sets a generic salt size 84 | SaltSize = 64 85 | ) 86 | 87 | // Cipher is a struct that contains a BlockCipher interface and stores a DerivedKey Complexity number 88 | type Cipher struct { 89 | BlockCipher 90 | DerivedKeyN int 91 | } 92 | 93 | func (c *Cipher) DeriveKey(pass, salt []byte) ([]byte, error) { 94 | return DeriveKey(pass, salt, c.DerivedKeyN, c.KeySize()) 95 | } 96 | 97 | // NewCipher returns a new Cipher containing a BlockCipher interface based on the CipherKind 98 | func NewCipher(kind CipherKind, derivedKeyN int, args ...[]byte) (*Cipher, error) { 99 | c := &Cipher{DerivedKeyN: derivedKeyN} 100 | switch kind { 101 | case GCM: 102 | c.BlockCipher = gcm.New() 103 | case NaCL: 104 | // special case, we need to define a pad for nacl 105 | if len(args) == 0 { 106 | return nil, encerrors.ErrNoPadProvided 107 | } 108 | n := &nacl.Cipher{} 109 | n.Pad = args[0] 110 | c.BlockCipher = n 111 | case CFB: 112 | c.BlockCipher = cfb.New() 113 | case CBC: 114 | c.BlockCipher = cbc.New() 115 | case CTR: 116 | c.BlockCipher = ctr.New() 117 | case Mock: 118 | c.BlockCipher = &MockBlockCipher{} 119 | default: 120 | return nil, encerrors.ErrInvalidCipherKind 121 | } 122 | return c, nil 123 | } 124 | 125 | // EncryptWithPassword takes a password, plaintext, and derives a key based on that password, 126 | // then encrypting that data with the underlying block cipher and appending the salt to the output 127 | func (c *Cipher) EncryptWithPassword(password, plaintext []byte) ([]byte, error) { 128 | salt, err := generate.RandBytes(SaltSize) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | key, err := c.DeriveKey(password, salt) 134 | if err != nil { 135 | return nil, err 136 | } 137 | 138 | out, err := c.BlockCipher.Encrypt(key, plaintext) 139 | Zero(key) 140 | if err != nil { 141 | return nil, err 142 | } 143 | 144 | out = append(salt, out...) 145 | return out, nil 146 | } 147 | 148 | // Overhead is the amount of Overhead contained in the ciphertext 149 | const Overhead = SaltSize + secretbox.Overhead + generate.NonceSize 150 | 151 | // Decrypt takes a password and ciphertext, derives a key, and attempts to decrypt that data 152 | func (c *Cipher) DecryptWithPassword(password, ciphertext []byte) ([]byte, error) { 153 | if len(ciphertext) < Overhead { 154 | return nil, encerrors.ErrInvalidMessageLength 155 | } 156 | 157 | key, err := c.DeriveKey(password, ciphertext[:SaltSize]) 158 | if err != nil { 159 | return nil, err 160 | } 161 | 162 | out, err := c.BlockCipher.Decrypt(key, ciphertext[SaltSize:]) 163 | Zero(key) 164 | if err != nil { 165 | return nil, err 166 | } 167 | 168 | return out, nil 169 | } 170 | 171 | // MockBlockCipher implements BlockCipher but does nothing 172 | type MockBlockCipher struct{} 173 | 174 | // Encrypt in this case is only implementing the BlockCipher interface, it doesn't do anything 175 | func (m *MockBlockCipher) Encrypt(key, plaintext []byte) ([]byte, error) { 176 | return plaintext, nil 177 | } 178 | 179 | // Decrypt in this case is only implementing the BlockCipher interface, it doesn't do anything 180 | func (m *MockBlockCipher) Decrypt(key, ciphertext []byte) ([]byte, error) { 181 | return ciphertext, nil 182 | } 183 | 184 | // KeySize is a mock key size to use with the mock cipher 185 | func (m *MockBlockCipher) KeySize() int { 186 | return 32 187 | } 188 | 189 | // Message represents a message being passed, and contains its contents and a sequence number 190 | type Message struct { 191 | Number uint32 192 | Contents []byte 193 | } 194 | 195 | // NewMessage returns a new message 196 | func NewMessage(in []byte, num uint32) *Message { 197 | return &Message{Contents: in, Number: num} 198 | } 199 | 200 | // Marshal encodes a sequence number into the data that we wish to send 201 | func (m *Message) Marshal() []byte { 202 | out := make([]byte, 4, len(m.Contents)+4) 203 | binary.BigEndian.PutUint32(out[:4], m.Number) 204 | return append(out, m.Contents...) 205 | } 206 | 207 | // UnmarshalMessage decodes bytes into a message pointer 208 | func UnmarshalMessage(in []byte) (*Message, error) { 209 | if len(in) <= 4 { 210 | return nil, encerrors.ErrInvalidMessageLength 211 | } 212 | m := &Message{} 213 | m.Number = binary.BigEndian.Uint32(in[:4]) 214 | m.Contents = in[4:] 215 | return m, nil 216 | } 217 | 218 | const ( 219 | // testComplexity is unexported because we don't want to use such a weak key in the wild 220 | testComplexity = 1 << (iota + 7) 221 | ) 222 | 223 | const ( 224 | // N Complexity in powers of 2 for key Derivation 225 | 226 | // InteractiveComplexity - recommended complexity for interactive sessions 227 | InteractiveComplexity = 1 << (iota + 14) 228 | // Complexity15 is 2^15 229 | Complexity15 230 | // Complexity16 is 2^16 231 | Complexity16 232 | // Complexity17 is 2^17 233 | Complexity17 234 | // Complexity18 is 2^18 235 | Complexity18 236 | // Complexity19 is 2^19 237 | Complexity19 238 | // AggressiveComplexity is 2^20 (don't use this unless you have incredibly strong CPU power 239 | AggressiveComplexity 240 | ) 241 | 242 | // DeriveKey generates a new NaCl key from a passphrase and salt. 243 | // This is a costly operation. 244 | func DeriveKey(pass, salt []byte, N, keySize int) ([]byte, error) { 245 | var naclKey = make([]byte, keySize) 246 | key, err := scrypt.Key(pass, salt, N, 8, 1, keySize) 247 | if err != nil { 248 | return nil, err 249 | } 250 | 251 | copy(naclKey, key) 252 | Zero(key) 253 | return naclKey, nil 254 | } 255 | 256 | // Zero zeroes out bytes of data so that it does not stay in memory any longer than necessary 257 | func Zero(data []byte) { 258 | for i := 0; i < len(data); i++ { 259 | data[i] = 0 260 | } 261 | } 262 | --------------------------------------------------------------------------------