Subject: [AVTCORE] AD review: draft-ietf-avtcore-srtp-aes-gcm

From: Richard Barnes <[email-masked]>
Date: Sat, 2 Nov 2013 17:13:19 -0700

I have reviewed this draft in preparation for IETF Last Call and IESG
processing.  Overall, it's in very good shape.  Just have a few minor
questions and editorial suggestions:

MINOR
-----

S5.2.1
It's a little confusing that the tag size an input to CCM, but "fixed by
the algorithm" for GCM, since in both cases (GCM/CCM), the length of the
tag is fixed by negotiated algorithm (Table 1/8).  Perhaps rephrase to
clarify that the CCM mode requires tag length to be explicitly input to the
algorithm, whereas with GCM, the tag is simply truncated.

S5.3
Why is the guidance for "MUST not decrypt before verify" different for GCM
vs. CCM?

S6
Why is this document re-defining the key streams from the GCM and CCM
specs?  Shouldn't this just say how the intial counter or counter sequence
is formed?

S13.1
In this section, _8 is SHOULD, and _12 is MAY.  What about AEAD_AES_128_GCM
itself?  Is that a MUST implement?  It seems like there should be one.
Likewise for S13.2 and CCM.

EDITORIAL
---------

S5.2.1
The document mostly uses "octet", but there are three instances of "byte",
all in the table in this section.  Probably good to use "octet" there as
well, for clarity.

S6
The code here is *almost* valid python, but not quite.  In case it's
helpful, a revised version in Python is below (since I had time on the
plane on the way to the IETF meeting :) ).

S14.1, 3rd bullet
It might be helpful to note that although the counter is reset for every
packet, IV uniqueness is ensured by the inclusion of SSRC/ROC/SEQ or SRTCP
Index in the IV (and the accompanying management of these parametesr.  This
is stated above, but if restated here, would help justify the last two
bullets.

-----BEGIN srtp-aead.py-----
#!/usr/bin/env python

from struct import pack
from Crypto.Cipher import AES
from Crypto.Util import Counter
from Crypto.Util.number import long_to_bytes, bytes_to_long

# Parameters
Plaintext = "\x00" * 48 # Must be all zeros for CTR-based validation to work
IV = "iv" * 6
Encryption_key = "sixteen byte key"

# Helper functions required by the algorithms in the draft
def AES_ENC( data="", key="" ):
    assert( len(data) == 16 )
    assert( len(key) in [16, 24, 32] )
    return AES.new(key).encrypt(data)

def TO_BYTES( counter, n ):
    return long_to_bytes(counter, n)

# For each algorithm, we generate the key stream with the algorithm
# in the draft, and compare to PyCrypto's CTR implementation.  (We
# get the key stream from the CTR mode by encrypting the all-zero
# octet string.)

# GCM
def GCM_keystream( Plaintext, IV, Encryption_key ):
    assert( len(Plaintext)  <= (2**36) - 32 ) ## measured in octets
    key_stream = ""
    block_counter = 1
    first_key_block = AES_ENC( data = IV + TO_BYTES(block_counter,4),
                               key=Encryption_key        )
    while len(key_stream) < len(Plaintext):
        block_counter = block_counter + 1
        key_block = AES_ENC( data=IV + TO_BYTES(block_counter,4),
                             key=Encryption_key        )
        key_stream  = key_stream + key_block
    key_stream = key_stream[:len(Plaintext)]
    return (first_key_block, key_stream )

Key_stream_G = GCM_keystream( Plaintext, IV, Encryption_key )
Key_stream_G = Key_stream_G[0] + Key_stream_G[1]
ivn = bytes_to_long( IV + ("\x00" * 4) )
ctr = Counter.new(128, initial_value = ivn+1)
Ciphertext_G = AES.new(Encryption_key, AES.MODE_CTR,
counter=ctr).encrypt(Plaintext)

# CCM
def CCM_keystream( Plaintext, IV, Encryption_key ):
    assert( len(Plaintext) <= (2**28)-16 ) ## measured in octets
    key_stream = ""
    block_counter = 0
    first_key_block = AES_ENC( data='\x02' + IV + TO_BYTES(block_counter,3),
                               key=Encryption_key            )
    while len(key_stream)<len(Plaintext):
        block_counter = block_counter + 1
        key_block = AES_ENC( data='\x02' + IV + TO_BYTES(block_counter,3),
                             key=Encryption_key            )
        key_stream  = key_stream + key_block
    key_stream = key_stream[:len(Plaintext)]
    return (first_key_block, key_stream )

Key_stream_C = CCM_keystream( Plaintext, IV, Encryption_key )
Key_stream_C = Key_stream_C[0] + Key_stream_C[1]
ivn = bytes_to_long( '\x02' + IV + ("\x00" * 3) )
ctr = Counter.new(128, initial_value = ivn)
Ciphertext_C = AES.new(Encryption_key, AES.MODE_CTR,
counter=ctr).encrypt(Plaintext)

# Print out results.  KSg and KSg' should match, and KSc/KSc'
print "P:    {}".format(Plaintext.encode("hex"))
print "IV:   {}".format(IV.encode("hex"))
print "K:    {}".format(Encryption_key.encode("hex"))
print "KSg:  {}".format(Key_stream_G.encode("hex"))
print "KSg': {}".format(Ciphertext_G.encode("hex"))
print "KSc:  {}".format(Key_stream_C.encode("hex"))
print "KSc': {}".format(Ciphertext_C.encode("hex"))
-----END srtp-aead.py-----