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  )