@@ -3,8 +3,10 @@ package crypto
33import (
44 "crypto/aes"
55 "crypto/cipher"
6+ "crypto/hmac"
67 "crypto/rand"
78 "crypto/sha512"
9+ "encoding/base64"
810 "encoding/hex"
911 "errors"
1012 "io"
@@ -14,31 +16,34 @@ var (
1416 // ErrCipherTooShort occurs when `Decrypt` does not
1517 // have input of enough length to decrypt using AES256
1618 ErrCipherTooShort = errors .New ("crypto: cipher plainText is too short for AES encryption" )
19+ // ErrCorruptedMessage occurs when an attempt of unsealing a message
20+ // does not pass the authentication check
21+ ErrCorruptedMessage = errors .New ("crypto: the message didn't pass the authentication check" )
1722)
1823
1924// PassphraseToKey converts a string to a key for encryption.
2025//
2126// This function must be used STRICTLY ONLY for generating
2227// an encryption key out of a passphrase.
2328// Please don't use this function for hashing user-provided values.
24- // It uses SHA2 for simplicity but it's slower. User-provided data should use SHA3
25- // because of its better performance .
29+ // It uses SHA2 for simplicity and it's faster but less secure than SHA3.
30+ // User-provided data should use SHA3 or bcrypt .
2631func PassphraseToKey (passphrase string ) (key []byte ) {
2732 // SHA512/256 will return exactly 32 bytes which is exactly
2833 // the length of the key needed for AES256 encryption
2934 hash := sha512 .Sum512_256 ([]byte (passphrase ))
3035 return hash [:]
3136}
3237
33- // Encrypt encrypts content with a key using AES256
34- func Encrypt (plainText , key []byte ) (encrypted []byte , err error ) {
38+ // Encrypt encrypts content with a key using AES256 CFB mode
39+ func Encrypt (plainText , key []byte ) (cipherText []byte , err error ) {
3540 // code is taken from here https://golang.org/pkg/crypto/cipher/#NewCFBEncrypter
3641 block , err := aes .NewCipher (key )
3742 if err != nil {
3843 return nil , err
3944 }
4045
41- cipherText : = make ([]byte , aes .BlockSize + len (plainText ))
46+ cipherText = make ([]byte , aes .BlockSize + len (plainText ))
4247 iv := cipherText [:aes .BlockSize ]
4348 if _ , err := io .ReadFull (rand .Reader , iv ); err != nil {
4449 return nil , err
@@ -60,8 +65,8 @@ func EncryptToString(plainText, key []byte) (string, error) {
6065 return hex .EncodeToString (bytes ), nil
6166}
6267
63- // Decrypt decrypts content with a key using AES256
64- func Decrypt (cipherText , key []byte ) (decrypted []byte , err error ) {
68+ // Decrypt decrypts content with a key using AES256 CFB mode
69+ func Decrypt (cipherText , key []byte ) (plainText []byte , err error ) {
6570 // code is taken from here https://golang.org/pkg/crypto/cipher/#NewCFBDecrypter
6671 block , err := aes .NewCipher (key )
6772 if err != nil {
@@ -75,17 +80,78 @@ func Decrypt(cipherText, key []byte) (decrypted []byte, err error) {
7580
7681 stream := cipher .NewCFBDecrypter (block , iv )
7782
78- plainText : = make ([]byte , len (cipherText ))
83+ plainText = make ([]byte , len (cipherText ))
7984 stream .XORKeyStream (plainText , cipherText )
8085
8186 return plainText , nil
8287}
8388
8489// DecryptFromString decrypts a string with a key
85- func DecryptFromString (cipherTextStr string , key []byte ) (decrypted []byte , err error ) {
90+ func DecryptFromString (cipherTextStr string , key []byte ) ([]byte , error ) {
8691 cipherText , err := hex .DecodeString (cipherTextStr )
8792 if err != nil {
8893 return nil , err
8994 }
9095 return Decrypt (cipherText , key )
9196}
97+
98+ // Seal implements authenticated encryption using the MAC-then-Encrypt (MtE) approach.
99+ // It's using SHA3-256 for MAC and AES256 CFB for encryption.
100+ // https://en.wikipedia.org/wiki/Authenticated_encryption#MAC-then-Encrypt_(MtE)
101+ func Seal (plainText , key []byte ) (cipherText []byte , err error ) {
102+ mac := hmac .New (sha512 .New512_256 , key )
103+
104+ // the doc says it never returns an error, but we don't trust it
105+ _ , err = mac .Write (plainText )
106+ if err != nil {
107+ return nil , err
108+ }
109+ messageMAC := mac .Sum (nil )
110+ messageAndMAC := append (plainText , messageMAC ... )
111+
112+ return Encrypt (messageAndMAC , key )
113+ }
114+
115+ // SealToString runs `Seal` and then encodes the result into base64.
116+ func SealToString (plainText , key []byte ) (string , error ) {
117+ bytes , err := Seal (plainText , key )
118+ if err != nil {
119+ return "" , err
120+ }
121+ return base64 .StdEncoding .EncodeToString (bytes ), nil
122+ }
123+
124+ // Unseal decrypts and authenticates the data encrypted by Seal
125+ func Unseal (cipherText , key []byte ) (plainText []byte , err error ) {
126+ messageAndMAC , err := Decrypt (cipherText , key )
127+ if err != nil {
128+ return nil , err
129+ }
130+
131+ splitPoint := len (messageAndMAC ) - sha512 .Size256
132+ messageMAC := messageAndMAC [splitPoint :]
133+ plainText = messageAndMAC [:splitPoint ]
134+
135+ mac := hmac .New (sha512 .New512_256 , key )
136+ // the doc says it never returns an error, but we don't trust it
137+ _ , err = mac .Write (plainText )
138+ if err != nil {
139+ return nil , err
140+ }
141+ expectedMAC := mac .Sum (nil )
142+
143+ if ! hmac .Equal (expectedMAC , messageMAC ) {
144+ return nil , ErrCorruptedMessage
145+ }
146+
147+ return plainText , err
148+ }
149+
150+ // UnsealFromString decodes from Base64 and applies `Unseal`.
151+ func UnsealFromString (cipherTextStr string , key []byte ) ([]byte , error ) {
152+ cipherText , err := base64 .StdEncoding .DecodeString (cipherTextStr )
153+ if err != nil {
154+ return nil , err
155+ }
156+ return Unseal (cipherText , key )
157+ }
0 commit comments