github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/crypto/aes.go (about)

     1  package crypto
     2  
     3  import (
     4  	"crypto/aes"
     5  	"crypto/cipher"
     6  	"crypto/hmac"
     7  	"crypto/sha256"
     8  	"crypto/subtle"
     9  	"encoding/base64"
    10  	"encoding/binary"
    11  	"errors"
    12  )
    13  
    14  func addPadding(payload []byte) []byte {
    15  	l := len(payload)
    16  	p := aes.BlockSize - (l % aes.BlockSize)
    17  	padded := make([]byte, l+p)
    18  	copy(padded, payload)
    19  	for i := 0; i < p; i++ {
    20  		padded[l+i] = byte(p)
    21  	}
    22  	return padded
    23  }
    24  
    25  func encryptAES256(key, payload, iv []byte) ([]byte, error) {
    26  	block, err := aes.NewCipher(key)
    27  	if err != nil {
    28  		return nil, err
    29  	}
    30  	dst := make([]byte, len(payload))
    31  	mode := cipher.NewCBCEncrypter(block, iv)
    32  	mode.CryptBlocks(dst, payload)
    33  	return dst, nil
    34  }
    35  
    36  // EncryptWithAES256CBC uses AES-256-CBC to encrypt the payload, and returns a
    37  // bitwarden cipher string.
    38  // See https://github.com/jcs/rubywarden/blob/master/API.md#example
    39  func EncryptWithAES256CBC(key, payload, iv []byte) (string, error) {
    40  	payload = addPadding(payload)
    41  	dst, err := encryptAES256(key, payload, iv)
    42  	if err != nil {
    43  		return "", err
    44  	}
    45  	iv64 := base64.StdEncoding.EncodeToString(iv)
    46  	dst64 := base64.StdEncoding.EncodeToString(dst)
    47  
    48  	// 0 means AesCbc256_B64
    49  	cipherString := "0." + iv64 + "|" + dst64
    50  	return cipherString, nil
    51  }
    52  
    53  // EncryptWithAES256HMAC uses AES-256-CBC with HMAC SHA-256 to encrypt the
    54  // payload, and returns a bitwarden cipher string.
    55  func EncryptWithAES256HMAC(encKey, macKey, payload, iv []byte) (string, error) {
    56  	payload = addPadding(payload)
    57  	dst, err := encryptAES256(encKey, payload, iv)
    58  	if err != nil {
    59  		return "", err
    60  	}
    61  	iv64 := base64.StdEncoding.EncodeToString(iv)
    62  	dst64 := base64.StdEncoding.EncodeToString(dst)
    63  
    64  	hash := hmac.New(sha256.New, macKey)
    65  	if _, err := hash.Write(iv); err != nil {
    66  		return "", err
    67  	}
    68  	if _, err := hash.Write(dst); err != nil {
    69  		return "", err
    70  	}
    71  	h64 := base64.StdEncoding.EncodeToString(hash.Sum(nil))
    72  
    73  	// 2 means AesCbc256_HmacSha256_B64
    74  	cipherString := "2." + iv64 + "|" + dst64 + "|" + h64
    75  	return cipherString, nil
    76  }
    77  
    78  // UnwrapA256KW decrypts the provided cipher text with the given AES cipher
    79  // (and corresponding key), using the AES Key Wrap algorithm (RFC-3394). The
    80  // decrypted cipher text is verified using the default IV and will return an
    81  // error if validation fails.
    82  //
    83  // Taken from https://github.com/NickBall/go-aes-key-wrap/blob/master/keywrap.go
    84  func UnwrapA256KW(block cipher.Block, cipherText []byte) ([]byte, error) {
    85  	a := make([]byte, 8)
    86  	n := (len(cipherText) / 8) - 1
    87  
    88  	r := make([][]byte, n)
    89  	for i := range r {
    90  		r[i] = make([]byte, 8)
    91  		copy(r[i], cipherText[(i+1)*8:])
    92  	}
    93  	copy(a, cipherText[:8])
    94  
    95  	for j := 5; j >= 0; j-- {
    96  		for i := n; i >= 1; i-- {
    97  			t := (n * j) + i
    98  			tBytes := make([]byte, 8)
    99  			binary.BigEndian.PutUint64(tBytes, uint64(t))
   100  
   101  			b := arrConcat(arrXor(a, tBytes), r[i-1])
   102  			block.Decrypt(b, b)
   103  
   104  			copy(a, b[:len(b)/2])
   105  			copy(r[i-1], b[len(b)/2:])
   106  		}
   107  	}
   108  
   109  	if subtle.ConstantTimeCompare(a, defaultIV) != 1 {
   110  		return nil, errors.New("integrity check failed - unexpected IV")
   111  	}
   112  
   113  	c := arrConcat(r...)
   114  	return c, nil
   115  }
   116  
   117  // defaultIV as specified in RFC-3394
   118  var defaultIV = []byte{0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6, 0xA6}
   119  
   120  func arrConcat(arrays ...[]byte) []byte {
   121  	out := make([]byte, len(arrays[0]))
   122  	copy(out, arrays[0])
   123  	for _, array := range arrays[1:] {
   124  		out = append(out, array...)
   125  	}
   126  	return out
   127  }
   128  
   129  func arrXor(arrL []byte, arrR []byte) []byte {
   130  	out := make([]byte, len(arrL))
   131  	for x := range arrL {
   132  		out[x] = arrL[x] ^ arrR[x]
   133  	}
   134  	return out
   135  }