diff --git a/pkg/cli/encrypt.go b/pkg/cli/encrypt.go new file mode 100644 index 0000000000..3df67eeeff --- /dev/null +++ b/pkg/cli/encrypt.go @@ -0,0 +1,62 @@ +package cli + +import ( + "fmt" + + "github.com/convox/rack/pkg/crypt" + "github.com/convox/rack/sdk" + "github.com/convox/stdcli" +) + +func init() { + register("encrypt", "encrypt data using key", Encrypt, stdcli.CommandOptions{ + Flags: []stdcli.Flag{ + stdcli.StringFlag("key", "", "key"), + stdcli.StringFlag("data", "", "data"), + }, + Usage: "", + }) + + register("decrypt", "decrypt data using key", Decrypt, stdcli.CommandOptions{ + Flags: []stdcli.Flag{ + stdcli.StringFlag("key", "", "key"), + stdcli.StringFlag("data", "", "data"), + }, + Usage: "", + }) +} + +func Encrypt(_ sdk.Interface, c *stdcli.Context) error { + + key := c.String("key") + data := c.String("data") + + if key == "" || data == "" { + return fmt.Errorf("key and data must be non empty") + } + + val, err := crypt.Encrypt(key, []byte(data)) + if err != nil { + return err + } + + fmt.Println(val) + return nil +} + +func Decrypt(_ sdk.Interface, c *stdcli.Context) error { + key := c.String("key") + data := c.String("data") + + if key == "" || data == "" { + return fmt.Errorf("key and data must be non empty") + } + + val, err := crypt.Decrypt(key, data) + if err != nil { + return err + } + + fmt.Println(string(val)) + return nil +} diff --git a/pkg/crypt/helpers.go b/pkg/crypt/helpers.go new file mode 100644 index 0000000000..3f3639a5cc --- /dev/null +++ b/pkg/crypt/helpers.go @@ -0,0 +1,105 @@ +package crypt + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/json" + "fmt" + "io" + + "github.com/pkg/errors" + "golang.org/x/crypto/nacl/secretbox" +) + +const ( + KeySize = 32 + NonceSize = 24 +) + +type RandEnvelope struct { + Nonce *[NonceSize]byte + CipherText []byte +} + +func OneWay(str string) string { + enc := sha256.Sum256([]byte(str)) + return base64.StdEncoding.EncodeToString(enc[:]) +} + +func Encrypt(ekey string, data []byte) (string, error) { + key, err := decodeKey(ekey) + if err != nil { + return "", errors.WithStack(err) + } + + nonce, err := generateNonce() + if err != nil { + return "", errors.WithStack(err) + } + + var cipherText []byte + cipherText = secretbox.Seal(cipherText, data, nonce, key) + + envelope := RandEnvelope{ + Nonce: nonce, + CipherText: cipherText, + } + + envelopeJson, err := json.Marshal(envelope) + if err != nil { + return "", errors.WithStack(err) + } + + return base64.StdEncoding.EncodeToString(envelopeJson), nil +} + +func Decrypt(ekey string, sealed string) ([]byte, error) { + decoded, err := base64.StdEncoding.DecodeString(sealed) + if err != nil { + return nil, errors.WithStack(err) + } + + var envelope RandEnvelope + + if err := json.Unmarshal(decoded, &envelope); err != nil { + return nil, errors.WithStack(err) + } + + key, err := decodeKey(ekey) + if err != nil { + return nil, errors.WithStack(err) + } + + data := []byte{} + + data, ok := secretbox.Open(nil, envelope.CipherText, envelope.Nonce, key) + if !ok { + return nil, errors.WithStack(fmt.Errorf("could not decrypt data")) + } + + return data, nil +} + +func decodeKey(ekey string) (*[KeySize]byte, error) { + var key [KeySize]byte + + data, err := base64.StdEncoding.DecodeString(ekey) + if err != nil { + return nil, errors.WithStack(err) + } + + copy(key[:], data[0:KeySize]) + + return &key, nil +} + +func generateNonce() (*[NonceSize]byte, error) { + nonce := new([NonceSize]byte) + _, err := io.ReadFull(rand.Reader, nonce[:]) + if err != nil { + return nil, errors.WithStack(err) + } + + return nonce, nil +}