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 }