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 }