| // Package encrypted provides a simple, secure system for encrypting data |
| // symmetrically with a passphrase. |
| // |
| // It uses scrypt derive a key from the passphrase and the NaCl secret box |
| // cipher for authenticated encryption. |
| package encrypted |
| |
| import ( |
| "crypto/rand" |
| "encoding/json" |
| "errors" |
| "fmt" |
| "io" |
| |
| "golang.org/x/crypto/nacl/secretbox" |
| "golang.org/x/crypto/scrypt" |
| ) |
| |
| const saltSize = 32 |
| |
| const ( |
| boxKeySize = 32 |
| boxNonceSize = 24 |
| ) |
| |
| const ( |
| // N parameter was chosen to be ~100ms of work using the default implementation |
| // on the 2.3GHz Core i7 Haswell processor in a late-2013 Apple Retina Macbook |
| // Pro (it takes ~113ms). |
| scryptN = 32768 |
| scryptR = 8 |
| scryptP = 1 |
| ) |
| |
| const ( |
| nameScrypt = "scrypt" |
| nameSecretBox = "nacl/secretbox" |
| ) |
| |
| type data struct { |
| KDF scryptKDF `json:"kdf"` |
| Cipher secretBoxCipher `json:"cipher"` |
| Ciphertext []byte `json:"ciphertext"` |
| } |
| |
| type scryptParams struct { |
| N int `json:"N"` |
| R int `json:"r"` |
| P int `json:"p"` |
| } |
| |
| func newScryptKDF() (scryptKDF, error) { |
| salt := make([]byte, saltSize) |
| if err := fillRandom(salt); err != nil { |
| return scryptKDF{}, err |
| } |
| return scryptKDF{ |
| Name: nameScrypt, |
| Params: scryptParams{ |
| N: scryptN, |
| R: scryptR, |
| P: scryptP, |
| }, |
| Salt: salt, |
| }, nil |
| } |
| |
| type scryptKDF struct { |
| Name string `json:"name"` |
| Params scryptParams `json:"params"` |
| Salt []byte `json:"salt"` |
| } |
| |
| func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) { |
| return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize) |
| } |
| |
| // CheckParams checks that the encoded KDF parameters are what we expect them to |
| // be. If we do not do this, an attacker could cause a DoS by tampering with |
| // them. |
| func (s *scryptKDF) CheckParams() error { |
| if s.Params.N != scryptN || s.Params.R != scryptR || s.Params.P != scryptP { |
| return errors.New("encrypted: unexpected kdf parameters") |
| } |
| return nil |
| } |
| |
| func newSecretBoxCipher() (secretBoxCipher, error) { |
| nonce := make([]byte, boxNonceSize) |
| if err := fillRandom(nonce); err != nil { |
| return secretBoxCipher{}, err |
| } |
| return secretBoxCipher{ |
| Name: nameSecretBox, |
| Nonce: nonce, |
| }, nil |
| } |
| |
| type secretBoxCipher struct { |
| Name string `json:"name"` |
| Nonce []byte `json:"nonce"` |
| |
| encrypted bool |
| } |
| |
| func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte { |
| var keyBytes [boxKeySize]byte |
| var nonceBytes [boxNonceSize]byte |
| |
| if len(key) != len(keyBytes) { |
| panic("incorrect key size") |
| } |
| if len(s.Nonce) != len(nonceBytes) { |
| panic("incorrect nonce size") |
| } |
| |
| copy(keyBytes[:], key) |
| copy(nonceBytes[:], s.Nonce) |
| |
| // ensure that we don't re-use nonces |
| if s.encrypted { |
| panic("Encrypt must only be called once for each cipher instance") |
| } |
| s.encrypted = true |
| |
| return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes) |
| } |
| |
| func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) { |
| var keyBytes [boxKeySize]byte |
| var nonceBytes [boxNonceSize]byte |
| |
| if len(key) != len(keyBytes) { |
| panic("incorrect key size") |
| } |
| if len(s.Nonce) != len(nonceBytes) { |
| // return an error instead of panicking since the nonce is user input |
| return nil, errors.New("encrypted: incorrect nonce size") |
| } |
| |
| copy(keyBytes[:], key) |
| copy(nonceBytes[:], s.Nonce) |
| |
| res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes) |
| if !ok { |
| return nil, errors.New("encrypted: decryption failed") |
| } |
| return res, nil |
| } |
| |
| // Encrypt takes a passphrase and plaintext, and returns a JSON object |
| // containing ciphertext and the details necessary to decrypt it. |
| func Encrypt(plaintext, passphrase []byte) ([]byte, error) { |
| k, err := newScryptKDF() |
| if err != nil { |
| return nil, err |
| } |
| key, err := k.Key(passphrase) |
| if err != nil { |
| return nil, err |
| } |
| |
| c, err := newSecretBoxCipher() |
| if err != nil { |
| return nil, err |
| } |
| |
| data := &data{ |
| KDF: k, |
| Cipher: c, |
| } |
| data.Ciphertext = c.Encrypt(plaintext, key) |
| |
| return json.Marshal(data) |
| } |
| |
| // Marshal encrypts the JSON encoding of v using passphrase. |
| func Marshal(v interface{}, passphrase []byte) ([]byte, error) { |
| data, err := json.MarshalIndent(v, "", "\t") |
| if err != nil { |
| return nil, err |
| } |
| return Encrypt(data, passphrase) |
| } |
| |
| // Decrypt takes a JSON-encoded ciphertext object encrypted using Encrypt and |
| // tries to decrypt it using passphrase. If successful, it returns the |
| // plaintext. |
| func Decrypt(ciphertext, passphrase []byte) ([]byte, error) { |
| data := &data{} |
| if err := json.Unmarshal(ciphertext, data); err != nil { |
| return nil, err |
| } |
| |
| if data.KDF.Name != nameScrypt { |
| return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name) |
| } |
| if data.Cipher.Name != nameSecretBox { |
| return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name) |
| } |
| if err := data.KDF.CheckParams(); err != nil { |
| return nil, err |
| } |
| |
| key, err := data.KDF.Key(passphrase) |
| if err != nil { |
| return nil, err |
| } |
| |
| return data.Cipher.Decrypt(data.Ciphertext, key) |
| } |
| |
| // Unmarshal decrypts the data using passphrase and unmarshals the resulting |
| // plaintext into the value pointed to by v. |
| func Unmarshal(data []byte, v interface{}, passphrase []byte) error { |
| decrypted, err := Decrypt(data, passphrase) |
| if err != nil { |
| return err |
| } |
| return json.Unmarshal(decrypted, v) |
| } |
| |
| func fillRandom(b []byte) error { |
| _, err := io.ReadFull(rand.Reader, b) |
| return err |
| } |