...

Source file src/github.com/secure-systems-lab/go-securesystemslib/encrypted/encrypted.go

Documentation: github.com/secure-systems-lab/go-securesystemslib/encrypted

     1  // Package encrypted provides a simple, secure system for encrypting data
     2  // symmetrically with a passphrase.
     3  //
     4  // It uses scrypt derive a key from the passphrase and the NaCl secret box
     5  // cipher for authenticated encryption.
     6  package encrypted
     7  
     8  import (
     9  	"crypto/rand"
    10  	"encoding/json"
    11  	"errors"
    12  	"fmt"
    13  	"io"
    14  
    15  	"golang.org/x/crypto/nacl/secretbox"
    16  	"golang.org/x/crypto/scrypt"
    17  )
    18  
    19  const saltSize = 32
    20  
    21  const (
    22  	boxKeySize   = 32
    23  	boxNonceSize = 24
    24  )
    25  
    26  // KDFParameterStrength defines the KDF parameter strength level to be used for
    27  // encryption key derivation.
    28  type KDFParameterStrength uint8
    29  
    30  const (
    31  	// Legacy defines legacy scrypt parameters (N:2^15, r:8, p:1)
    32  	Legacy KDFParameterStrength = iota + 1
    33  	// Standard defines standard scrypt parameters which is focusing 100ms of computation (N:2^16, r:8, p:1)
    34  	Standard
    35  	// OWASP defines OWASP recommended scrypt parameters (N:2^17, r:8, p:1)
    36  	OWASP
    37  )
    38  
    39  var (
    40  	// legacyParams represents old scrypt derivation parameters for backward
    41  	// compatibility.
    42  	legacyParams = scryptParams{
    43  		N: 32768, // 2^15
    44  		R: 8,
    45  		P: 1,
    46  	}
    47  
    48  	// standardParams defines scrypt parameters based on the scrypt creator
    49  	// recommendation to limit key derivation in time boxed to 100ms.
    50  	standardParams = scryptParams{
    51  		N: 65536, // 2^16
    52  		R: 8,
    53  		P: 1,
    54  	}
    55  
    56  	// owaspParams defines scrypt parameters recommended by OWASP
    57  	owaspParams = scryptParams{
    58  		N: 131072, // 2^17
    59  		R: 8,
    60  		P: 1,
    61  	}
    62  
    63  	// defaultParams defines scrypt parameters which will be used to generate a
    64  	// new key.
    65  	defaultParams = standardParams
    66  )
    67  
    68  const (
    69  	nameScrypt    = "scrypt"
    70  	nameSecretBox = "nacl/secretbox"
    71  )
    72  
    73  type data struct {
    74  	KDF        scryptKDF       `json:"kdf"`
    75  	Cipher     secretBoxCipher `json:"cipher"`
    76  	Ciphertext []byte          `json:"ciphertext"`
    77  }
    78  
    79  type scryptParams struct {
    80  	N int `json:"N"`
    81  	R int `json:"r"`
    82  	P int `json:"p"`
    83  }
    84  
    85  func (sp *scryptParams) Equal(in *scryptParams) bool {
    86  	return in != nil && sp.N == in.N && sp.P == in.P && sp.R == in.R
    87  }
    88  
    89  func newScryptKDF(level KDFParameterStrength) (scryptKDF, error) {
    90  	salt := make([]byte, saltSize)
    91  	if err := fillRandom(salt); err != nil {
    92  		return scryptKDF{}, fmt.Errorf("unable to generate a random salt: %w", err)
    93  	}
    94  
    95  	var params scryptParams
    96  	switch level {
    97  	case Legacy:
    98  		params = legacyParams
    99  	case Standard:
   100  		params = standardParams
   101  	case OWASP:
   102  		params = owaspParams
   103  	default:
   104  		// Fallback to default parameters
   105  		params = defaultParams
   106  	}
   107  
   108  	return scryptKDF{
   109  		Name:   nameScrypt,
   110  		Params: params,
   111  		Salt:   salt,
   112  	}, nil
   113  }
   114  
   115  type scryptKDF struct {
   116  	Name   string       `json:"name"`
   117  	Params scryptParams `json:"params"`
   118  	Salt   []byte       `json:"salt"`
   119  }
   120  
   121  func (s *scryptKDF) Key(passphrase []byte) ([]byte, error) {
   122  	return scrypt.Key(passphrase, s.Salt, s.Params.N, s.Params.R, s.Params.P, boxKeySize)
   123  }
   124  
   125  // CheckParams checks that the encoded KDF parameters are what we expect them to
   126  // be. If we do not do this, an attacker could cause a DoS by tampering with
   127  // them.
   128  func (s *scryptKDF) CheckParams() error {
   129  	switch {
   130  	case legacyParams.Equal(&s.Params):
   131  	case standardParams.Equal(&s.Params):
   132  	case owaspParams.Equal(&s.Params):
   133  	default:
   134  		return errors.New("unsupported scrypt parameters")
   135  	}
   136  
   137  	return nil
   138  }
   139  
   140  func newSecretBoxCipher() (secretBoxCipher, error) {
   141  	nonce := make([]byte, boxNonceSize)
   142  	if err := fillRandom(nonce); err != nil {
   143  		return secretBoxCipher{}, err
   144  	}
   145  	return secretBoxCipher{
   146  		Name:  nameSecretBox,
   147  		Nonce: nonce,
   148  	}, nil
   149  }
   150  
   151  type secretBoxCipher struct {
   152  	Name  string `json:"name"`
   153  	Nonce []byte `json:"nonce"`
   154  
   155  	encrypted bool
   156  }
   157  
   158  func (s *secretBoxCipher) Encrypt(plaintext, key []byte) []byte {
   159  	var keyBytes [boxKeySize]byte
   160  	var nonceBytes [boxNonceSize]byte
   161  
   162  	if len(key) != len(keyBytes) {
   163  		panic("incorrect key size")
   164  	}
   165  	if len(s.Nonce) != len(nonceBytes) {
   166  		panic("incorrect nonce size")
   167  	}
   168  
   169  	copy(keyBytes[:], key)
   170  	copy(nonceBytes[:], s.Nonce)
   171  
   172  	// ensure that we don't re-use nonces
   173  	if s.encrypted {
   174  		panic("Encrypt must only be called once for each cipher instance")
   175  	}
   176  	s.encrypted = true
   177  
   178  	return secretbox.Seal(nil, plaintext, &nonceBytes, &keyBytes)
   179  }
   180  
   181  func (s *secretBoxCipher) Decrypt(ciphertext, key []byte) ([]byte, error) {
   182  	var keyBytes [boxKeySize]byte
   183  	var nonceBytes [boxNonceSize]byte
   184  
   185  	if len(key) != len(keyBytes) {
   186  		panic("incorrect key size")
   187  	}
   188  	if len(s.Nonce) != len(nonceBytes) {
   189  		// return an error instead of panicking since the nonce is user input
   190  		return nil, errors.New("encrypted: incorrect nonce size")
   191  	}
   192  
   193  	copy(keyBytes[:], key)
   194  	copy(nonceBytes[:], s.Nonce)
   195  
   196  	res, ok := secretbox.Open(nil, ciphertext, &nonceBytes, &keyBytes)
   197  	if !ok {
   198  		return nil, errors.New("encrypted: decryption failed")
   199  	}
   200  	return res, nil
   201  }
   202  
   203  // Encrypt takes a passphrase and plaintext, and returns a JSON object
   204  // containing ciphertext and the details necessary to decrypt it.
   205  func Encrypt(plaintext, passphrase []byte) ([]byte, error) {
   206  	return EncryptWithCustomKDFParameters(plaintext, passphrase, Standard)
   207  }
   208  
   209  // EncryptWithCustomKDFParameters takes a passphrase, the plaintext and a KDF
   210  // parameter level (Legacy, Standard, or OWASP), and returns a JSON object
   211  // containing ciphertext and the details necessary to decrypt it.
   212  func EncryptWithCustomKDFParameters(plaintext, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
   213  	k, err := newScryptKDF(kdfLevel)
   214  	if err != nil {
   215  		return nil, err
   216  	}
   217  	key, err := k.Key(passphrase)
   218  	if err != nil {
   219  		return nil, err
   220  	}
   221  
   222  	c, err := newSecretBoxCipher()
   223  	if err != nil {
   224  		return nil, err
   225  	}
   226  
   227  	data := &data{
   228  		KDF:    k,
   229  		Cipher: c,
   230  	}
   231  	data.Ciphertext = c.Encrypt(plaintext, key)
   232  
   233  	return json.Marshal(data)
   234  }
   235  
   236  // Marshal encrypts the JSON encoding of v using passphrase.
   237  func Marshal(v interface{}, passphrase []byte) ([]byte, error) {
   238  	return MarshalWithCustomKDFParameters(v, passphrase, Standard)
   239  }
   240  
   241  // MarshalWithCustomKDFParameters encrypts the JSON encoding of v using passphrase.
   242  func MarshalWithCustomKDFParameters(v interface{}, passphrase []byte, kdfLevel KDFParameterStrength) ([]byte, error) {
   243  	data, err := json.MarshalIndent(v, "", "\t")
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  	return EncryptWithCustomKDFParameters(data, passphrase, kdfLevel)
   248  }
   249  
   250  // Decrypt takes a JSON-encoded ciphertext object encrypted using Encrypt and
   251  // tries to decrypt it using passphrase. If successful, it returns the
   252  // plaintext.
   253  func Decrypt(ciphertext, passphrase []byte) ([]byte, error) {
   254  	data := &data{}
   255  	if err := json.Unmarshal(ciphertext, data); err != nil {
   256  		return nil, err
   257  	}
   258  
   259  	if data.KDF.Name != nameScrypt {
   260  		return nil, fmt.Errorf("encrypted: unknown kdf name %q", data.KDF.Name)
   261  	}
   262  	if data.Cipher.Name != nameSecretBox {
   263  		return nil, fmt.Errorf("encrypted: unknown cipher name %q", data.Cipher.Name)
   264  	}
   265  	if err := data.KDF.CheckParams(); err != nil {
   266  		return nil, err
   267  	}
   268  
   269  	key, err := data.KDF.Key(passphrase)
   270  	if err != nil {
   271  		return nil, err
   272  	}
   273  
   274  	return data.Cipher.Decrypt(data.Ciphertext, key)
   275  }
   276  
   277  // Unmarshal decrypts the data using passphrase and unmarshals the resulting
   278  // plaintext into the value pointed to by v.
   279  func Unmarshal(data []byte, v interface{}, passphrase []byte) error {
   280  	decrypted, err := Decrypt(data, passphrase)
   281  	if err != nil {
   282  		return err
   283  	}
   284  	return json.Unmarshal(decrypted, v)
   285  }
   286  
   287  func fillRandom(b []byte) error {
   288  	_, err := io.ReadFull(rand.Reader, b)
   289  	return err
   290  }
   291  

View as plain text