123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 |
- package crypto
- import (
- "crypto/aes"
- "crypto/cipher"
- "crypto/rand"
- "encoding/base64"
- "errors"
- "io"
- )
- const (
- versionByte = 0x31 // "1"
- gcmTagSize = 16
- gcmNonceSize = 12
- )
- var (
- errCiphertextTooShort = errors.New("ciphertext too short")
- errCiphertextUnexpectedVersion = errors.New("unsupported ciphertext version")
- )
- // Encrypt encrypts the given plaintext with the given key using AES-GCM,
- // and encodes the (version, tag, nonce, ciphertext) set as base64.
- //
- // The output format is (|| means concatenate):
- // "1" || tag (128 bits) || IV/nonce (96 bits) || ciphertext (remaining)
- //
- // This format is compatible with Pushbullet's encryption format.
- // See https://docs.pushbullet.com/#encryption for details.
- func Encrypt(plaintext string, key []byte) (string, error) {
- nonce := make([]byte, gcmNonceSize) // Never use more than 2^32 random nonces
- if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
- return "", err
- }
- return encryptWithNonce(plaintext, nonce, key)
- }
- func encryptWithNonce(plaintext string, nonce, key []byte) (string, error) {
- block, err := aes.NewCipher(key)
- if err != nil {
- return "", err
- }
- aesgcm, err := cipher.NewGCM(block)
- if err != nil {
- return "", err
- }
- ciphertextWithTag := aesgcm.Seal(nil, nonce, []byte(plaintext), nil)
- tagIndex := len(ciphertextWithTag) - gcmTagSize
- ciphertext, tag := ciphertextWithTag[:tagIndex], ciphertextWithTag[tagIndex:]
- output := appendSlices([]byte{versionByte}, tag, nonce, ciphertext)
- return base64.StdEncoding.EncodeToString(output), nil
- }
- // Decrypt decodes and decrypts a message that was encrypted with the Encrypt function.
- func Decrypt(input string, key []byte) (string, error) {
- inputBytes, err := base64.StdEncoding.DecodeString(input)
- if err != nil {
- return "", err
- }
- if len(inputBytes) < 1+gcmTagSize+gcmNonceSize {
- return "", errCiphertextTooShort
- }
- version, tag, nonce, ciphertext := inputBytes[0], inputBytes[1:gcmTagSize+1], inputBytes[1+gcmTagSize:1+gcmTagSize+gcmNonceSize], inputBytes[1+gcmTagSize+gcmNonceSize:]
- if version != versionByte {
- return "", errCiphertextUnexpectedVersion
- }
- cipherTextWithTag := append(ciphertext, tag...)
- block, err := aes.NewCipher(key)
- if err != nil {
- return "", err
- }
- aesgcm, err := cipher.NewGCM(block)
- if err != nil {
- return "", err
- }
- plaintext, err := aesgcm.Open(nil, nonce, cipherTextWithTag, nil)
- if err != nil {
- return "", err
- }
- return string(plaintext), nil
- }
- func appendSlices(s ...[]byte) []byte {
- var output []byte
- for _, r := range s {
- output = append(output, r...)
- }
- return output
- }
|