4
$\begingroup$

I was trying to use OpenSSL 1.1 (via Ruby) to validate the ChaCha20-Poly1305 test vector from this IETF draft: https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7

The relevant test vector is quoted here:

The following block contains a test vector for the
AEAD_CHACHA20_POLY1305 algorithm.  The first four lines consist of
the standard inputs to an AEAD algorithm and the last line contains
the encrypted and authenticated result.

KEY:    4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd110
        0a1007
INPUT:  86d09974840bded2a5ca
NONCE:  cd7cf67be39c794a
AD:     87e229d4500845a079c0
OUTPUT: e3e446f7ede9a19b62a4677dabf4e3d24b876bb284753896e1d6

This gives me an 8-byte nonce. But when I try to run this test, I quickly run into a problem:

cipher = OpenSSL::Cipher.new("chacha20-poly1305")
cipher.encrypt
cipher.key = ["4290bcb154173531f314af57f3be3b5006da371ece272afa1b5dbdd1100a1007"].pack("H*")
cipher.iv = ["cd7cf67be39c794a"].pack("H*")

ArgumentError: iv must be 12 bytes
        from (irb):81:in `iv='
        from (irb):81
        from ~/.rvm/rubies/ruby-2.4.0/bin/irb:11:in `<main>'

How come OpenSSL is insisting on a 12-byte nonce, but the draft spec is giving me an 8-byte nonce? I'm still learning about ChaCha20 and Poly1305, so I'm sure I'm missing something basic.

$\endgroup$
  • 2
    $\begingroup$ You could try setting the leftmost (or rightmost, I hate the these Little Endian implementations, I always get it wrong initially) of the nonce to all zero if you want to confirm to the test vectors. Read chapter 3 to understand why: if the nonce is larger then it is very likely that the block counter is smaller by the same amount. And as they are at the high end of the block counter, those zero bytes will never be set to anything other than zero. $\endgroup$ – Maarten - reinstate Monica Jul 29 '17 at 14:21
3
$\begingroup$

For the complete picture, as was pointed out, you should use the final RFC, not drafts. There are two relevant RFC here:

  • RFC 7539 describes the stream cipher ChaCha20, the MAC algorithm Poly1305, and an Authenticated Encryption with Associated Data mode that combines ChaCha20 and Poly1305 in a safe way (in particular, it uses ChaCha20 to provide the secret values needed by Poly1305).

  • RFC 7905 describes TLS cipher suites that rely on the AEAD construction described in RFC 7539; that RFC explains how the associated data and nonce are built in the context of TLS.

In RFC 7539, section 2.6, there are some explanations about the nonce. The nonce must have length exactly 96 bits (12 bytes); however, the following text appears:

The protocol will specify a 96-bit or 64-bit nonce.  This MUST be
unique per invocation with the same key, so it MUST NOT be
randomly generated.  A counter is a good way to implement this,
but other methods, such as a Linear Feedback Shift Register (LFSR)
are also acceptable.  ChaCha20 as specified here requires a 96-bit
nonce.  So if the provided nonce is only 64-bit, then the first 32
bits of the nonce will be set to a constant number.  This will
usually be zero, but for protocols with multiple senders it may be
different for each sender, but should be the same for all
invocations of the function with the same key by a particular
sender.

This is why the test vector in RFC 7539, section 2.8.2, shows this:

IV:
000  40 41 42 43 44 45 46 47                          @ABCDEFG

32-bit fixed-common part:
000  07 00 00 00 

These are a 64-bit IV and a 32-bit "fixed-common part", which are meant to be assembled (concatenated) into the 96-bit value that the algorithm requires.

However, if you look at RFC 7905, you will see that what happens in TLS is something a bit different:

AEAD_CHACHA20_POLY1305 requires a 96-bit nonce, which is formed as
follows:

1.  The 64-bit record sequence number is serialized as an 8-byte,
    big-endian value and padded on the left with four 0x00 bytes.

2.  The padded sequence number is XORed with the client_write_IV
    (when the client is sending) or server_write_IV (when the server
    is sending).

In other words, a full 96-bit (12 bytes) value is generated from the handshake, and the record sequence number is XORed into the last 8 bytes. An alternative design, more in line with the method explained in RFC 7539, would have been to concatenate a 32-bit (4 bytes) value generated during the handshake with the 64-bit (8 bytes) record counter. However, the RFC 7905 designers found it fit to use the XOR method, which can be argued to make nonce reuse between distinct TLS connections even less probable (not that it would matter much, since distinct TLS connections also use distinct encryption keys).

$\endgroup$
  • $\begingroup$ Thanks! I'm not doing TLS, but I appreciate the detailed breakdown. This seems like a very comprehensive answer. $\endgroup$ – Jonas Jul 30 '17 at 18:08
3
$\begingroup$

I wound up finding RFC 7539, and using the test vector from section 2.8.2 of that document. I present the same vector here, in a more code-friendly format:

# test vector courtesy https://tools.ietf.org/html/rfc7539
vector = {
  key: "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",
  input: "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e",
  nonce: "070000004041424344454647",
  ad: "50515253c0c1c2c3c4c5c6c7",
  output: "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116",
  tag: "1ae10b594f09e26a7e902ecbd0600691"
}

As Maarten Bodewes wrote in a comment on my question, I had to pad the leftmost bits of the nonce with the "32-bit fixed-common part", which was given as 07 00 00 00. This validated with OpenSSL 1.1.

$\endgroup$
  • $\begingroup$ For reference, in case it may be helpful to others, I have copied all of the RFC 7539 Poly1305 (plain Poly, not the Chacha+Poly AEAD construction) test vectors in a way that can be machine-parsed. It is not JSON, but someone could easily modify the C program to emit JSON for their use. svnweb.freebsd.org/base/head/tests/sys/opencrypto/… $\endgroup$ – Conrad Meyer Aug 25 '18 at 20:19
1
$\begingroup$

You should rather consider the following RFC: https://tools.ietf.org/html/rfc7905, which describes the use of the ChaCha stream cipher in the TLS protocol.

As said in section 2, in TLS, ChaCha uses a 96-bit nonce built from the client/server_write_IV (derived during the handshake) and the record sequence number.

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.