github.com/minio/madmin-go@v1.7.5/encrypt.go (about) 1 // 2 // MinIO Object Storage (c) 2021 MinIO, Inc. 3 // 4 // Licensed under the Apache License, Version 2.0 (the "License"); 5 // you may not use this file except in compliance with the License. 6 // You may obtain a copy of the License at 7 // 8 // http://www.apache.org/licenses/LICENSE-2.0 9 // 10 // Unless required by applicable law or agreed to in writing, software 11 // distributed under the License is distributed on an "AS IS" BASIS, 12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 // See the License for the specific language governing permissions and 14 // limitations under the License. 15 // 16 17 package madmin 18 19 import ( 20 "bytes" 21 "crypto/sha256" 22 "errors" 23 "io" 24 "io/ioutil" 25 26 "github.com/secure-io/sio-go" 27 "github.com/secure-io/sio-go/sioutil" 28 "golang.org/x/crypto/argon2" 29 "golang.org/x/crypto/pbkdf2" 30 ) 31 32 // IsEncrypted reports whether data is encrypted. 33 func IsEncrypted(data []byte) bool { 34 if len(data) <= 32 { 35 return false 36 } 37 b := data[32] 38 return b == pbkdf2AESGCM || b == argon2idAESGCM || b == argon2idChaCHa20Poly1305 39 } 40 41 // EncryptData encrypts the data with an unique key 42 // derived from password using the Argon2id PBKDF. 43 // 44 // The returned ciphertext data consists of: 45 // 46 // salt | AEAD ID | nonce | encrypted data 47 // 32 1 8 ~ len(data) 48 func EncryptData(password string, data []byte) ([]byte, error) { 49 salt := sioutil.MustRandom(32) 50 51 var ( 52 id byte 53 err error 54 stream *sio.Stream 55 ) 56 if FIPSEnabled() { 57 key := pbkdf2.Key([]byte(password), salt, pbkdf2Cost, 32, sha256.New) 58 stream, err = sio.AES_256_GCM.Stream(key) 59 if err != nil { 60 return nil, err 61 } 62 id = pbkdf2AESGCM 63 } else { 64 key := argon2.IDKey([]byte(password), salt, argon2idTime, argon2idMemory, argon2idThreads, 32) 65 if sioutil.NativeAES() { 66 stream, err = sio.AES_256_GCM.Stream(key) 67 if err != nil { 68 return nil, err 69 } 70 id = argon2idAESGCM 71 } else { 72 stream, err = sio.ChaCha20Poly1305.Stream(key) 73 if err != nil { 74 return nil, err 75 } 76 id = argon2idChaCHa20Poly1305 77 } 78 } 79 80 nonce := sioutil.MustRandom(stream.NonceSize()) 81 82 // ciphertext = salt || AEAD ID | nonce | encrypted data 83 cLen := int64(len(salt)+1+len(nonce)+len(data)) + stream.Overhead(int64(len(data))) 84 ciphertext := bytes.NewBuffer(make([]byte, 0, cLen)) // pre-alloc correct length 85 86 // Prefix the ciphertext with salt, AEAD ID and nonce 87 ciphertext.Write(salt) 88 ciphertext.WriteByte(id) 89 ciphertext.Write(nonce) 90 91 w := stream.EncryptWriter(ciphertext, nonce, nil) 92 if _, err = w.Write(data); err != nil { 93 return nil, err 94 } 95 if err = w.Close(); err != nil { 96 return nil, err 97 } 98 return ciphertext.Bytes(), nil 99 } 100 101 // ErrMaliciousData indicates that the stream cannot be 102 // decrypted by provided credentials. 103 var ErrMaliciousData = sio.NotAuthentic 104 105 // DecryptData decrypts the data with the key derived 106 // from the salt (part of data) and the password using 107 // the PBKDF used in EncryptData. DecryptData returns 108 // the decrypted plaintext on success. 109 // 110 // The data must be a valid ciphertext produced by 111 // EncryptData. Otherwise, the decryption will fail. 112 func DecryptData(password string, data io.Reader) ([]byte, error) { 113 var ( 114 salt [32]byte 115 id [1]byte 116 nonce [8]byte // This depends on the AEAD but both used ciphers have the same nonce length. 117 ) 118 119 if _, err := io.ReadFull(data, salt[:]); err != nil { 120 return nil, err 121 } 122 if _, err := io.ReadFull(data, id[:]); err != nil { 123 return nil, err 124 } 125 if _, err := io.ReadFull(data, nonce[:]); err != nil { 126 return nil, err 127 } 128 129 var ( 130 err error 131 stream *sio.Stream 132 ) 133 switch { 134 case id[0] == argon2idAESGCM: 135 key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) 136 stream, err = sio.AES_256_GCM.Stream(key) 137 case id[0] == argon2idChaCHa20Poly1305: 138 key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) 139 stream, err = sio.ChaCha20Poly1305.Stream(key) 140 case id[0] == pbkdf2AESGCM: 141 key := pbkdf2.Key([]byte(password), salt[:], pbkdf2Cost, 32, sha256.New) 142 stream, err = sio.AES_256_GCM.Stream(key) 143 default: 144 err = errors.New("madmin: invalid encryption algorithm ID") 145 } 146 if err != nil { 147 return nil, err 148 } 149 150 plaintext, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil)) 151 if err != nil { 152 return nil, err 153 } 154 return plaintext, err 155 } 156 157 const ( 158 argon2idAESGCM = 0x00 159 argon2idChaCHa20Poly1305 = 0x01 160 pbkdf2AESGCM = 0x02 161 ) 162 163 const ( 164 argon2idTime = 1 165 argon2idMemory = 64 * 1024 166 argon2idThreads = 4 167 pbkdf2Cost = 8192 168 )