-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4082f74
commit a3332f8
Showing
3 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
This document specifies the XAES-256-GCM authenticated encryption with additional data algorithm, based on the composition of a standard NIST SP 800-108r1 KDF and the standard NIST AES-256-GCM AEAD. | ||
|
||
The XAES-256-GCM inputs are a 256-bit key, a 192-bit nonce, a plaintext of up to approximately 64GiB, and additional data of up to 2 EiB. | ||
|
||
Unlike AES-256-GCM, the XAES-256-GCM nonce can be randomly generated for a virtually unlimited number of messages (2⁸⁰ messages with collision risk 2⁻³²). Only a 256-bit key version is specified, which provides a comfortable multi-user security margin. Like AES-256-GCM, XAES-256-GCM is not nonce misuse-resistant, nor is it key-committing. Compared to AES-256-GCM, XAES-256-GCM requires three extra invocations of the AES-256 function, but has otherwise the same performance profile. | ||
|
||
## Overview | ||
|
||
XAES-256-GCM derives a subkey for use with AES-256-GCM from the input key and half the input nonce using a NIST SP 800-108r1 KDF. | ||
|
||
The counter-based KDF (NIST SP 800-108r1, Section 4.1) is instantiated with CMAC-AES256 (NIST SP 800-38B) and the input key as *Kin*, the ASCII letter `X` (0x58) as *Label*, and the first 96 bits of the input nonce as *Context* (as recommended by NIST SP 800-38B, Section 4, point 4), to produce a 256-bit derived key. | ||
|
||
Note that with a counter (*i*) size of 16 bits and omitting the optional *L* field, the AES-CMAC input totals 128 bits, which fits into a single block, mitigating the key control security issue described in NIST SP 800-38B, Section 6.7. It would in theory be possible to shrink *i* to 8 bits to fit a 8 bits *L*, but some implementations unfortunately fix the *L* size to 32 bits. | ||
|
||
The derived key and the last 96 bits of the input nonce are used to encrypt the message with AES-256-GCM. | ||
|
||
## Detailed key derivation algorithm | ||
|
||
Inputs: | ||
|
||
* 256-bit key *K* | ||
* 192-bit nonce *N* | ||
|
||
Outputs: | ||
|
||
* 256-bit key *Kₓ* | ||
* 96-bit nonce *Nₓ* | ||
|
||
Algorithm: | ||
|
||
1. *L* = AES-256ₖ(0¹²⁸) | ||
2. If MSB₁(*L*) = 0, then *K1* = *L* << 1; | ||
Else *K1* = (*L* << 1) ⊕ 0¹²⁰10000111 | ||
3. *M1* = 0x00 || 0x01 || `X` || 0x00 || *N*[:12] | ||
4. *M2* = 0x00 || 0x02 || `X` || 0x00 || *N*[:12] | ||
5. *Kₓ* = AES-256ₖ(*M1* ⊕ *K1*) || AES-256ₖ(*M2* ⊕ *K1*) | ||
6. *Nₓ* = *N*[12:] | ||
|
||
Steps 1 and 2 reproduce the CMAC subkey generation specified in NIST SP 800-38B, Section 6.1. Note that only *K1* is needed. | ||
|
||
Steps 2 and 3 compose the PRF inputs for *i* = 1, 2 according to NIST SP 800-108r1, Section 4.1. | ||
|
||
Step 5 applies CMAC twice to the two single-block messages to derive the KDF output. | ||
|
||
*Kₓ* and *Nₓ* are then used as the AES-256-GCM key and nonce, respectively. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
int derive_key(unsigned char out[32], const unsigned char key[32], const unsigned char nonce[24]) { | ||
EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL); | ||
EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); | ||
EVP_KDF_free(kdf); | ||
|
||
OSSL_PARAM params[9], *p = params; | ||
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_CIPHER, "AES256", 0); | ||
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MAC, "CMAC", 0); | ||
*p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_MODE, "COUNTER", 0); | ||
*p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_KBKDF_USE_L, 0); | ||
*p++ = OSSL_PARAM_construct_int(OSSL_KDF_PARAM_KBKDF_R, 16); | ||
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, key, sizeof(key)); | ||
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, "X", strlen("X")); | ||
*p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, nonce[:12], 12); | ||
*p = OSSL_PARAM_construct_end(); | ||
|
||
int res = EVP_KDF_derive(kctx, out, sizeof(out), params); | ||
|
||
EVP_KDF_CTX_free(kctx); | ||
return res; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package xaes256gcm | ||
|
||
import ( | ||
"crypto/aes" | ||
"crypto/cipher" | ||
"crypto/subtle" | ||
"errors" | ||
) | ||
|
||
const ( | ||
KeySize = 32 | ||
NonceSize = 24 | ||
Overhead = 16 | ||
) | ||
|
||
type xaes256gcm struct { | ||
c cipher.Block | ||
k1 [aes.BlockSize]byte | ||
} | ||
|
||
func New(key []byte) (cipher.AEAD, error) { | ||
if len(key) != KeySize { | ||
return nil, errors.New("xaes256gcm: bad key length") | ||
} | ||
|
||
x := new(xaes256gcm) | ||
|
||
x.c, _ = aes.NewCipher(key) | ||
x.c.Encrypt(x.k1[:], x.k1[:]) | ||
|
||
var msb byte | ||
for i := len(x.k1) - 1; i >= 0; i-- { | ||
msb = x.k1[i] >> 7 | ||
x.k1[i] = x.k1[i]<<1 | msb | ||
} | ||
x.k1[len(x.k1)-1] ^= msb * 0b10000111 | ||
|
||
return x, nil | ||
} | ||
|
||
func (*xaes256gcm) NonceSize() int { | ||
return NonceSize | ||
} | ||
|
||
func (*xaes256gcm) Overhead() int { | ||
return Overhead | ||
} | ||
|
||
func (x *xaes256gcm) deriveKey(nonce []byte) []byte { | ||
k := make([]byte, 0, 2*aes.BlockSize) | ||
k = append(k, 0, 1, 'X', 0) | ||
k = append(k, nonce...) | ||
k = append(k, 0, 2, 'X', 0) | ||
k = append(k, nonce...) | ||
subtle.XORBytes(k[:aes.BlockSize], k[:aes.BlockSize], x.k1[:]) | ||
subtle.XORBytes(k[aes.BlockSize:], k[aes.BlockSize:], x.k1[:]) | ||
x.c.Encrypt(k[:aes.BlockSize], k[:aes.BlockSize]) | ||
x.c.Encrypt(k[aes.BlockSize:], k[aes.BlockSize:]) | ||
return k | ||
} | ||
|
||
func (x *xaes256gcm) Seal(dst, nonce, plaintext, additionalData []byte) []byte { | ||
if len(nonce) != NonceSize { | ||
panic("xaes256gcm: bad nonce length passed to Seal") | ||
} | ||
|
||
k, n := x.deriveKey(nonce[:12]), nonce[12:] | ||
c, _ := aes.NewCipher(k) | ||
a, _ := cipher.NewGCM(c) | ||
return a.Seal(dst, n, plaintext, additionalData) | ||
} | ||
|
||
var errOpen = errors.New("xaes256gcm: message authentication failed") | ||
|
||
func (x *xaes256gcm) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) { | ||
if len(nonce) != NonceSize { | ||
panic("xaes256gcm: bad nonce length passed to Open") | ||
} | ||
|
||
k, n := x.deriveKey(nonce[:12]), nonce[12:] | ||
c, _ := aes.NewCipher(k) | ||
a, _ := cipher.NewGCM(c) | ||
return a.Open(dst, n, ciphertext, additionalData) | ||
} |