Encryption: Add support for decrypting ciphertexts with algorithm metadata (#49312)

This commit is contained in:
Joan López de la Franca Beltran
2022-05-20 17:11:51 +02:00
committed by GitHub
parent 258b3ab18b
commit 15605b6c80
2 changed files with 80 additions and 4 deletions
@@ -1,11 +1,13 @@
package ossencryption
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"errors"
"fmt"
"io"
@@ -22,11 +24,20 @@ func ProvideService() *Service {
return &Service{}
}
const saltLength = 8
const (
saltLength = 8
aesCfb = "aes-cfb"
encryptionAlgorithmDelimiter = '*'
)
func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]byte, error) {
alg, payload, err := deriveEncryptionAlgorithm(payload)
if err != nil {
return nil, err
}
if len(payload) < saltLength {
return nil, fmt.Errorf("unable to compute salt")
return nil, errors.New("unable to compute salt")
}
salt := payload[:saltLength]
key, err := encryptionKeyToBytes(secret, string(salt))
@@ -39,11 +50,49 @@ func (s *Service) Decrypt(_ context.Context, payload []byte, secret string) ([]b
return nil, err
}
// The IV needs to be unique, but not secure. Therefore it's common to
switch alg {
case aesCfb:
return decryptCFB(block, payload)
default:
return nil, errors.New("unsupported encryption algorithm")
}
}
func deriveEncryptionAlgorithm(payload []byte) (string, []byte, error) {
if len(payload) == 0 {
return "", nil, fmt.Errorf("unable to derive encryption algorithm")
}
if payload[0] != encryptionAlgorithmDelimiter {
return aesCfb, payload, nil // backwards compatibility
}
payload = payload[1:]
algDelim := bytes.Index(payload, []byte{encryptionAlgorithmDelimiter})
if algDelim == -1 {
return aesCfb, payload, nil // backwards compatibility
}
algB64 := payload[:algDelim]
payload = payload[algDelim+1:]
alg := make([]byte, base64.RawStdEncoding.DecodedLen(len(algB64)))
_, err := base64.RawStdEncoding.Decode(alg, algB64)
if err != nil {
return "", nil, err
}
return string(alg), payload, nil
}
func decryptCFB(block cipher.Block, payload []byte) ([]byte, error) {
// The IV needs to be unique, but not secure. Therefore, it's common to
// include it at the beginning of the ciphertext.
if len(payload) < aes.BlockSize {
return nil, errors.New("payload too short")
}
iv := payload[saltLength : saltLength+aes.BlockSize]
payload = payload[saltLength+aes.BlockSize:]
payloadDst := make([]byte, len(payload))
@@ -65,6 +114,7 @@ func (s *Service) Encrypt(_ context.Context, payload []byte, secret string) ([]b
if err != nil {
return nil, err
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err