Last active
November 12, 2021 08:54
-
-
Save kennwhite/983217f927bc8422639dd2c1a37d3be9 to your computer and use it in GitHub Desktop.
Implementation of AEAD-AES-256-CBC-HMAC-SHA-512 using spec test vectors
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
/* | |
Demonstration of AEAD_AES_256_CBC_HMAC_SHA_512, an authenticated encryption with associated | |
data (AEAD) scheme using a composition of AES in the CBC mode of operation with an | |
HMAC-SHA512 message authentication code, an Encrypt-Then-MAC construction. | |
From IETF draft spec by McGrew, Foley, & Paterson: | |
https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-2.7 | |
Disclaimer: | |
This is strictly a proof of concept personal project and has not had any proper cryptography review. | |
Do not use for anything even vaguely important. | |
v. 0.1 | |
https://play.golang.org/p/dHRe3yxnrgp | |
@todo verify slice logic tests on common edge cases, e.g., empty plaintext or associated data | |
@todo robust debug logging (output for decryption error oracles is intentionally spartan) | |
*/ | |
package main | |
import ( | |
"bytes" | |
"crypto/aes" | |
"crypto/cipher" | |
"crypto/hmac" | |
"crypto/sha512" | |
"encoding/binary" | |
"encoding/hex" | |
"errors" | |
"fmt" | |
"log" | |
) | |
const ( | |
K_LEN = 64 // size in bytes of composite input key K: 2.7, pg 10 | |
T_LEN = 32 // size in bytes of MAC tag T: 2.8, pg 10 | |
AL_LEN = 8 // size in bytes of AL, a uint64 expressing the number of bits in Associated Data A: #4, pg 6 | |
IV_LEN = 16 // size in bytes of IV (b bits/blocksize of AES is 128): 5.4, pg 20 | |
DEBUG = false // toggles (dangerously) verbose stdout logging, (true | false) | |
FAIL = "decryption failed" // special symbol per spec: 2.2, pg 7) | |
) | |
// Test vectors from: https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5.4 | |
type TestCase struct { | |
inputKey []byte // K | |
hmacKey []byte // MAC_KEY | |
encryptKey []byte // ENC_KEY | |
plaintextMsg []byte // P | |
iv []byte // IV | |
assocData []byte // A | |
padStr []byte // PS | |
assocDataLen []byte // AL | |
ciphertextMsg []byte // Q | |
ciphertextWithIV []byte // S | |
authTag []byte // T | |
aeadCiphertext []byte // C | |
} | |
func main() { | |
fmt.Printf("Begin authenticated encryption...\n\n") | |
var testVals *TestCase = loadTestVals() | |
aeadPayload := aeadEncryptor( | |
testVals.inputKey, | |
testVals.iv, | |
testVals.plaintextMsg, | |
testVals.assocData, | |
testVals.assocDataLen) | |
fmt.Printf("Encryption complete: \n") | |
fmt.Printf("AEAD output payload (A || IV || enc(P||PS) || AL || T) \n%s \n", hex.Dump(aeadPayload)) | |
fmt.Printf("Begin authenticating & decrypting AEAD payload...\n\n") | |
assocData, plaintextMsg, err := aeadDecryptor(testVals.inputKey, aeadPayload) | |
// Per spec, decryption output shoud ONLY be either authenticated plaintext or a FAIL indication. 2.2, pg7 | |
if err != nil { | |
fmt.Println(FAIL) | |
} else { | |
fmt.Printf("Authenticated Associated Data: \n%s\n", hex.Dump(assocData)) | |
fmt.Printf("Authenticated plaintext msg: \n%s\n", hex.Dump(plaintextMsg)) | |
} | |
} | |
/* | |
Perform decryption operations according to method described in spec: 2.2, pg 7 | |
Returns: Associated Data, unpadded plaintext message, error | |
*/ | |
func aeadDecryptor(inputKey []byte, payload []byte) ([]byte, []byte, error) { | |
// Step 1: Generate secondary keys from input key K | |
if len(inputKey) != K_LEN { | |
if DEBUG { | |
log.Printf("Decryption fail: Invalid size for input key K. Should be %d bytes", K_LEN) | |
} | |
return []byte{}, []byte{}, errors.New(FAIL) | |
} | |
hmacKey, encryptKey := genKeys(inputKey) | |
// Step 2: Strip final T_LEN octets which contain the MAC auth tag T | |
// Sanity check: | |
// Min payload should be >= size of MAC tag T + AD bit counter AL + empty AD + IV + encrypted empty padded P | |
if len(payload) < (T_LEN + AL_LEN + IV_LEN + aes.BlockSize) { | |
if DEBUG { | |
log.Printf("Decryption fail: Corrupt payload smaller than expected minimum") | |
} | |
return []byte{}, []byte{}, errors.New(FAIL) | |
} | |
// Parse trailing MAC auth tag T | |
macExpected := payload[(len(payload) - T_LEN):] | |
// Remove it from the payload | |
payload = payload[:(len(payload) - T_LEN)] | |
// Step 3: Check integrity and authenticity of AD & padded ciphertext, fail fast | |
mac := hmac.New(sha512.New, hmacKey) | |
mac.Write(payload) | |
macComputed := mac.Sum(nil) | |
macComputed = macComputed[:T_LEN] // Truncate to HMAC-SHA-512 to 256 bits, T_LEN bytes: 2.8, pg 10 | |
// Constant time MAC comparison to minimize timing-related side channel leaks | |
// Strive to fail fast to avoid error oracles | |
if !hmac.Equal(macComputed, macExpected) { | |
if DEBUG { | |
log.Printf("Authentication fail: Computed AEAD MAC != supplied payload MAC") | |
} | |
return []byte{}, []byte{}, errors.New(FAIL) | |
} | |
if DEBUG { | |
fmt.Printf("Split MAC & ENC keys: \n%s\n%s\n", hex.Dump(hmacKey), hex.Dump(encryptKey)) | |
fmt.Printf("MAC input (AEAD payload): \n%s \n", hex.Dump(payload)) | |
fmt.Printf("Expected MAC tag: \n%s \n", hex.Dump(macExpected)) | |
fmt.Printf("Computed MAC tag: \n%s \n", hex.Dump(macComputed)) | |
fmt.Printf("Payload successfully authenticated... \n\n") | |
} | |
// Parse value of current trailing segment containing data structure AL expressing size of Associated Data in bits | |
assocDataLenInBits := payload[(len(payload) - AL_LEN):] | |
assocDataLenInBytes := binary.BigEndian.Uint64(assocDataLenInBits) / 8 | |
// Strip off trailing AL structure from payload | |
payload = payload[:(len(payload) - AL_LEN)] | |
// Parse Associated Data | |
assocData := payload[:assocDataLenInBytes] | |
payload = payload[assocDataLenInBytes:] | |
// Step 4: Parse IV, decrypt remaining (authenticated) payload of ciphertext of plaintext msg + padding string | |
iv := payload[:aes.BlockSize] | |
ciphertext := payload[aes.BlockSize:] | |
if DEBUG { | |
fmt.Printf("IV: \n%s \n", hex.Dump(iv)) | |
fmt.Printf("Ciphertext: \n%s \n", hex.Dump(ciphertext)) | |
} | |
block, err := aes.NewCipher(encryptKey) | |
if err != nil { | |
// Decryption failure, return empty slices to indicate FAIL response | |
return []byte{}, []byte{}, errors.New(FAIL) | |
} | |
plaintextMsg := make([]byte, len(ciphertext)) | |
mode := cipher.NewCBCDecrypter(block, iv) | |
mode.CryptBlocks(plaintextMsg, ciphertext) | |
// Step 5: Strip padding string. The final padding octet indicates the size of the full pad: #5 pg 7 | |
padSize := int(plaintextMsg[len(plaintextMsg)-1]) | |
plaintextMsg = plaintextMsg[:(len(plaintextMsg) - padSize)] | |
// Step 6: Return authenticated associated data & plaintext with no errors | |
return assocData, plaintextMsg, nil | |
} | |
/* | |
Perform authenticated encryption operations according to method described in spec: 2.1, pg 5 | |
Returns: AEAD payload byte slice in the following format | |
(A || IV || enc(P||PS) || AL || T) | |
Associated Data || IV || encrypted ( plaintext || padding string ) || length of Associated Data || MAC tag | |
*/ | |
func aeadEncryptor(inputKey []byte, iv []byte, plaintextMsg []byte, assocData []byte, assocDataLen []byte) []byte { | |
hmacKey, encryptKey := genKeys(inputKey) | |
msgPadded := pkcs5Pad(plaintextMsg) | |
if len(msgPadded)%aes.BlockSize != 0 { | |
panic("Padded plaintext msg is not a multiple of the block size") | |
} | |
block, err := aes.NewCipher(encryptKey) | |
if err != nil { | |
panic(err) | |
} | |
// The IV needs to be unique, but not secure. Therefore it's common to | |
// include it at the beginning of the ciphertext. | |
ivLen := aes.BlockSize | |
ciphertext := make([]byte, ivLen+len(msgPadded)) | |
copy(ciphertext[:ivLen], iv) | |
mode := cipher.NewCBCEncrypter(block, iv) | |
// If len(dst) < len(src), CryptBlocks should panic per https://pkg.go.dev/crypto/cipher#BlockMode | |
mode.CryptBlocks(ciphertext[ivLen:], msgPadded) | |
if len(ciphertext)%aes.BlockSize != 0 { | |
panic("Ciphertext is not a multiple of the block size") | |
} | |
// Concatenate (A || S || AL) | |
macInput := append(assocData, append(ciphertext, assocDataLen...)...) | |
// Calculate MAC auth tag T: 2.7, pg 10 | |
mac := hmac.New(sha512.New, hmacKey) | |
mac.Write(macInput) | |
authTag := mac.Sum(nil) | |
authTag = authTag[:T_LEN] // Truncate to HMAC-SHA-512 to 256 bits, T_LEN bytes: 2.8, pg 10 | |
// Add MAC to the end before returning payload | |
aeadPayload := append(macInput, authTag...) | |
fmt.Printf("Split MAC & ENC keys: \n%s\n%s\n", hex.Dump(hmacKey), hex.Dump(encryptKey)) | |
fmt.Printf("Associated Data (A): \n%s\n", hex.Dump(assocData)) | |
fmt.Printf("Associated Data length (AL): \n%s\n", hex.Dump(assocDataLen)) | |
fmt.Printf("IV:\n%s\n", hex.Dump(iv)) | |
fmt.Printf("MAC tag T:\n%s\n", hex.Dump(authTag)) | |
fmt.Printf("Plaintext msg P with Padding String PS ( P || PS): \n%s\n", hex.Dump(msgPadded)) | |
return aeadPayload | |
} | |
/* | |
PKCS#5/7 padding is not currently supported in Go's standard library | |
See: https://groups.google.com/g/golang-dev/c/q6wSqGyJo2s/m/cOy-g8pUBwAJ | |
But note: https://github.com/search?l=Go&o=desc&q=%22func+pkcs5Pad%22&s=indexed&type=Code | |
And also: https://github.com/search?l=Go&o=desc&q=%22func+pkcs7Pad%22&s=indexed&type=Code | |
@todo: File PR to attempt to revive this feature request for Go std lib | |
Source: https://github.com/elastic/cloud-on-k8s/blob/master/pkg/controller/common/license/crypto.go#L58 | |
*/ | |
func pkcs5Pad(data []byte) []byte { | |
padLen := aes.BlockSize - len(data)%aes.BlockSize | |
padding := bytes.Repeat([]byte{byte(padLen)}, padLen) | |
return append(data, padding...) | |
} | |
func genKeys(inputKey []byte) ([]byte, []byte) { | |
if len(inputKey) != 64 { | |
panic("Composite key must be 512 bits.") | |
} | |
hmacKey := inputKey[:32] | |
encryptKey := inputKey[32:] | |
if len(encryptKey) != aes.BlockSize*2 { | |
panic("AEAD_AES_256_CBC_HMAC_SHA_512 encryption requires a 256 bit key") | |
} | |
return hmacKey, encryptKey | |
} | |
/* | |
All definitions & values here are from AEAD_AES_256_CBC_HMAC_SHA512 (pg 20) test vectors in: | |
https://datatracker.ietf.org/doc/html/draft-mcgrew-aead-aes-cbc-hmac-sha2-05#section-5.4 | |
*/ | |
func loadTestVals() *TestCase { | |
// K - 2.1, pg 5 | |
inputKey, _ := hex.DecodeString( | |
"000102030405060708090a0b0c0d0e0f" + | |
"101112131415161718191a1b1c1d1e1f" + | |
"202122232425262728292a2b2c2d2e2f" + | |
"303132333435363738393a3b3c3d3e3f") | |
// MAC_KEY - 2.1, pg 5 | |
hmacKey, _ := hex.DecodeString( | |
"000102030405060708090a0b0c0d0e0f" + | |
"101112131415161718191a1b1c1d1e1f") | |
// ENC_KEY - 2.1, pg 5 | |
encryptKey, _ := hex.DecodeString( | |
"202122232425262728292a2b2c2d2e2f" + | |
"303132333435363738393a3b3c3d3e3f") | |
// P - 2.1, pg 5 | |
plaintextMsg, _ := hex.DecodeString( | |
"41206369706865722073797374656d20" + | |
"6d757374206e6f742062652072657175" + | |
"6972656420746f206265207365637265" + | |
"742c20616e64206974206d7573742062" + | |
"652061626c6520746f2066616c6c2069" + | |
"6e746f207468652068616e6473206f66" + | |
"2074686520656e656d7920776974686f" + | |
"757420696e636f6e76656e69656e6365") | |
// IV - Appendix A, pg 28 | |
iv, _ := hex.DecodeString( | |
"1af38c2dc2b96ffdd86694092341bc04") | |
// A - 2.1, pg 5 | |
assocData, _ := hex.DecodeString( | |
"546865207365636f6e64207072696e63" + | |
"69706c65206f66204175677573746520" + | |
"4b6572636b686f666673") | |
// PS - #2, pg 6 | |
padStr, _ := hex.DecodeString( | |
"10101010101010101010101010101010") | |
// AL - #4, pg 6 | |
assocDataLen, _ := hex.DecodeString( | |
"0000000000000150") | |
// Q - Appendix A, pg 28 | |
ciphertextMsg, _ := hex.DecodeString( | |
"4affaaadb78c31c5da4b1b590d10ffbd" + | |
"3dd8d5d302423526912da037ecbcc7bd" + | |
"822c301dd67c373bccb584ad3e9279c2" + | |
"e6d12a1374b77f077553df829410446b" + | |
"36ebd97066296ae6427ea75c2e0846a1" + | |
"1a09ccf5370dc80bfecbad28c73f09b3" + | |
"a3b75e662a2594410ae496b2e2e6609e" + | |
"31e6e02cc837f053d21f37ff4f51950b" + | |
"be2638d09dd7a4930930806d0703b1f6") | |
// S - Appendix A, pg 21 | |
ciphertextWithIV, _ := hex.DecodeString( | |
"1af38c2dc2b96ffdd86694092341bc04" + | |
"4affaaadb78c31c5da4b1b590d10ffbd" + | |
"3dd8d5d302423526912da037ecbcc7bd" + | |
"822c301dd67c373bccb584ad3e9279c2" + | |
"e6d12a1374b77f077553df829410446b" + | |
"36ebd97066296ae6427ea75c2e0846a1" + | |
"1a09ccf5370dc80bfecbad28c73f09b3" + | |
"a3b75e662a2594410ae496b2e2e6609e" + | |
"31e6e02cc837f053d21f37ff4f51950b" + | |
"be2638d09dd7a4930930806d0703b1f6") | |
// T - #5, pg 6 | |
authTag, _ := hex.DecodeString( | |
"4dd3b4c088a7f45c216839645b2012bf" + | |
"2e6269a8c56a816dbc1b267761955bc5") | |
// C - #6, pg 6 | |
aeadCiphertext, _ := hex.DecodeString( | |
"1af38c2dc2b96ffdd86694092341bc04" + | |
"4affaaadb78c31c5da4b1b590d10ffbd" + | |
"3dd8d5d302423526912da037ecbcc7bd" + | |
"822c301dd67c373bccb584ad3e9279c2" + | |
"e6d12a1374b77f077553df829410446b" + | |
"36ebd97066296ae6427ea75c2e0846a1" + | |
"1a09ccf5370dc80bfecbad28c73f09b3" + | |
"a3b75e662a2594410ae496b2e2e6609e" + | |
"31e6e02cc837f053d21f37ff4f51950b" + | |
"be2638d09dd7a4930930806d0703b1f6" + | |
"4dd3b4c088a7f45c216839645b2012bf" + | |
"2e6269a8c56a816dbc1b267761955bc5") | |
return &TestCase{ | |
inputKey: inputKey, | |
hmacKey: hmacKey, | |
encryptKey: encryptKey, | |
plaintextMsg: plaintextMsg, | |
iv: iv, | |
assocData: assocData, | |
padStr: padStr, | |
assocDataLen: assocDataLen, | |
ciphertextMsg: ciphertextMsg, | |
ciphertextWithIV: ciphertextWithIV, | |
authTag: authTag, | |
aeadCiphertext: aeadCiphertext} | |
} |
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
$ go run main.go | |
Begin authenticated encryption... | |
Split MAC & ENC keys: | |
00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................| | |
00000010 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f |................| | |
00000000 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f | !"#$%&'()*+,-./| | |
00000010 30 31 32 33 34 35 36 37 38 39 3a 3b 3c 3d 3e 3f |0123456789:;<=>?| | |
Associated Data (A): | |
00000000 54 68 65 20 73 65 63 6f 6e 64 20 70 72 69 6e 63 |The second princ| | |
00000010 69 70 6c 65 20 6f 66 20 41 75 67 75 73 74 65 20 |iple of Auguste | | |
00000020 4b 65 72 63 6b 68 6f 66 66 73 |Kerckhoffs| | |
Associated Data length (AL): | |
00000000 00 00 00 00 00 00 01 50 |.......P| | |
IV: | |
00000000 1a f3 8c 2d c2 b9 6f fd d8 66 94 09 23 41 bc 04 |...-..o..f..#A..| | |
MAC tag T: | |
00000000 4d d3 b4 c0 88 a7 f4 5c 21 68 39 64 5b 20 12 bf |M......\!h9d[ ..| | |
00000010 2e 62 69 a8 c5 6a 81 6d bc 1b 26 77 61 95 5b c5 |.bi..j.m..&wa.[.| | |
Plaintext msg P with Padding String PS ( P || PS): | |
00000000 41 20 63 69 70 68 65 72 20 73 79 73 74 65 6d 20 |A cipher system | | |
00000010 6d 75 73 74 20 6e 6f 74 20 62 65 20 72 65 71 75 |must not be requ| | |
00000020 69 72 65 64 20 74 6f 20 62 65 20 73 65 63 72 65 |ired to be secre| | |
00000030 74 2c 20 61 6e 64 20 69 74 20 6d 75 73 74 20 62 |t, and it must b| | |
00000040 65 20 61 62 6c 65 20 74 6f 20 66 61 6c 6c 20 69 |e able to fall i| | |
00000050 6e 74 6f 20 74 68 65 20 68 61 6e 64 73 20 6f 66 |nto the hands of| | |
00000060 20 74 68 65 20 65 6e 65 6d 79 20 77 69 74 68 6f | the enemy witho| | |
00000070 75 74 20 69 6e 63 6f 6e 76 65 6e 69 65 6e 63 65 |ut inconvenience| | |
00000080 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 10 |................| | |
Encryption complete: | |
AEAD output payload (A || IV || enc(P||PS) || AL || T) | |
00000000 54 68 65 20 73 65 63 6f 6e 64 20 70 72 69 6e 63 |The second princ| | |
00000010 69 70 6c 65 20 6f 66 20 41 75 67 75 73 74 65 20 |iple of Auguste | | |
00000020 4b 65 72 63 6b 68 6f 66 66 73 1a f3 8c 2d c2 b9 |Kerckhoffs...-..| | |
00000030 6f fd d8 66 94 09 23 41 bc 04 4a ff aa ad b7 8c |o..f..#A..J.....| | |
00000040 31 c5 da 4b 1b 59 0d 10 ff bd 3d d8 d5 d3 02 42 |1..K.Y....=....B| | |
00000050 35 26 91 2d a0 37 ec bc c7 bd 82 2c 30 1d d6 7c |5&.-.7.....,0..|| | |
00000060 37 3b cc b5 84 ad 3e 92 79 c2 e6 d1 2a 13 74 b7 |7;....>.y...*.t.| | |
00000070 7f 07 75 53 df 82 94 10 44 6b 36 eb d9 70 66 29 |..uS....Dk6..pf)| | |
00000080 6a e6 42 7e a7 5c 2e 08 46 a1 1a 09 cc f5 37 0d |j.B~.\..F.....7.| | |
00000090 c8 0b fe cb ad 28 c7 3f 09 b3 a3 b7 5e 66 2a 25 |.....(.?....^f*%| | |
000000a0 94 41 0a e4 96 b2 e2 e6 60 9e 31 e6 e0 2c c8 37 |.A......`.1..,.7| | |
000000b0 f0 53 d2 1f 37 ff 4f 51 95 0b be 26 38 d0 9d d7 |.S..7.OQ...&8...| | |
000000c0 a4 93 09 30 80 6d 07 03 b1 f6 00 00 00 00 00 00 |...0.m..........| | |
000000d0 01 50 4d d3 b4 c0 88 a7 f4 5c 21 68 39 64 5b 20 |.PM......\!h9d[ | | |
000000e0 12 bf 2e 62 69 a8 c5 6a 81 6d bc 1b 26 77 61 95 |...bi..j.m..&wa.| | |
000000f0 5b c5 |[.| | |
Begin authenticating & decrypting AEAD payload... | |
Authenticated Associated Data: | |
00000000 54 68 65 20 73 65 63 6f 6e 64 20 70 72 69 6e 63 |The second princ| | |
00000010 69 70 6c 65 20 6f 66 20 41 75 67 75 73 74 65 20 |iple of Auguste | | |
00000020 4b 65 72 63 6b 68 6f 66 66 73 |Kerckhoffs| | |
Authenticated plaintext msg: | |
00000000 41 20 63 69 70 68 65 72 20 73 79 73 74 65 6d 20 |A cipher system | | |
00000010 6d 75 73 74 20 6e 6f 74 20 62 65 20 72 65 71 75 |must not be requ| | |
00000020 69 72 65 64 20 74 6f 20 62 65 20 73 65 63 72 65 |ired to be secre| | |
00000030 74 2c 20 61 6e 64 20 69 74 20 6d 75 73 74 20 62 |t, and it must b| | |
00000040 65 20 61 62 6c 65 20 74 6f 20 66 61 6c 6c 20 69 |e able to fall i| | |
00000050 6e 74 6f 20 74 68 65 20 68 61 6e 64 73 20 6f 66 |nto the hands of| | |
00000060 20 74 68 65 20 65 6e 65 6d 79 20 77 69 74 68 6f | the enemy witho| | |
00000070 75 74 20 69 6e 63 6f 6e 76 65 6e 69 65 6e 63 65 |ut inconvenience| |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment