storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/madmin/encrypt.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018-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 18 package madmin 19 20 import ( 21 "bytes" 22 "crypto/sha256" 23 "errors" 24 "io" 25 "io/ioutil" 26 27 "github.com/secure-io/sio-go" 28 "github.com/secure-io/sio-go/sioutil" 29 "golang.org/x/crypto/pbkdf2" 30 31 "storj.io/minio/pkg/argon2" 32 "storj.io/minio/pkg/fips" 33 ) 34 35 // EncryptData encrypts the data with an unique key 36 // derived from password using the Argon2id PBKDF. 37 // 38 // The returned ciphertext data consists of: 39 // salt | AEAD ID | nonce | encrypted data 40 // 32 1 8 ~ len(data) 41 func EncryptData(password string, data []byte) ([]byte, error) { 42 salt := sioutil.MustRandom(32) 43 44 var ( 45 id byte 46 err error 47 stream *sio.Stream 48 ) 49 if fips.Enabled() { 50 key := pbkdf2.Key([]byte(password), salt, pbkdf2Cost, 32, sha256.New) 51 stream, err = sio.AES_256_GCM.Stream(key) 52 if err != nil { 53 return nil, err 54 } 55 id = pbkdf2AESGCM 56 } else { 57 key := argon2.IDKey([]byte(password), salt, argon2idTime, argon2idMemory, argon2idThreads, 32) 58 if sioutil.NativeAES() { 59 stream, err = sio.AES_256_GCM.Stream(key) 60 if err != nil { 61 return nil, err 62 } 63 id = argon2idAESGCM 64 } else { 65 stream, err = sio.ChaCha20Poly1305.Stream(key) 66 if err != nil { 67 return nil, err 68 } 69 id = argon2idChaCHa20Poly1305 70 } 71 } 72 73 nonce := sioutil.MustRandom(stream.NonceSize()) 74 75 // ciphertext = salt || AEAD ID | nonce | encrypted data 76 cLen := int64(len(salt)+1+len(nonce)+len(data)) + stream.Overhead(int64(len(data))) 77 ciphertext := bytes.NewBuffer(make([]byte, 0, cLen)) // pre-alloc correct length 78 79 // Prefix the ciphertext with salt, AEAD ID and nonce 80 ciphertext.Write(salt) 81 ciphertext.WriteByte(id) 82 ciphertext.Write(nonce) 83 84 w := stream.EncryptWriter(ciphertext, nonce, nil) 85 if _, err = w.Write(data); err != nil { 86 return nil, err 87 } 88 if err = w.Close(); err != nil { 89 return nil, err 90 } 91 return ciphertext.Bytes(), nil 92 } 93 94 // ErrMaliciousData indicates that the stream cannot be 95 // decrypted by provided credentials. 96 var ErrMaliciousData = sio.NotAuthentic 97 98 // DecryptData decrypts the data with the key derived 99 // from the salt (part of data) and the password using 100 // the PBKDF used in EncryptData. DecryptData returns 101 // the decrypted plaintext on success. 102 // 103 // The data must be a valid ciphertext produced by 104 // EncryptData. Otherwise, the decryption will fail. 105 func DecryptData(password string, data io.Reader) ([]byte, error) { 106 var ( 107 salt [32]byte 108 id [1]byte 109 nonce [8]byte // This depends on the AEAD but both used ciphers have the same nonce length. 110 ) 111 112 if _, err := io.ReadFull(data, salt[:]); err != nil { 113 return nil, err 114 } 115 if _, err := io.ReadFull(data, id[:]); err != nil { 116 return nil, err 117 } 118 if _, err := io.ReadFull(data, nonce[:]); err != nil { 119 return nil, err 120 } 121 122 var ( 123 err error 124 stream *sio.Stream 125 ) 126 switch { 127 case id[0] == argon2idAESGCM: 128 key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) 129 stream, err = sio.AES_256_GCM.Stream(key) 130 case id[0] == argon2idChaCHa20Poly1305: 131 key := argon2.IDKey([]byte(password), salt[:], argon2idTime, argon2idMemory, argon2idThreads, 32) 132 stream, err = sio.ChaCha20Poly1305.Stream(key) 133 case id[0] == pbkdf2AESGCM: 134 key := pbkdf2.Key([]byte(password), salt[:], pbkdf2Cost, 32, sha256.New) 135 stream, err = sio.AES_256_GCM.Stream(key) 136 default: 137 err = errors.New("madmin: invalid encryption algorithm ID") 138 } 139 if err != nil { 140 return nil, err 141 } 142 143 plaintext, err := ioutil.ReadAll(stream.DecryptReader(data, nonce[:], nil)) 144 if err != nil { 145 return nil, err 146 } 147 return plaintext, err 148 } 149 150 const ( 151 argon2idAESGCM = 0x00 152 argon2idChaCHa20Poly1305 = 0x01 153 pbkdf2AESGCM = 0x02 154 ) 155 156 const ( 157 argon2idTime = 1 158 argon2idMemory = 64 * 1024 159 argon2idThreads = 4 160 pbkdf2Cost = 8192 161 )