Merge pull request #1696 from hashicorp/transit-convergent-specify-nonce

Require nonce specification for more flexibility
This commit is contained in:
Jeff Mitchell 2016-08-08 11:41:10 -04:00 committed by GitHub
commit 0a67bcb5bd
10 changed files with 204 additions and 89 deletions

View file

@ -29,9 +29,10 @@ FEATURES:
deprecates App-ID. [GH-1426] deprecates App-ID. [GH-1426]
* **Convergent Encryption in `Transit`**: The `transit` backend now supports a * **Convergent Encryption in `Transit`**: The `transit` backend now supports a
convergent encryption mode where the same plaintext will produce the same convergent encryption mode where the same plaintext will produce the same
ciphertext. Although very useful in some situations, this has security ciphertext. Although very useful in some situations, this has potential
implications, which are mostly mitigated by requiring the use of key security implications, which are mostly mitigated by requiring the use of
derivation when convergent encryption is enabled. See [the `transit` key derivation when convergent encryption is enabled. See [the `transit`
backend
documentation](https://www.vaultproject.io/docs/secrets/transit/index.html) documentation](https://www.vaultproject.io/docs/secrets/transit/index.html)
for more details. [GH-1537] for more details. [GH-1537]
* **Improved LDAP Group Filters**: The `ldap` auth backend now uses templates * **Improved LDAP Group Filters**: The `ldap` auth backend now uses templates

View file

@ -56,19 +56,6 @@ func TestBackend_upsert(t *testing.T) {
}) })
} }
func TestBackend_upsert_convergent(t *testing.T) {
decryptData := make(map[string]interface{})
logicaltest.Test(t, logicaltest.TestCase{
Factory: Factory,
Steps: []logicaltest.TestStep{
testAccStepReadPolicy(t, "test", true, false),
testAccStepEncryptUpsertConvergent(t, "test", testPlaintext, decryptData),
testAccStepReadPolicy(t, "test", false, false),
testAccStepDecrypt(t, "test", testPlaintext, decryptData),
},
})
}
func TestBackend_datakey(t *testing.T) { func TestBackend_datakey(t *testing.T) {
dataKeyInfo := make(map[string]interface{}) dataKeyInfo := make(map[string]interface{})
logicaltest.Test(t, logicaltest.TestCase{ logicaltest.Test(t, logicaltest.TestCase{
@ -323,30 +310,6 @@ func testAccStepEncryptUpsert(
} }
} }
func testAccStepEncryptUpsertConvergent(
t *testing.T, name, plaintext string, decryptData map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{
Operation: logical.CreateOperation,
Path: "encrypt/" + name,
Data: map[string]interface{}{
"plaintext": base64.StdEncoding.EncodeToString([]byte(plaintext)),
},
Check: func(resp *logical.Response) error {
var d struct {
Ciphertext string `mapstructure:"ciphertext"`
}
if err := mapstructure.Decode(resp.Data, &d); err != nil {
return err
}
if d.Ciphertext == "" {
return fmt.Errorf("missing ciphertext")
}
decryptData["ciphertext"] = d.Ciphertext
return nil
},
}
}
func testAccStepEncryptContext( func testAccStepEncryptContext(
t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep { t *testing.T, name, plaintext, context string, decryptData map[string]interface{}) logicaltest.TestStep {
return logicaltest.TestStep{ return logicaltest.TestStep{
@ -633,7 +596,8 @@ func TestConvergentEncryption(t *testing.T) {
req.Path = "encrypt/testkey" req.Path = "encrypt/testkey"
req.Data = map[string]interface{}{ req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap" "plaintext": "emlwIHphcA==", // "zip zap"
"context": "Zm9vIGJhcg==", // "foo bar" "nonce": "Zm9vIGJhcg==", // "foo bar"
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
} }
resp, err = b.HandleRequest(req) resp, err = b.HandleRequest(req)
if resp == nil { if resp == nil {
@ -643,10 +607,21 @@ func TestConvergentEncryption(t *testing.T) {
t.Fatalf("expected error response, got %#v", *resp) t.Fatalf("expected error response, got %#v", *resp)
} }
// Ensure we fail if we do not provide a nonce
req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap"
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
}
resp, err = b.HandleRequest(req)
if err == nil && (resp == nil || !resp.IsError()) {
t.Fatal("expected error response")
}
// Now test encrypting the same value twice // Now test encrypting the same value twice
req.Data = map[string]interface{}{ req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap" "plaintext": "emlwIHphcA==", // "zip zap"
"context": "b25ldHdvdGhyZWVl", // "onetwothreee" "nonce": "b25ldHdvdGhyZWVl", // "onetwothreee"
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
} }
resp, err = b.HandleRequest(req) resp, err = b.HandleRequest(req)
if resp == nil { if resp == nil {
@ -670,10 +645,11 @@ func TestConvergentEncryption(t *testing.T) {
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2) t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2)
} }
// For sanity, also check a different value // For sanity, also check a different nonce value...
req.Data = map[string]interface{}{ req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap" "plaintext": "emlwIHphcA==", // "zip zap"
"context": "dHdvdGhyZWVmb3Vy", // "twothreefour" "nonce": "dHdvdGhyZWVmb3Vy", // "twothreefour"
"context": "pWZ6t/im3AORd0lVYE0zBdKpX6Bl3/SvFtoVTPWbdkzjG788XmMAnOlxandSdd7S",
} }
resp, err = b.HandleRequest(req) resp, err = b.HandleRequest(req)
if resp == nil { if resp == nil {
@ -694,12 +670,45 @@ func TestConvergentEncryption(t *testing.T) {
ciphertext4 := resp.Data["ciphertext"].(string) ciphertext4 := resp.Data["ciphertext"].(string)
if ciphertext3 != ciphertext4 { if ciphertext3 != ciphertext4 {
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext1, ciphertext2) t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext3, ciphertext4)
} }
if ciphertext1 == ciphertext3 { if ciphertext1 == ciphertext3 {
t.Fatalf("expected different ciphertexts") t.Fatalf("expected different ciphertexts")
} }
// ...and a different context value
req.Data = map[string]interface{}{
"plaintext": "emlwIHphcA==", // "zip zap"
"nonce": "dHdvdGhyZWVmb3Vy", // "twothreefour"
"context": "qV4h9iQyvn+raODOer4JNAsOhkXBwdT4HZ677Ql4KLqXSU+Jk4C/fXBWbv6xkSYT",
}
resp, err = b.HandleRequest(req)
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.IsError() {
t.Fatalf("got error response: %#v", *resp)
}
ciphertext5 := resp.Data["ciphertext"].(string)
resp, err = b.HandleRequest(req)
if resp == nil {
t.Fatal("expected non-nil response")
}
if resp.IsError() {
t.Fatalf("got error response: %#v", *resp)
}
ciphertext6 := resp.Data["ciphertext"].(string)
if ciphertext5 != ciphertext6 {
t.Fatalf("expected the same ciphertext but got %s and %s", ciphertext5, ciphertext6)
}
if ciphertext1 == ciphertext5 {
t.Fatalf("expected different ciphertexts")
}
if ciphertext3 == ciphertext5 {
t.Fatalf("expected different ciphertexts")
}
} }
func TestPolicyFuzzing(t *testing.T) { func TestPolicyFuzzing(t *testing.T) {

View file

@ -132,7 +132,7 @@ func (lm *lockManager) GetPolicyExclusive(storage logical.Storage, name string)
// Get the policy with a read lock; if it returns that an exclusive lock is // Get the policy with a read lock; if it returns that an exclusive lock is
// needed, retry. If successful, call one more time to get a read lock and // needed, retry. If successful, call one more time to get a read lock and
// return the value. // return the value.
func (lm *lockManager) GetPolicyUpsert(storage logical.Storage, name string, derived bool, convergent bool) (*Policy, *sync.RWMutex, bool, error) { func (lm *lockManager) GetPolicyUpsert(storage logical.Storage, name string, derived, convergent bool) (*Policy, *sync.RWMutex, bool, error) {
p, lock, _, err := lm.getPolicyCommon(storage, name, true, derived, convergent, shared) p, lock, _, err := lm.getPolicyCommon(storage, name, true, derived, convergent, shared)
if err == nil || if err == nil ||
(err != nil && err != errNeedExclusiveLock) { (err != nil && err != errNeedExclusiveLock) {

View file

@ -30,6 +30,11 @@ ciphertext; "wrapped" will return the ciphertext only.`,
Description: "Context for key derivation. Required for derived keys.", Description: "Context for key derivation. Required for derived keys.",
}, },
"nonce": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used",
},
"bits": &framework.FieldSchema{ "bits": &framework.FieldSchema{
Type: framework.TypeInt, Type: framework.TypeInt,
Description: `Number of bits for the key; currently 128, 256, Description: `Number of bits for the key; currently 128, 256,
@ -61,17 +66,28 @@ func (b *backend) pathDatakeyWrite(
return logical.ErrorResponse("Invalid path, must be 'plaintext' or 'wrapped'"), logical.ErrInvalidRequest return logical.ErrorResponse("Invalid path, must be 'plaintext' or 'wrapped'"), logical.ErrInvalidRequest
} }
var err error
// Decode the context if any // Decode the context if any
contextRaw := d.Get("context").(string) contextRaw := d.Get("context").(string)
var context []byte var context []byte
if len(contextRaw) != 0 { if len(contextRaw) != 0 {
var err error
context, err = base64.StdEncoding.DecodeString(contextRaw) context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil { if err != nil {
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
} }
} }
// Decode the nonce if any
nonceRaw := d.Get("nonce").(string)
var nonce []byte
if len(nonceRaw) != 0 {
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
}
}
// Get the policy // Get the policy
p, lock, err := b.lm.GetPolicyShared(req.Storage, name) p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
if lock != nil { if lock != nil {
@ -100,7 +116,7 @@ func (b *backend) pathDatakeyWrite(
return nil, err return nil, err
} }
ciphertext, err := p.Encrypt(context, base64.StdEncoding.EncodeToString(newKey)) ciphertext, err := p.Encrypt(context, nonce, base64.StdEncoding.EncodeToString(newKey))
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View file

@ -27,6 +27,11 @@ func (b *backend) pathDecrypt() *framework.Path {
Type: framework.TypeString, Type: framework.TypeString,
Description: "Context for key derivation. Required for derived keys.", Description: "Context for key derivation. Required for derived keys.",
}, },
"nonce": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used",
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -46,17 +51,28 @@ func (b *backend) pathDecryptWrite(
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
} }
var err error
// Decode the context if any // Decode the context if any
contextRaw := d.Get("context").(string) contextRaw := d.Get("context").(string)
var context []byte var context []byte
if len(contextRaw) != 0 { if len(contextRaw) != 0 {
var err error
context, err = base64.StdEncoding.DecodeString(contextRaw) context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil { if err != nil {
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
} }
} }
// Decode the nonce if any
nonceRaw := d.Get("nonce").(string)
var nonce []byte
if len(nonceRaw) != 0 {
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
}
}
// Get the policy // Get the policy
p, lock, err := b.lm.GetPolicyShared(req.Storage, name) p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
if lock != nil { if lock != nil {
@ -69,7 +85,7 @@ func (b *backend) pathDecryptWrite(
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
} }
plaintext, err := p.Decrypt(context, ciphertext) plaintext, err := p.Decrypt(context, nonce, ciphertext)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View file

@ -28,6 +28,11 @@ func (b *backend) pathEncrypt() *framework.Path {
Type: framework.TypeString, Type: framework.TypeString,
Description: "Context for key derivation. Required for derived keys.", Description: "Context for key derivation. Required for derived keys.",
}, },
"nonce": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used",
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -63,10 +68,11 @@ func (b *backend) pathEncryptWrite(
return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest return logical.ErrorResponse("missing plaintext to encrypt"), logical.ErrInvalidRequest
} }
var err error
// Decode the context if any // Decode the context if any
contextRaw := d.Get("context").(string) contextRaw := d.Get("context").(string)
var context []byte var context []byte
var err error
if len(contextRaw) != 0 { if len(contextRaw) != 0 {
context, err = base64.StdEncoding.DecodeString(contextRaw) context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil { if err != nil {
@ -74,6 +80,16 @@ func (b *backend) pathEncryptWrite(
} }
} }
// Decode the nonce if any
nonceRaw := d.Get("nonce").(string)
var nonce []byte
if len(nonceRaw) != 0 {
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
}
}
// Get the policy // Get the policy
var p *Policy var p *Policy
var lock *sync.RWMutex var lock *sync.RWMutex
@ -93,7 +109,7 @@ func (b *backend) pathEncryptWrite(
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
} }
ciphertext, err := p.Encrypt(context, value) ciphertext, err := p.Encrypt(context, nonce, value)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View file

@ -18,23 +18,25 @@ func (b *backend) pathKeys() *framework.Path {
}, },
"derived": &framework.FieldSchema{ "derived": &framework.FieldSchema{
Type: framework.TypeBool, Type: framework.TypeBool,
Description: "Enables key derivation mode. This allows for per-transaction unique keys", Description: `Enables key derivation mode. This
allows for per-transaction unique keys.`,
}, },
"convergent_encryption": &framework.FieldSchema{ "convergent_encryption": &framework.FieldSchema{
Type: framework.TypeBool, Type: framework.TypeBool,
Description: `Whether to use convergent encryption. Description: `Whether to support convergent encryption.
This is only supported when using a key with This is only supported when using a key with
key derivation enabled and will require all key derivation enabled and will require all
context values to be 96 bits (12 bytes) when requests to carry both a context and 96-bit
base64-decoded. This mode ensures that when (12-byte) nonce. The given nonce will be used
the same context is supplied, the same in place of a randomly generated nonce. As a
ciphertext is emitted from the encryption result, when the same context and nonce are
function. It is *very important* when using supplied, the same ciphertext is generated. It
this mode that you ensure that all contexts is *very important* when using this mode that
are *globally unique*. Failing to do so will you ensure that all nonces are unique for a
severely impact the security of the key.`, given context. Failing to do so will severely
impact the ciphertext's security.`,
}, },
}, },

View file

@ -27,6 +27,11 @@ func (b *backend) pathRewrap() *framework.Path {
Type: framework.TypeString, Type: framework.TypeString,
Description: "Context for key derivation. Required for derived keys.", Description: "Context for key derivation. Required for derived keys.",
}, },
"nonce": &framework.FieldSchema{
Type: framework.TypeString,
Description: "Nonce for when convergent encryption is used",
},
}, },
Callbacks: map[logical.Operation]framework.OperationFunc{ Callbacks: map[logical.Operation]framework.OperationFunc{
@ -47,17 +52,28 @@ func (b *backend) pathRewrapWrite(
return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest return logical.ErrorResponse("missing ciphertext to decrypt"), logical.ErrInvalidRequest
} }
var err error
// Decode the context if any // Decode the context if any
contextRaw := d.Get("context").(string) contextRaw := d.Get("context").(string)
var context []byte var context []byte
if len(contextRaw) != 0 { if len(contextRaw) != 0 {
var err error
context, err = base64.StdEncoding.DecodeString(contextRaw) context, err = base64.StdEncoding.DecodeString(contextRaw)
if err != nil { if err != nil {
return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest return logical.ErrorResponse("failed to decode context as base64"), logical.ErrInvalidRequest
} }
} }
// Decode the nonce if any
nonceRaw := d.Get("nonce").(string)
var nonce []byte
if len(nonceRaw) != 0 {
nonce, err = base64.StdEncoding.DecodeString(nonceRaw)
if err != nil {
return logical.ErrorResponse("failed to decode nonce as base64"), logical.ErrInvalidRequest
}
}
// Get the policy // Get the policy
p, lock, err := b.lm.GetPolicyShared(req.Storage, name) p, lock, err := b.lm.GetPolicyShared(req.Storage, name)
if lock != nil { if lock != nil {
@ -71,7 +87,7 @@ func (b *backend) pathRewrapWrite(
return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest return logical.ErrorResponse("policy not found"), logical.ErrInvalidRequest
} }
plaintext, err := p.Decrypt(context, value) plaintext, err := p.Decrypt(context, nonce, value)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:
@ -87,7 +103,7 @@ func (b *backend) pathRewrapWrite(
return nil, fmt.Errorf("empty plaintext returned during rewrap") return nil, fmt.Errorf("empty plaintext returned during rewrap")
} }
ciphertext, err := p.Encrypt(context, plaintext) ciphertext, err := p.Encrypt(context, nonce, plaintext)
if err != nil { if err != nil {
switch err.(type) { switch err.(type) {
case errutil.UserError: case errutil.UserError:

View file

@ -307,10 +307,6 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"} return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
} }
if p.LatestVersion == 0 {
return nil, errutil.InternalError{Err: "unable to access the key; no key versions found"}
}
if ver <= 0 || ver > p.LatestVersion { if ver <= 0 || ver > p.LatestVersion {
return nil, errutil.UserError{Err: "invalid key version"} return nil, errutil.UserError{Err: "invalid key version"}
} }
@ -335,7 +331,7 @@ func (p *Policy) DeriveKey(context []byte, ver int) ([]byte, error) {
} }
} }
func (p *Policy) Encrypt(context []byte, value string) (string, error) { func (p *Policy) Encrypt(context, nonce []byte, value string) (string, error) {
// Decode the plaintext value // Decode the plaintext value
plaintext, err := base64.StdEncoding.DecodeString(value) plaintext, err := base64.StdEncoding.DecodeString(value)
if err != nil { if err != nil {
@ -367,15 +363,12 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
return "", errutil.InternalError{Err: err.Error()} return "", errutil.InternalError{Err: err.Error()}
} }
if p.ConvergentEncryption && len(context) != gcm.NonceSize() {
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded context must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())}
}
// Compute random nonce
var nonce []byte
if p.ConvergentEncryption { if p.ConvergentEncryption {
nonce = context if len(nonce) != gcm.NonceSize() {
return "", errutil.UserError{Err: fmt.Sprintf("base64-decoded nonce must be %d bytes long when using convergent encryption with this key", gcm.NonceSize())}
}
} else { } else {
// Compute random nonce
nonce = make([]byte, gcm.NonceSize()) nonce = make([]byte, gcm.NonceSize())
_, err = rand.Read(nonce) _, err = rand.Read(nonce)
if err != nil { if err != nil {
@ -387,7 +380,10 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
out := gcm.Seal(nil, nonce, plaintext, nil) out := gcm.Seal(nil, nonce, plaintext, nil)
// Place the encrypted data after the nonce // Place the encrypted data after the nonce
full := append(nonce, out...) full := out
if !p.ConvergentEncryption {
full = append(nonce, out...)
}
// Convert to base64 // Convert to base64
encoded := base64.StdEncoding.EncodeToString(full) encoded := base64.StdEncoding.EncodeToString(full)
@ -398,12 +394,16 @@ func (p *Policy) Encrypt(context []byte, value string) (string, error) {
return encoded, nil return encoded, nil
} }
func (p *Policy) Decrypt(context []byte, value string) (string, error) { func (p *Policy) Decrypt(context, nonce []byte, value string) (string, error) {
// Verify the prefix // Verify the prefix
if !strings.HasPrefix(value, "vault:v") { if !strings.HasPrefix(value, "vault:v") {
return "", errutil.UserError{Err: "invalid ciphertext: no prefix"} return "", errutil.UserError{Err: "invalid ciphertext: no prefix"}
} }
if p.ConvergentEncryption && (nonce == nil || len(nonce) == 0) {
return "", errutil.UserError{Err: "invalid convergent nonce supplied"}
}
splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, "vault:v"), ":", 2) splitVerCiphertext := strings.SplitN(strings.TrimPrefix(value, "vault:v"), ":", 2)
if len(splitVerCiphertext) != 2 { if len(splitVerCiphertext) != 2 {
return "", errutil.UserError{Err: "invalid ciphertext: wrong number of fields"} return "", errutil.UserError{Err: "invalid ciphertext: wrong number of fields"}
@ -460,8 +460,13 @@ func (p *Policy) Decrypt(context []byte, value string) (string, error) {
} }
// Extract the nonce and ciphertext // Extract the nonce and ciphertext
nonce := decoded[:gcm.NonceSize()] var ciphertext []byte
ciphertext := decoded[gcm.NonceSize():] if p.ConvergentEncryption {
ciphertext = decoded
} else {
nonce = decoded[:gcm.NonceSize()]
ciphertext = decoded[gcm.NonceSize():]
}
// Verify and Decrypt // Verify and Decrypt
plain, err := gcm.Open(nil, nonce, ciphertext, nil) plain, err := gcm.Open(nil, nonce, ciphertext, nil)

View file

@ -33,7 +33,7 @@ data's attack surface.
Key derivation is supported, which allows the same key to be used for multiple Key derivation is supported, which allows the same key to be used for multiple
purposes by deriving a new key based on a user-supplied context value. In this purposes by deriving a new key based on a user-supplied context value. In this
mode, convergent encryption can optionally be supported, which allows the same mode, convergent encryption can optionally be supported, which allows the same
context and plaintext to produce the same ciphertext. input values to produce the same ciphertext.
The backend also supports key rotation, which allows a new version of the named The backend also supports key rotation, which allows a new version of the named
key to be generated. All data encrypted with the key will use the newest key to be generated. All data encrypted with the key will use the newest
@ -156,12 +156,16 @@ only encrypt or decrypt using the named keys they need access to.
<span class="param-flags">optional</span> <span class="param-flags">optional</span>
If set, the key will support convergent encryption, where the same If set, the key will support convergent encryption, where the same
plaintext creates the same ciphertext. This requires _derived_ to be plaintext creates the same ciphertext. This requires _derived_ to be
set to `true`. When enabled, the context value must be exactly 12 bytes set to `true`. When enabled, each
(96 bits) and will both be used to derive the key and as the nonce for encryption(/decryption/rewrap/datakey) operation will require a `nonce`
the encryption operation. Note that while this is useful for particular value to be specified. Note that while this is useful for particular
situations, it also has security implications. In particular, you must situations, all nonce values used with a given context value **must be
ensure that you do **not** use the same context value for more than one unique** or it will compromise the security of your key. A common way
plaintext value. Defaults to false. to use this will be to generate a unique identifier for the given data
(for instance, a SHA-512 sum), then separate the bytes so that twelve
bytes are used as the nonce and the remaining as the context, ensuring
that all bits of unique identity are used as a part of the encryption
operation. Defaults to false.
</li> </li>
</ul> </ul>
</dd> </dd>
@ -347,6 +351,15 @@ only encrypt or decrypt using the named keys they need access to.
The key derivation context, provided as base64 encoded. The key derivation context, provided as base64 encoded.
Must be provided if derivation is enabled. Must be provided if derivation is enabled.
</li> </li>
<li>
<span class="param">nonce</span>
<span class="param-flags">optional</span>
The nonce value, provided as base64 encoded. Must be provided if
convergent encryption is enabled for this key. The value must be
exactly 96 bits (12 bytes) long and the user must ensure that for any
given context (and thus, any given encryption key) this nonce value is
**never reused**.
</li>
</ul> </ul>
</dd> </dd>
@ -393,6 +406,12 @@ only encrypt or decrypt using the named keys they need access to.
The key derivation context, provided as base64 encoded. The key derivation context, provided as base64 encoded.
Must be provided if derivation is enabled. Must be provided if derivation is enabled.
</li> </li>
<li>
<span class="param">nonce</span>
<span class="param-flags">optional</span>
The nonce value used during encryption, provided as base64 encoded.
Must be provided if convergent encryption is enabled for this key.
</li>
</ul> </ul>
</dd> </dd>
@ -441,6 +460,12 @@ only encrypt or decrypt using the named keys they need access to.
The key derivation context, provided as base64 encoded. The key derivation context, provided as base64 encoded.
Must be provided if derivation is enabled. Must be provided if derivation is enabled.
</li> </li>
<li>
<span class="param">nonce</span>
<span class="param-flags">optional</span>
The nonce value used during encryption, provided as base64 encoded.
Must be provided if convergent encryption is enabled for this key.
</li>
</ul> </ul>
</dd> </dd>
@ -493,6 +518,15 @@ only encrypt or decrypt using the named keys they need access to.
The key derivation context, provided as base64 encoded. The key derivation context, provided as base64 encoded.
Must be provided if derivation is enabled. Must be provided if derivation is enabled.
</li> </li>
<li>
<span class="param">nonce</span>
<span class="param-flags">optional</span>
The nonce value, provided as base64 encoded. Must be provided if
convergent encryption is enabled for this key. The value must be
exactly 96 bits (12 bytes) long and the user must ensure that for any
given context (and thus, any given encryption key) this nonce value is
**never reused**.
</li>
<li> <li>
<span class="param">bits</span> <span class="param">bits</span>
<span class="param-flags">optional</span> <span class="param-flags">optional</span>