mirror of
https://github.com/kopia/kopia.git
synced 2026-04-22 15:08:09 -04:00
crypto: deprecated crypto algorithms and replaced with better alternatives
New ciphers are using authenticated encryption with associated data
(AEAD) and per-content key derived using HMAC-SHA256:
* AES256-GCM-HMAC-SHA256
* CHACHA20-POLY1305-HMAC-SHA256
They support content IDs of arbitrary length and are quite fast:
On my 2019 MBP:
- BLAKE2B-256 + AES256-GCM-HMAC-SHA256 - 648.7 MiB / second
- BLAKE2B-256 + CHACHA20-POLY1305-HMAC-SHA256 - 597.1 MiB / second
- HMAC-SHA256 + AES256-GCM-HMAC-SHA256 351 MiB / second
- HMAC-SHA256 + CHACHA20-POLY1305-HMAC-SHA256 316.2 MiB / second
Previous ciphers had several subtle issues:
* SALSA20 encryption, used weak nonce (64 bit prefix of content ID),
which means that for any two contents, whose IDs that have the same
64-bit prefix, their plaintext can be decoded from the ciphertext
alone.
* AES-{128,192,256}-CTR were not authenticated, so we were
required to hash plaintext after decryption to validate. This is not
recommended due to possibility of subtle timing attacks if an attacker
controls the ciphertext.
* SALSA20-HMAC was only validating checksum and not that the ciphertext
was for the correct content ID.
New repositories cannot be created using deprecated ciphers, but they
will still be supported for existing repositories, until at least 0.6.0.
The users are encouraged to migrate to one of new ciphers when 0.5.0 is
out.
This commit is contained in:
@@ -13,10 +13,11 @@
|
||||
)
|
||||
|
||||
var (
|
||||
benchmarkCryptoCommand = benchmarkCommands.Command("crypto", "Run hash and encryption benchmarks")
|
||||
benchmarkCryptoBlockSize = benchmarkCryptoCommand.Flag("block-size", "Size of a block to encrypt").Default("1MB").Bytes()
|
||||
benchmarkCryptoEncryption = benchmarkCryptoCommand.Flag("encryption", "Test encrypted formats").Default("true").Bool()
|
||||
benchmarkCryptoRepeat = benchmarkCryptoCommand.Flag("repeat", "Number of repetitions").Default("100").Int()
|
||||
benchmarkCryptoCommand = benchmarkCommands.Command("crypto", "Run hash and encryption benchmarks")
|
||||
benchmarkCryptoBlockSize = benchmarkCryptoCommand.Flag("block-size", "Size of a block to encrypt").Default("1MB").Bytes()
|
||||
benchmarkCryptoEncryption = benchmarkCryptoCommand.Flag("encryption", "Test encrypted formats").Default("true").Bool()
|
||||
benchmarkCryptoRepeat = benchmarkCryptoCommand.Flag("repeat", "Number of repetitions").Default("100").Int()
|
||||
benchmarkCryptoDeprecatedAlgorithms = benchmarkCryptoCommand.Flag("deprecated", "Include deprecated algorithms").Bool()
|
||||
)
|
||||
|
||||
func runBenchmarkCryptoAction(ctx *kingpin.ParseContext) error {
|
||||
@@ -31,7 +32,7 @@ type benchResult struct {
|
||||
data := make([]byte, *benchmarkCryptoBlockSize)
|
||||
|
||||
for _, ha := range hashing.SupportedAlgorithms() {
|
||||
for _, ea := range encryption.SupportedAlgorithms() {
|
||||
for _, ea := range encryption.SupportedAlgorithms(*benchmarkCryptoDeprecatedAlgorithms) {
|
||||
isEncrypted := ea != encryption.NoneAlgorithm
|
||||
if *benchmarkCryptoEncryption != isEncrypted {
|
||||
continue
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
var (
|
||||
createCommand = repositoryCommands.Command("create", "Create new repository in a specified location.")
|
||||
|
||||
createBlockHashFormat = createCommand.Flag("block-hash", "Block hash algorithm.").PlaceHolder("ALGO").Default(hashing.DefaultAlgorithm).Enum(hashing.SupportedAlgorithms()...)
|
||||
createBlockEncryptionFormat = createCommand.Flag("encryption", "Block encryption algorithm.").PlaceHolder("ALGO").Default(encryption.DefaultAlgorithm).Enum(encryption.SupportedAlgorithms()...)
|
||||
createBlockHashFormat = createCommand.Flag("block-hash", "Content hash algorithm.").PlaceHolder("ALGO").Default(hashing.DefaultAlgorithm).Enum(hashing.SupportedAlgorithms()...)
|
||||
createBlockEncryptionFormat = createCommand.Flag("encryption", "Content encryption algorithm.").PlaceHolder("ALGO").Default(encryption.DefaultAlgorithm).Enum(encryption.SupportedAlgorithms(false)...)
|
||||
createSplitter = createCommand.Flag("object-splitter", "The splitter to use for new objects in the repository").Default(splitter.DefaultAlgorithm).Enum(splitter.SupportedAlgorithms()...)
|
||||
|
||||
createOnly = createCommand.Flag("create-only", "Create repository, but don't connect to it.").Short('c').Bool()
|
||||
|
||||
2
go.mod
2
go.mod
@@ -32,7 +32,7 @@ require (
|
||||
github.com/studio-b12/gowebdav v0.0.0-20190103184047-38f79aeaf1ac
|
||||
github.com/zalando/go-keyring v0.0.0-20190715212148-76787ff3b3bd
|
||||
gocloud.dev v0.18.0
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d
|
||||
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478
|
||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45
|
||||
|
||||
2
go.sum
2
go.sum
@@ -396,6 +396,8 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U
|
||||
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f h1:kz4KIr+xcPUsI3VMoqWfPMvtnJ6MGfiVwsWSVzphMO4=
|
||||
golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
|
||||
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
|
||||
@@ -121,7 +121,7 @@ func (s *Server) handleRepoSupportedAlgorithms(ctx context.Context, r *http.Requ
|
||||
HashAlgorithms: hashing.SupportedAlgorithms(),
|
||||
|
||||
DefaultEncryptionAlgorithm: encryption.DefaultAlgorithm,
|
||||
EncryptionAlgorithms: encryption.SupportedAlgorithms(),
|
||||
EncryptionAlgorithms: encryption.SupportedAlgorithms(false),
|
||||
|
||||
DefaultSplitterAlgorithm: splitter.DefaultAlgorithm,
|
||||
SplitterAlgorithms: splitter.SupportedAlgorithms(),
|
||||
|
||||
@@ -39,7 +39,7 @@ func TestFormatters(t *testing.T) {
|
||||
for _, hashAlgo := range hashing.SupportedAlgorithms() {
|
||||
hashAlgo := hashAlgo
|
||||
t.Run(hashAlgo, func(t *testing.T) {
|
||||
for _, encryptionAlgo := range encryption.SupportedAlgorithms() {
|
||||
for _, encryptionAlgo := range encryption.SupportedAlgorithms(true) {
|
||||
encryptionAlgo := encryptionAlgo
|
||||
t.Run(encryptionAlgo, func(t *testing.T) {
|
||||
ctx := testlogging.Context(t)
|
||||
|
||||
66
repo/encryption/aes256_gcm_hmac_sha256_encryptor.go
Normal file
66
repo/encryption/aes256_gcm_hmac_sha256_encryptor.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/aes"
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var zeroAES256GCMNonce = make([]byte, 12)
|
||||
|
||||
type aes256GCMHmacSha256 struct {
|
||||
keyDerivationSecret []byte
|
||||
}
|
||||
|
||||
// aeadForContent returns cipher.AEAD using key derived from a given contentID.
|
||||
func (e aes256GCMHmacSha256) aeadForContent(contentID []byte) (cipher.AEAD, error) {
|
||||
h := hmac.New(sha256.New, e.keyDerivationSecret)
|
||||
if _, err := h.Write(contentID); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to derive encryption key")
|
||||
}
|
||||
|
||||
key := h.Sum(nil)
|
||||
|
||||
c, err := aes.NewCipher(key)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to create AES-256 cipher")
|
||||
}
|
||||
|
||||
return cipher.NewGCM(c)
|
||||
}
|
||||
|
||||
func (e aes256GCMHmacSha256) Decrypt(input, contentID []byte) ([]byte, error) {
|
||||
a, err := e.aeadForContent(contentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.Open(nil, zeroAES256GCMNonce, input, contentID)
|
||||
}
|
||||
|
||||
func (e aes256GCMHmacSha256) Encrypt(input, contentID []byte) ([]byte, error) {
|
||||
a, err := e.aeadForContent(contentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.Seal(nil, zeroAES256GCMNonce, input, contentID), nil
|
||||
}
|
||||
|
||||
func (e aes256GCMHmacSha256) IsAuthenticated() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("AES256-GCM-HMAC-SHA256", "AES-256-GCM using per-content key generated using HMAC-SHA256", false, func(p Parameters) (Encryptor, error) {
|
||||
keyDerivationSecret, err := deriveKey(p, []byte(purposeEncryptionKey), 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return aes256GCMHmacSha256{keyDerivationSecret}, nil
|
||||
})
|
||||
}
|
||||
61
repo/encryption/chacha20_poly1305_hmac_sha256_encryptor.go
Normal file
61
repo/encryption/chacha20_poly1305_hmac_sha256_encryptor.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package encryption
|
||||
|
||||
import (
|
||||
"crypto/cipher"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/crypto/chacha20poly1305"
|
||||
)
|
||||
|
||||
var chacha20poly1305ZeroNonce = make([]byte, chacha20poly1305.NonceSize)
|
||||
|
||||
type chacha20poly1305hmacSha256Encryptor struct {
|
||||
keyDerivationSecret []byte
|
||||
}
|
||||
|
||||
// aeadForContent returns cipher.AEAD using key derived from a given contentID.
|
||||
func (e chacha20poly1305hmacSha256Encryptor) aeadForContent(contentID []byte) (cipher.AEAD, error) {
|
||||
h := hmac.New(sha256.New, e.keyDerivationSecret)
|
||||
if _, err := h.Write(contentID); err != nil {
|
||||
return nil, errors.Wrap(err, "unable to derive encryption key")
|
||||
}
|
||||
|
||||
key := h.Sum(nil)
|
||||
|
||||
return chacha20poly1305.New(key)
|
||||
}
|
||||
|
||||
func (e chacha20poly1305hmacSha256Encryptor) Decrypt(input, contentID []byte) ([]byte, error) {
|
||||
a, err := e.aeadForContent(contentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.Open(nil, chacha20poly1305ZeroNonce, input, contentID)
|
||||
}
|
||||
|
||||
func (e chacha20poly1305hmacSha256Encryptor) Encrypt(input, contentID []byte) ([]byte, error) {
|
||||
a, err := e.aeadForContent(contentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return a.Seal(nil, chacha20poly1305ZeroNonce, input, contentID), nil
|
||||
}
|
||||
|
||||
func (e chacha20poly1305hmacSha256Encryptor) IsAuthenticated() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("CHACHA20-POLY1305-HMAC-SHA256", "CHACHA20-POLY1305 using per-content key generated using HMAC-SHA256", false, func(p Parameters) (Encryptor, error) {
|
||||
keyDerivationSecret, err := deriveKey(p, []byte(purposeEncryptionKey), 32)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chacha20poly1305hmacSha256Encryptor{keyDerivationSecret}, nil
|
||||
})
|
||||
}
|
||||
@@ -70,7 +70,7 @@ func newCTREncryptorFactory(keySize int, createCipherWithKey func(key []byte) (c
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("AES-128-CTR", "AES-128 in CTR mode", false, newCTREncryptorFactory(16, aes.NewCipher)) //nolint:gomnd
|
||||
Register("AES-192-CTR", "AES-192 in CTR mode", false, newCTREncryptorFactory(24, aes.NewCipher)) //nolint:gomnd
|
||||
Register("AES-256-CTR", "AES-256 in CTR mode", false, newCTREncryptorFactory(32, aes.NewCipher)) //nolint:gomnd
|
||||
Register("AES-128-CTR", "DEPRECATED: AES-128 in CTR mode", true, newCTREncryptorFactory(16, aes.NewCipher)) //nolint:gomnd
|
||||
Register("AES-192-CTR", "DEPRECATED: AES-192 in CTR mode", true, newCTREncryptorFactory(24, aes.NewCipher)) //nolint:gomnd
|
||||
Register("AES-256-CTR", "DEPRECATED: AES-256 in CTR mode", true, newCTREncryptorFactory(32, aes.NewCipher)) //nolint:gomnd
|
||||
}
|
||||
@@ -63,15 +63,21 @@ func (s salsaEncryptor) encryptDecrypt(input, contentID []byte) ([]byte, error)
|
||||
}
|
||||
|
||||
func init() {
|
||||
Register("SALSA20", "SALSA20 using shared key and 64-bit nonce", true, func(p Parameters) (Encryptor, error) {
|
||||
Register("SALSA20", "DEPRECATED: SALSA20 using shared key and 64-bit nonce", true, func(p Parameters) (Encryptor, error) {
|
||||
var k [salsaKeyLength]byte
|
||||
copy(k[:], p.GetMasterKey()[0:salsaKeyLength])
|
||||
return salsaEncryptor{8, &k, nil}, nil
|
||||
})
|
||||
|
||||
Register("SALSA20-HMAC", "SALSA20 with HMAC-SHA256 using shared key and 64-bit nonce", true, func(p Parameters) (Encryptor, error) {
|
||||
encryptionKey := deriveKey(p, []byte(purposeEncryptionKey), salsaKeyLength)
|
||||
hmacSecret := deriveKey(p, []byte(purposeHMACSecret), hmacLength)
|
||||
Register("SALSA20-HMAC", "DEPRECATED: SALSA20 with HMAC-SHA256 using shared key and 64-bit nonce", true, func(p Parameters) (Encryptor, error) {
|
||||
encryptionKey, err := deriveKey(p, []byte(purposeEncryptionKey), salsaKeyLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hmacSecret, err := deriveKey(p, []byte(purposeHMACSecret), hmacLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var k [salsaKeyLength]byte
|
||||
copy(k[:], encryptionKey)
|
||||
@@ -10,6 +10,8 @@
|
||||
"golang.org/x/crypto/hkdf"
|
||||
)
|
||||
|
||||
const minDerivedKeyLength = 32
|
||||
|
||||
// Encryptor performs encryption and decryption of contents of data.
|
||||
type Encryptor interface {
|
||||
// Encrypt returns encrypted bytes corresponding to the given plaintext.
|
||||
@@ -46,16 +48,21 @@ func CreateEncryptor(p Parameters) (Encryptor, error) {
|
||||
type EncryptorFactory func(p Parameters) (Encryptor, error)
|
||||
|
||||
// DefaultAlgorithm is the name of the default encryption algorithm.
|
||||
const DefaultAlgorithm = "SALSA20-HMAC"
|
||||
const DefaultAlgorithm = "AES256-GCM-HMAC-SHA256"
|
||||
|
||||
// NoneAlgorithm is the name of the algorithm that does not encrypt.
|
||||
const NoneAlgorithm = "NONE"
|
||||
|
||||
// SupportedAlgorithms returns the names of the supported encryption
|
||||
// methods
|
||||
func SupportedAlgorithms() []string {
|
||||
func SupportedAlgorithms(includeDeprecated bool) []string {
|
||||
var result []string
|
||||
for k := range encryptors {
|
||||
|
||||
for k, e := range encryptors {
|
||||
if e.deprecated && !includeDeprecated {
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, k)
|
||||
}
|
||||
|
||||
@@ -87,10 +94,14 @@ func cloneBytes(b []byte) []byte {
|
||||
|
||||
// deriveKey uses HKDF to derive a key of a given length and a given purpose from parameters.
|
||||
// nolint:unparam
|
||||
func deriveKey(p Parameters, purpose []byte, length int) []byte {
|
||||
func deriveKey(p Parameters, purpose []byte, length int) ([]byte, error) {
|
||||
if length < minDerivedKeyLength {
|
||||
return nil, errors.Errorf("derived key must be at least 32 bytes, was %v", length)
|
||||
}
|
||||
|
||||
key := make([]byte, length)
|
||||
k := hkdf.New(sha256.New, p.GetMasterKey(), purpose, nil)
|
||||
io.ReadFull(k, key) //nolint:errcheck
|
||||
|
||||
return key
|
||||
return key, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
mathrand "math/rand"
|
||||
"testing"
|
||||
|
||||
"github.com/kopia/kopia/repo/encryption"
|
||||
@@ -30,7 +32,7 @@ func TestRoundTrip(t *testing.T) {
|
||||
contentID2 := make([]byte, 16)
|
||||
rand.Read(contentID2) //nolint:errcheck
|
||||
|
||||
for _, encryptionAlgo := range encryption.SupportedAlgorithms() {
|
||||
for _, encryptionAlgo := range encryption.SupportedAlgorithms(true) {
|
||||
encryptionAlgo := encryptionAlgo
|
||||
t.Run(encryptionAlgo, func(t *testing.T) {
|
||||
e, err := encryption.CreateEncryptor(parameters{encryptionAlgo, masterKey})
|
||||
@@ -73,14 +75,115 @@ func TestRoundTrip(t *testing.T) {
|
||||
|
||||
// decrypt using wrong content ID
|
||||
badPlainText2, err := e.Decrypt(cipherText2, contentID1)
|
||||
if err != nil || plainText2 == nil {
|
||||
t.Errorf("invalid response from Decrypt: %v %v", plainText2, err)
|
||||
if e.IsAuthenticated() {
|
||||
if err == nil && encryptionAlgo != "SALSA20-HMAC" {
|
||||
// "SALSA20-HMAC" is deprecated & wrong, and only validates that checksum is
|
||||
// valid for some content, but does not validate that we decrypted the
|
||||
// intended content.
|
||||
t.Errorf("expected decrypt to fail for authenticated encryption")
|
||||
}
|
||||
} else {
|
||||
if bytes.Equal(badPlainText2, plainText2) {
|
||||
t.Errorf("decrypted plaintext matches, but it should not: %x", plainText2)
|
||||
}
|
||||
}
|
||||
|
||||
if bytes.Equal(badPlainText2, plainText2) {
|
||||
t.Errorf("decrypted plaintext matches, but it should not: %x", plainText2)
|
||||
// flip some bits in the cipherText
|
||||
if e.IsAuthenticated() {
|
||||
cipherText2[mathrand.Intn(len(cipherText2))] ^= byte(1 + mathrand.Intn(254))
|
||||
if _, err := e.Decrypt(cipherText2, contentID1); err == nil {
|
||||
t.Errorf("expected decrypt failure on invalid ciphertext, got success")
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCiphertextSamples(t *testing.T) {
|
||||
cases := []struct {
|
||||
masterKey []byte
|
||||
contentID []byte
|
||||
payload []byte
|
||||
samples map[string]string
|
||||
}{
|
||||
{
|
||||
masterKey: []byte("01234567890123456789012345678901"), // 32 bytes
|
||||
contentID: []byte("aabbccddeeffgghhiijjkkllmmnnoopp"), // 32 bytes
|
||||
payload: []byte("foo"),
|
||||
|
||||
// samples of base16-encoded ciphertexts of payload encrypted with masterKey & contentID
|
||||
samples: map[string]string{
|
||||
"NONE": hex.EncodeToString([]byte("foo")),
|
||||
|
||||
"AES256-GCM-HMAC-SHA256": "785c71de7c8ae8a5c0b5e2ad03f0be21620329",
|
||||
"CHACHA20-POLY1305-HMAC-SHA256": "c93d644c5de803f017cad8ca331b7331e4cf55",
|
||||
|
||||
// deprecated
|
||||
"AES-128-CTR": "54cd8d",
|
||||
"AES-192-CTR": "2d084b",
|
||||
"AES-256-CTR": "8a580a",
|
||||
"SALSA20": "bf5ec3",
|
||||
"SALSA20-HMAC": "8bf37fd9ec69843c3c2ac2a2cfdd59f36077206a15289efde640d0e677d03e6ac8f8ec",
|
||||
},
|
||||
},
|
||||
{
|
||||
masterKey: []byte("01234567890123456789012345678901"), // 32 bytes
|
||||
contentID: []byte("00000000000000000000000000000000"), // 32 bytes
|
||||
payload: []byte("quick brown fox jumps over the lazy dog"),
|
||||
|
||||
// samples of base16-encoded ciphertexts of payload encrypted with masterKey & contentID
|
||||
samples: map[string]string{
|
||||
"NONE": hex.EncodeToString([]byte("quick brown fox jumps over the lazy dog")),
|
||||
|
||||
"AES256-GCM-HMAC-SHA256": "e485b1f970e5d31f74b81c5b6336c3c5ef0de8f507943ce402b8ad3f282b8fd2e0b2554b13d0274ae088e119e2823f435bff9723b8201d",
|
||||
"CHACHA20-POLY1305-HMAC-SHA256": "3e539c14afbcb990a546404bd0f0cb4d92c7d56593e04338dbb035aa38a75df37fcc42ebbe348ef13a1a40afcb55b1e2e3834b529388c4",
|
||||
|
||||
// deprecated
|
||||
"AES-128-CTR": "974c5c1782076e3de7255deabe8706a509b5772a8b7a8e7f83d01de7098c945934417071ec5351",
|
||||
"AES-192-CTR": "1200e755ec14125e87136b5281957895eeb429be673b2241da261f949283aea59fd2fa64387764",
|
||||
"AES-256-CTR": "39f13367828efb5fb22b97865ca0dbaad352d0c1a3083ff056bc771b812239445ed8af022f3760",
|
||||
"SALSA20": "65ce12b14739aecbf9e6a9b9b9c4a72ffa8886fe0b071c0abdfb3d3e5c336b90f9af411ba69faf",
|
||||
"SALSA20-HMAC": "a1dc47f250def4d97a422d505fb5e9a9a13699762cb32cfe7705982fa68ce71f54544ab932a1045fb0601087159954d563f0de0aaa15690d93ea63748bf91889e577daeeed5cf8",
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range cases {
|
||||
verifyCiphertextSamples(t, tc.masterKey, tc.contentID, tc.payload, tc.samples)
|
||||
}
|
||||
}
|
||||
|
||||
func verifyCiphertextSamples(t *testing.T, masterKey, contentID, payload []byte, samples map[string]string) {
|
||||
for _, encryptionAlgo := range encryption.SupportedAlgorithms(true) {
|
||||
enc, err := encryption.CreateEncryptor(parameters{encryptionAlgo, masterKey})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
ct := samples[encryptionAlgo]
|
||||
if ct == "" {
|
||||
v, err := enc.Encrypt(payload, contentID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Errorf("missing ciphertext sample for %q: %q,", encryptionAlgo, hex.EncodeToString(v))
|
||||
} else {
|
||||
b, err := hex.DecodeString(ct)
|
||||
if err != nil {
|
||||
t.Errorf("invalid ciphertext for %v: %v", encryptionAlgo, err)
|
||||
continue
|
||||
}
|
||||
|
||||
plainText, err := enc.Decrypt(b, contentID)
|
||||
if err != nil {
|
||||
t.Errorf("unable to decrypt %v: %v", encryptionAlgo, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(plainText, payload) {
|
||||
t.Errorf("invalid plaintext after decryption %x, want %x", plainText, payload)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user