github.com/slackhq/nebula@v1.9.0/cert/crypto.go (about)

     1  package cert
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/rand"
     7  	"fmt"
     8  	"io"
     9  
    10  	"golang.org/x/crypto/argon2"
    11  )
    12  
    13  // KDF factors
    14  type Argon2Parameters struct {
    15  	version     rune
    16  	Memory      uint32 // KiB
    17  	Parallelism uint8
    18  	Iterations  uint32
    19  	salt        []byte
    20  }
    21  
    22  // Returns a new Argon2Parameters object with current version set
    23  func NewArgon2Parameters(memory uint32, parallelism uint8, iterations uint32) *Argon2Parameters {
    24  	return &Argon2Parameters{
    25  		version:     argon2.Version,
    26  		Memory:      memory, // KiB
    27  		Parallelism: parallelism,
    28  		Iterations:  iterations,
    29  	}
    30  }
    31  
    32  // Encrypts data using AES-256-GCM and the Argon2id key derivation function
    33  func aes256Encrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
    34  	key, err := aes256DeriveKey(passphrase, kdfParams)
    35  	if err != nil {
    36  		return nil, err
    37  	}
    38  
    39  	// this should never happen, but since this dictates how our calls into the
    40  	// aes package behave and could be catastraphic, let's sanity check this
    41  	if len(key) != 32 {
    42  		return nil, fmt.Errorf("invalid AES-256 key length (%d) - cowardly refusing to encrypt", len(key))
    43  	}
    44  
    45  	block, err := aes.NewCipher(key)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	gcm, err := cipher.NewGCM(block)
    51  	if err != nil {
    52  		return nil, err
    53  	}
    54  
    55  	nonce := make([]byte, gcm.NonceSize())
    56  	if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	ciphertext := gcm.Seal(nil, nonce, data, nil)
    61  	blob := joinNonceCiphertext(nonce, ciphertext)
    62  
    63  	return blob, nil
    64  }
    65  
    66  // Decrypts data using AES-256-GCM and the Argon2id key derivation function
    67  // Expects the data to include an Argon2id parameter string before the encrypted data
    68  func aes256Decrypt(passphrase []byte, kdfParams *Argon2Parameters, data []byte) ([]byte, error) {
    69  	key, err := aes256DeriveKey(passphrase, kdfParams)
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  
    74  	block, err := aes.NewCipher(key)
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  
    79  	gcm, err := cipher.NewGCM(block)
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  
    84  	nonce, ciphertext, err := splitNonceCiphertext(data, gcm.NonceSize())
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  
    89  	plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
    90  	if err != nil {
    91  		return nil, fmt.Errorf("invalid passphrase or corrupt private key")
    92  	}
    93  
    94  	return plaintext, nil
    95  }
    96  
    97  func aes256DeriveKey(passphrase []byte, params *Argon2Parameters) ([]byte, error) {
    98  	if params.salt == nil {
    99  		params.salt = make([]byte, 32)
   100  		if _, err := rand.Read(params.salt); err != nil {
   101  			return nil, err
   102  		}
   103  	}
   104  
   105  	// keySize of 32 bytes will result in AES-256 encryption
   106  	key, err := deriveKey(passphrase, 32, params)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	return key, nil
   112  }
   113  
   114  // Derives a key from a passphrase using Argon2id
   115  func deriveKey(passphrase []byte, keySize uint32, params *Argon2Parameters) ([]byte, error) {
   116  	if params.version != argon2.Version {
   117  		return nil, fmt.Errorf("incompatible Argon2 version: %d", params.version)
   118  	}
   119  
   120  	if params.salt == nil {
   121  		return nil, fmt.Errorf("salt must be set in argon2Parameters")
   122  	} else if len(params.salt) < 16 {
   123  		return nil, fmt.Errorf("salt must be at least 128  bits")
   124  	}
   125  
   126  	key := argon2.IDKey(passphrase, params.salt, params.Iterations, params.Memory, params.Parallelism, keySize)
   127  
   128  	return key, nil
   129  }
   130  
   131  // Prepends nonce to ciphertext
   132  func joinNonceCiphertext(nonce []byte, ciphertext []byte) []byte {
   133  	return append(nonce, ciphertext...)
   134  }
   135  
   136  // Splits nonce from ciphertext
   137  func splitNonceCiphertext(blob []byte, nonceSize int) ([]byte, []byte, error) {
   138  	if len(blob) <= nonceSize {
   139  		return nil, nil, fmt.Errorf("invalid ciphertext blob - blob shorter than nonce length")
   140  	}
   141  
   142  	return blob[:nonceSize], blob[nonceSize:], nil
   143  }