Draft: draft-ietf-quic-tls

Commentary: As discussed in Cupertino , this PR adds an integrity tag to retry packets to prevent on - network packet corruption from breaking QUIC handshakes . 
 The ODCID being implicit has the same checking properties as an explicit ODCID - the client could ignore that field entirely before . That said I do like the idea of making it harder to not check , by encrypting the token . if we were to encrypt the token with a zero - key , would that still be suitable ? I think the overhead is very small as long as we do n't use HKDF / SHA-2 here . 
 Yes , this is also a concern I have but it might not be worth the complexity . A use case goes like : Embedded microcontroller links to hub via bluetooh and does not have an IP network of its own , but it does have a complete QUIC endpoint that the hub should not interfere with other than deliver to the proper IP remote endpoint and attach a local IP . The device is constrained and only supports ChaCha / Poly crypto . The exception is then the Retry packet ( and possibly the Initial packet , I do n't recall ) . This requires additional software which may be a problem in limited flash storage even if the initial slowdown is otherwise acceptable . 
 encrypting using AES - GCM with an all - zero key and all - zero nonce is just a XOR operation between a known string and the cleartext so the performance cost is very low . QUICv1 uses AES - GCM for Initial packets so all QUIC clients need AES - GCM support . You 're free to build a different version of QUIC that only uses ChaChaPoly , but that wo n't impact the design of QUICv1 . 
 on AES - GCM that is not correct : The crypto part yes , but you do not encrypt , you only GHASH because it is AEAD only ( edit : only authenticated data ) . GHASH is a 128 - bit carryless multiplication over a Galois Field which completes the checksum . If you have CLMUL instructions this is efficient , otherwise not so much . That is where Poly1305 is a gain . CLMUL is not very common on  . controllers even if they have other accellerations , at least for the time being . E.g. ESP8266 . I agree that AES - GCM is the right choice given the circumstances but I think Poly1305 would be better for initial and retry because it would work in a broader range . The benefit of AES - GCM is that highly loaded servers will have the necessary accelleration and may slow down on non AES - GCM - even if this is a tiny fraction of the overall connection cost , it may be important as it fends of noise from the broader internet . 
 I 'm going to have this PR match the algorithm used for Initial , and stick with AES - GCM . Feel free to file a separate issue to discuss using ChaChaPoly for both . 
 I think it is way too late to make such a change , which is why I wrote that given the circumstances I agree with the design choice . 
 Oh I see what you mean . That 's a keen observation . Though , I think that observation also shows the reason some of us thought use of encryption to be beneficial to be pointless . IIUC , suggested using encryption , because it would prevent implementations from using the token without checking the tag . The assumption behind is that AEAD - decryption is going to be an atomic operation . But as you point out , in this particular case , there is a interesting optimization where endpoints can have a pre - built XOR vector , and just do GCM . Does n't that reduce the motivation to use encryption , as it is a complexity to apply XOR ? 
 One reason to use encryption was some implementations ( ? ) already had an AES - GCM implementation , and did n't want to add another hash or crypto function . 
 So though there is a simple and easy optimization , many implementations will have AES - GCM available , but wo n't have an independent GHASH on tap . And getting the mask requires effort . And client 's ca n't predict how many bytes of mask they need , so they would have to get quite a few . I 'm speculating here , but I tend to think that this puts a thumb on the scale in the right way . 
 Yeah , my train of thought as a server stack developer that happens to also work as a client was that I might be interested in having a pre - built XOR mask on the server - side . Then , I might expand that constant vector to the 1,200 bytes , and use it for decoding the Retry packets too . That said , I do agree that not all stack developers would ( think and ) actually implement that way . So I agree that there 's some value in encrypting the token rather than just applying GMAC , at the cost of as small as requiring XOR on the server - side . 
 XOR'ing with zero keys provides no confidentiality and just adds overhead . It is fine to use AES - GCM to tap into the GHASH logic by adding only authenticated additional data . But requiring a large static buffer of zero encrypted data is just overhead . For servers it harms expensive L1 cache and for embedded devices it would require encryption as usual . As I said before GHASH is not the ideal hash for broader use , but given what we have it makes sense for authenticated additional data . 
 I would suggest two revisions to this PR . 's encrypt the Retry Token . of having a zero key , let 's use a per - version key ( analagous to the per - version salt ) . This has the same anti - ossification benefits . 
 the token has been discussed in this PR and does not have consensus zero key can be per - version ( by which I mean that QUICv1 uses a zero key , and QUICv2 can decide to use a different key ) The discussion on this PR and related issue indicates that there is interest in the Retry Integrity Tag . On top of that , the PR as - is is currently covered by the chairs ' latest consensus call . Therefore I 'd suggest you please file a separate issue to discuss encrypting the token , and we can discuss that once this PR has landed . You can also file a second issue to discuss zero key vs non - zero . 
 I think we should change the Retry key for every draft version ( or every time we change the Initial salt ) , as that would prevent ossification assuming that we would be running certain number of draft versions concurrently . I also prefer having a non - zero salt for the upcoming draft version , as it would be an indication to the implementors that the key is a " constant " , rather than zeros that endpoints might synthetically generate ( e.g. , by using ) . 
 Yes , and I 'm responding to that saying we ought to encrypt . 
 Can you elaborate why ? Is this about security , or ossification ? 
 Ossification . Same reason we encrypt Initial Christian Huitema < : 
 To recap my proposal from this morning . should have a separate fixed key derived by HKDF - ing the salt . This is just good key separation practice and we can publish the fixed value in the spec if we want . I think there was consensus on this and I can send text for this . ? should encrypt the token using that fixed key and use the top min(12,dcid_len ) bytes of the DCID . This will provide some obfuscation at the cost of just the AES operation but not the KDF . 
 I 've made this a separate random key to avoid the HKDF . I hope this addresses point ( 1 ) . Regarding point ( 2 ) I do n't care either way but if we were to use a connection ID as the nonce I would use the original destination connection ID since that 's not sent over the wire but required to be known by endpoints . 
 Yes a random key is fine 
 Can we add a test vector to the appendix of the TLS document ? 
 A pull request would be appreciated . One that creates a .js file - so that the value can be automatically generated - would be ideal . 
 In Singapore , we were debating whether to just do checksum , versus encrypt and use an initial vector based on the CID . I wanted to compare the two variants using a benchmark designed for Picotls , but I ran into a little bit of trouble because of the structure of the picotls encryption API , which looks like : 1 ) Initialize an encryption context with the key context , the IV , and the authenticated data 2 ) Encrypt the message 3 ) Compute the AEAD checksum With that API , there is no option to not use an IV , . Using a constant IV would have exactly the same cost as using a variable IV . I went on to compare the time to encrypt 64 bytes versus the time to just authenticate 64 bytes . Results are below for two implementations of AES128 GCM , one from OpenSSL crypto library and another from Windows Bcrypt library -- both measured using 64 bit code on my Windows laptop : verify -- -- bcrypt 0 . openssl 1.1.1c 0 . The times are measured here in microseconds -- these are the average on 100,000 operations . We see that the times are fractions of microseconds in both cases . The encryption operation is more expensive than a simple authentication , 45 % more with brcrypt , 24 % more with openssl . The decrypt operation is 12 % more expensive than the simple authenticate with brcypt , almost the same with openssl . 
 : Let 's have general discussion on , not here . 
 I am busy implementing that , and I have a doubt . In the TLS spec , AEAD keys and vectors are derived from a master secret . Something like : [ sender ] writekey = HKDF - Expand - Label(Secret , " key " , " " , keylength ) [ sender ] writeiv = HKDF - Expand - Label(Secret , " iv " , " " , ivlength ) Then we use the sequence number as a " per packet nonce " , and encrypt using " IV^per - packetnonce " But the text of the TLS draft says " The secret key , K , is 128 bits equal to 0xf5ed4642e0e4c8d878bbbc8a828821c9 . The nonce , N , is 96 bits all set to zero . " Should I read that as equivalent to : retrykey = 0xf5ed4642e0e4c8d878bbbc8a828821c9 retryiv = 0x00000000000000000000000000 per - packet - nonce= 0x00000000000000000000000000 Or is it : retrykey = HKDF - Expand - Label ( 0xf5ed4642e0e4c8d878bbbc8a828821c9 , " key " , " " , 16 ) retry_iv = HKDF - Expand - Label(0xf5ed4642e0e4c8d878bbbc8a828821c9 , " iv " , " " , 12 ) per - packet - nonce= 0x00000000000000000000000000 
 I 'd argue that it is the former . The new text refers to RFC5116 ( AES - GCM ) directly , and that 's natural , because we are hardcoding the use of AES - GCM in this particular case , and because , unlike Initial packets , there is no reason to have a generalized design here . 
 PS . Though I understand your concern that not every TLS stack exposes direct access to AES - GCM . If it is the case that the API we assume our TLS stack to provide is at AEAD level ( e.g , an API that takes ( AEAD algorithm , hash algorithm , secret ) as the input ) , then we should better specify those rather than specifying the AES key and IV directly . 
 In Singapore , we reached an impasse regarding the use of encryption on Retry . From that discussion , there were two outcomes considered : " encrypt " using a fixed key and nonce and do n't encrypt the token ; put the original destination connection ID in the AAD along with the entire packet encrypt the token using the fixed key and a nonce that takes the first 8 bytes of the original destination connection ID ; the AAD only includes the connection IDs and version The first can be reduced to running GHASH with a fixed XOR on the final tag if you really want to save effort . I 've implemented and measured that . The second can be implemented naively , just by building a new AES key for every record . That 's not terribly slow at 2.8x slower . With some nasty tricks , I was able to get the AES version under 1.7x slower . Given the relative cost of these functions , I think that this is an acceptable cost . There 's no guarantee that this is correct - I have n't even checked that these programs produce the right output , but the count of operations is approximately right , so I 'm posting this issue . My code can be found at in case reproduction or validation thrills you . Here 's the output of a sample run on my old machine with about 1Mb of Retry packets fed through the different implementations , which takes about 32 seconds to complete : Now , obviously this is a microbenchmark . The rest of the cost will be elsewhere , but I tried to isolate the variable . Hopefully this helps the decision - making process . 
 Since I did this , I 've realized that even this was an overly pessimistic outcome . By precomputing the XOR stream for CTR protection , it should be possible to make an even faster version of the protected form here . The result should be to narrow the gap between the GHASH - only and encrypted variant further . The difference should be limited to performing an XOR with a fixed value and a single AES operation ( which in my experience is marginally cheaper than an extra GHASH operation , though the addition of ODCID to the AAD is rarely a full block , so it 's hard to predict the exact outcome ) . 
 I might be missing something , but I wonder if you can precompute the XOR streams for all the nonces ( 2 < sup64</suppossibilities ) . 
 Regarding the numbers ,  , thank you for collecting them . I think that the benchmark confirms our expectation that the cost of running AES ( based on fixed key , with different nonce ) does not matter when a conventional CPU with AES offloading is used . Though I think the other question might be if we can require all the DDoS mitigation boxes to be capable of running AES fast . 
 Oh , and I realized again why I did n't do that yesterday : 2 < sup64</suplots of precomputation is a little too much :) Forget that bit . The numbers are basically correct . 
 Here are the results from a different system ( ignore absolute values and concentrate on relative values ): 
 From verify -- -- bcrypt 0 . openssl 1.1.1c 0 . 
 These results are making me lean towards encrypting the token using the original connection ID as a nonce , but I do n't feel overly strongly . I 'll let folks share their thoughts here and if I do n't hear back I 'll add encryption to . 
 It 's possible that the values produced for just authentication are high . Most GCM implementations will still run the AES operation necessary to calculate the mask for GHASH . With a fixed key and nonce , that operation can be replaced with a simple XOR . Doing that additional optimization is not obvious , and it requires breaking the AEAD box open , but I expect that this will happen if implementations REALLY care about every cycle . That would increase the difference a little , maybe as much as my 1.7x ( or 1.5x on the newer CPU ) . 
 I think at this point we need to see some data that supports the argument that encrypting the token is too slow . i think this was your view ? 
 ~What about the cost to the recipient of the Retry packet ? Currently , it is as simple as . IIUC , encrypting the token would replace the hash lookup with a decryption operation?~ I take it back : this is for stateless reset _ , not Retry . I was confusing myself -- apologies for the noise . 
 This seems like an insignificant cost , given that any successful QUIC connection will involve quite a lot more cryptography . 
 I 'm working with our hardware vendor that does DoS mitigation for Azure . Give me a little time and I 'll try to get back to you with data . Also , as far as the cost to the client for Retry , IMO , the higher the better . The goal in this scenario is to make it more expensive for attackers who are spamming initial packets . For a normal client creating a single connection , the cost is insignificant as ekr points out . 
 Here 's a quote from our hardware partner : And a bit more details in follow up : I 'm still of the opinion that , since the Retry packet is almost purely a DoS mitigation mechanism , our view should be what 's the absolute simplest design we can take and still get what 's absolutely necessary , not nice to have ; instead of what else can we squeeze into this feature and hopefully not make it " too expensive " . 
 I 'm in agreement with My interest here is to detect corruption of Retry packets , and a ghash ( equivalent ) is adequate . Everything else is a nice to have ... and I do n't care much for these . 
 I think I agree with  's reasoning . We started this issue by looking only at corruption during transmission . If we focus on that , a simple checksum would be sufficient . We can now start spin - bitting the checksum : crc64 , fnv1a , 0 - keyed aesgcm , md5 ... In fact , for curiosity , what is the spec of ghash ? 
 I do n't think we need to bikeshed the checksum . We already agreed in Cupertino that we 'd use GMAC ( which some people seem to call GHASH apparently ? ) which is equal to AES-128 - GCM with an empty plaintext . 
 I too think anything more than a checksum is unnecessary here . For a corner case scenario where there is NATting , we should not make the common case expensive . Given this is a DoS protection scenario when the system is deemed to be under attack , the mechanism we pick must be the least CPU intensive . My vote is to just keep simple checksum . Anything more is unnecessary because no need has been demonstrated for it . 
 Agreed , let 's stick with a checksum(GMAC / GHASH ) as agreed in Cupertino 
 If it 's not clear , GHASH is the underlying hash algorithm for GCM in addition to the AES encryption track , but it somehow needs a key and a way to specify content and somewhere to place the result . GCM provides the framework for that . GMAC uses the GCM framework with additional authenticated data without encrypted plaintext . Since there is no encryption in GMAC , GMAC is essentially GHASH , but with a clear way to specify the input / output parameters . There is probably an extra AES block encryption in GMAC to derive the GHASH key , and possibly an extra AES block encryption on the output , compared to raw GHASH , but I do not remember exactly . 
 Using raw GHASH over GMAC would avoid the need for an AES module in hardware and only require carryless multiplication ( CLMUL ) for efficient operation , but I ca n't imagine where that case would be very relevant . Even on low - end microcontrollers it is more likely you have SHA-256 and AES hardware than CLMUL , at least for now . From that perspective Poly1305 would be more efficient , but given the nature of Retry and the dependence on AES - GCM elsewhere in the protocol , GMAC or GHASH makes more sense . GMAC is much simpler than GHASH because it can use existing libraries . If GHASH were to be used directly one would have to ensure that it is safe in the applied context and with the supplied key whereas GCM ( and thus GMAC ) is well understood even if it costs a few block encryptions extra . 
 As the PR proposes , GMAC is what we are using . A simple implementation can then just use GCM . Someone looking to save one AES operation can run GHASH with an XOR . This saves on the number of primitives , and for someone willing to build silicon , it can be implemented fairly efficiently . There is some odd middle space with software implementations on low end hardware that might be a little worse off , but they win because they need fewer primitives . A low end device probably needs external help with DoS , so maybe we can say that this is a net win . To be clear ,  's situation is n't ideal either . Implementing GMAC in hardware is certainly not free , falling back to software is not fast , and this style of standalone DoS protection is otherwise relatively simple . But there are a number of competing concerns that need to be balanced . 
 I do n't think you can avoid the AES for GMAC , at least not if both endpoints are to verify the MAC . This is because GCM first receives a " master " key which it then splits into two keys , one for encryption and one for GHASH . This key split is done with an AES operation . So unless both endpoints agree on the GHASH key directly , you need to use AES to derive the GHASH key . EDIT : I missed that if you use a fixed key for Retry , the AES operation is not necessary , or rather , it can be precomputed . 
 I 'm not sure if it is safe to use a fixed key for GMAC because the collision restance of GCM is the same as for GHASH , which limits traffic to roughly 2 ^ 32 bytes and places requirements on non - reuse . 
 Yes , this is not " secure " in the sense that it would meet IND - CCA . After all , we are publishing the key in an RFC . The point is to reduce this to a checksum . The arguments about " encryption " all amount to a desire to make accessing the Retry more difficult for a casual observer . Someone with a copy of the RFC will obviously have no trouble . 
 My +1 goes to just doing GMAC , due to the reasons others have laid out . I 'd also note that it is unlikely that middleboxes will ossify on the fact that the values of the token fields in Retry and Initial packets are equivalent , based on the behavior those developers see on the wire . This is because Retry is expected to be used only when there is an attack . Or , if those developers read the spec , the token will get ossified regardless of if it 's encrypted in the Retry packet . 
 +1 to GMAC , though I care much more about resolving this ( as one of the few remaining wire image issues ) prior to draft-25 than what the actual outcome is . I think it 's important that  be the last wire image change for a while . 
 This thread got poorly forked from . We 've already marked as proposal - ready , and I 'm marking this proposal - ready as well . 
 I do n't really agree with the reasoning here , but I 'm willing to concede I am in the rough . 
 Retry packet does not have a checksum ( e.g. ,  , AEAD tag ) , and therefore is fragile to accidental bit flips that happen on the wire . Are we fine with that ? I ask this , because it seems to me that an one bit flip in some parts of a Retry packet can cause the connection establishment effort to fail in unexpected ways . To paraphrase , are we fine with certain deployments of QUIC being more fragile to accidental bit flips during connection establishment , than compared to TCP ( that always have the checksum ) ? Note that checksum is an optional feature in UDP . Below are what I believe would happen when there is a bit flip in a Retry packet . Consider the case where a bit in the SCID field of a Retry packet is flipped . The client would send back an Initial packet that carries this CID as the DCID . To the server , such an Initial packet would look like an attack , as it contains a Retry Token issued for a different server CID . Therefore , the server is likely to either drop the Initial packet , or close the connection immediately . This behavior would be a must for firewall generating Retry packets in order to redistribute the load between the servers running behind , as the load would be distributed by assigning different server CIDs to different servers . Consider the case where a bit in the Token field of a Retry packet is flipped . The client would send back an Initial packet that carries this corrupt token . To the server , such an Initial packet might look like an Initial packet carrying an outdated token . Then , that server would send back a Retry packet carrying a new Retry token . But the client would drop the new Retry packet ( because the client is required to accept only one Retry packet ) . The client continues retransmitting Initial packets using the corrupt token , until it time outs . Bit flips in the DCID , ODCID , are non - issues as such packets would be dropped by the client . Bit flips in the length fields are likely to cause no harm , and in the worst case scenario it would look either of the above . PS . Version Negotiation packet also lacks a checksum . Though I think we are not going to use that packet type in V1 ( see ) . 
 One option would be to require UDP checksum for QUIC , right ? 
 I do n't think we should mix those layers . It would prevent QUIC from running elsewhere . I do think that everything should be checksummed , like the initial packet . The problem is versioning , so each version need some official   . 
 I do n't think it 's a huge requirement that QUIC must be sent over a medium that is checksummed . IPv6 also has checksum already too , right ? 
 Well , sort of , but since QUIC in 99.999 % if the cases does its own QUICsumming , it would be an odd requirement . 
 Of course UDP sums still make sense for intermediate routing but that is fabric internal . 
 I 'd personally prefer to depend on UDP in this case , especially since it already has performant hardware solutions . Why invent something new when there is a pre - existing solution ? Also , we are only charted for UDP , as far as I know . 
 Sure , only UDP , I 'd just like to avoid hardcoding that dependency for a single case when you could just add a sha256 at the end or something . 
 I think it 's reasonable to recommend using UDP checksums , particularly for unprotected packets such as Retry and VN . I had n't thought through the implications of a corrupted Retry token to realize it makes the handshake unrecoverable . I 'm not sure I want to fix that issue now , but I think it 's worth documenting . 
 FWIW , I think that there are three options : the behavior , recommend using UDP checksums . a QUIC error code that recommends the client to retry establishing a connection . Then , a server recognizing the situations listed above can send that error code . a checksum field to the Retry packet ( the use of the checksum can be an optional feature , much like that of UDP ) . The algorithm could be CRC , or GMAC ( i.e. AES - GCM without any encrypted data ) . 
 I 'd suggest not using GMAC , at least for VNEG , because GMAC might be outdated before VNEG , and it is complex . 
 Retry is a concept specific to QUIC v1 , which already depends on AES - GCM for protecting the integrity of Initial packets . Therefore , IMO use of GMAC would be a reasonable choice if we decide to give it a checksum . Use of GMAC would be simpler than other algorithms , because then QUIC stacks can use the existing code that encrypts the Initial packet ; Retry packet would be a packet that is encrypted exactly the same way as the Initial packet , with the only exceptions being that the nonce is fixed to zero and that the encrypted part is zero bytes . PS . Yes , we need to think about GCM becoming obsolete for Version Negotiation packets . 
 I do n't think we disagree . I did mention VNEG explicitly . But , one could argue that that if VNEG uses a different hash , why not use it for Retry as well - not that I care that much . 
 This definitely seems like a reasonable concern to me . I 'd be inclined to extend to Retry packets the use of obfuscation / encryption similar to what we do for Initial packets . Heck , we could even encrypt the retry token itself if we really wished to ( in keeping with our philosophy of obfuscating / hiding as much as humanly possible ) 
 +1 . Using AES - GCM-128 with initial salts for retry could solve this elegantly . 
 I am not sure . I doubt if it would be practical to suggest the cloud users to ask their cloud service providers " are you currently using UDP checksums , and committed to using that in the future " before starting to use QUIC . Also , UDP checksums are rewritten by NAT devices , so there is no way to tell for an endpoint if a UDP packet is ( was ) protected by the UDP checksum . 
 Presumably devices that rewrite addresses and checksums also check checksums on the way in ... Right ? :) This seems like a fine design issue . It 's a fairly big change , for Retry , and late , but not impossible . Version Negotiation is much , much more difficult to retrofit , so I think that we should concentrate on Retry here and open a VN issue if anyone feels strongly about that . Using Initial packet protection is n't crazy . We 'd be able to drop ODCID if we did that . Saying that we do n't care is also rational . The UDP checksum is imperfect ( especially when it comes to catching bit flips ) , but we are talking about having maybe a few connections per day time out , globally . I did n't do the numbers on that , but my recollection is that the odds are that small . 
 Totally agree that VN is a different issue and this should focus on Retry . I also love the idea of getting rid of ODCID ! That has always seems like an ugly wart to me and replacing it with initial packet protection seems much more elegant . ( As well as solving the packet corruption issue ) 
    work ] showed that the internet is actually quite terrible and that the TCP / UDP checksums are super important . From the conclusion section : " Our trace data shows that the TCP and UDP checksums are catching a signi ca nt number of persistent errors . In practice , the checksum is being asked to detect an error every few thousand packets . " Of course , this data is dated , but given the I would n't be surprised if the numbers today were not too far off from the ones he reports . right that we can not rely on the UDP checksum being on in all circumstances , but I would guess that it 'll be on in most deployments . That said , the failure rate can be high enough to be bothersome in specific deployments . I would argue that it 's a valuable goal to ensure that QUIC is protected against trivial corruption in all deployments . I agree with that encrypting this packet is aligned with how we do things anyways , and there 's additional benefit in being able to remove the ODCID . It is a bit late in the process , but this is a fairly isolated and local change . I would argue that unless there 's a strong reason to not encrypt this packet , we should encrypt it . 1 : 
 I would prefer not to encrypt the Retry packet and keep things how they are . We 've done a lot of work to create a design that requires the least amount of work to accomplish the stateless retry scenario . Adding an additional level of encryption , just to get a checksum ( something that is practically , already accomplished in most deployments with UDP ) is just going to add complexity ( we 've already started work with HW vendors on the current model , and it 's complexity , compared to TCP , is already a major hurdle ) . 
 To quote the paper links to above : That translates to less than 0.1 % error rate on the high side . We already know that a significant percentage of QUIC connections will fail -- 3 % , IIRC , according to Google -- for other reasons . < 0.1 % is 3 % of the 3 % ... Is it worth it ? 
 I think the concern here is not that the handshake fails , but that it takes a long time to fail in a circumstance where UDP connectivity does work . Chrome currently uses quite a short handshake timeout of 4 seconds , but if clients use longer timeouts , then users could wait a long time in this unrecoverable state . I think UDP checksums are probably sufficient here , but these long timeout based handshake failures are very user visible . 
 writes : This is an overkill . Just use CRC32 , which is simple , cheap ( there is a dedicated CPU instruction for it ! ) , and does what we need . 
 Not arguing against CRC but intels CRC instruction is not necessarily standard CRC checksum for reasons that elude me . 
 As people have been discussing , use of AES - GCM ( or GMAC ) is a simplification in terms of code reuse and also provides us the chance to get rid of the ODCID field . Clarification : GMAC is any - cipher - with - GCM with zero - byte encrypted text , so if it 's going to be called AES - GCM or GMAC simply depends on if we decide to encrypt the token . If we are going to make the use of the QUIC - level checksum an optional feature , it would make more sense to not encrypt the token regardless of the QUIC - level checksum being used - which means GMAC . 
 Do you believe that obfuscating the packet is inconvenient at this late time , or that it is a lot of work for the HW vendors ? 
 It 's more about the cost per packet for the HW . With TCP , the devices handle millions , if not tens of millions of connections per second using the SYN cookie method . They wo n't be able to get anywhere close to that with QUIC Retry packets , and it will be even worse if every packet must be individually encrypted . 
 So far our design has been to take the processing costs in favor of making the protocol more reliable . Given that hardware will need to support sending encrypted Initial packets , I do n't see the cost on Retry as a blocking issue . 
 The HW I am referring to are standalone devices which only do the following : incoming Initial packets ( primarily the Token field ) . valid , allow the packet through . If invalid , drop the packet and reply with a Retry packet . No encryption or decryption of packets is necessary . 
 Is there crypto involved in constructing a Retry token that ca n't be spoofed ? 
 Two options exist : use crypto . generate random token and save in a cache . I 'm not sure which will be used in the final design . Even if we use crypto for the token , any addtional crypto for the packet is at least twice the CPU required to encrypt the token ; halving the packets per second . 
 This assumes that the great majority of packet processing time is spent on cryptographic operations , and that has never been my personal experience . 
 That may be so when doing things at the HTTP layer . I have had completely the opposite experience with my QUIC only performance tests . Encryption / decryption is closer to 60 % to 75 % of the CPU . UDP is the next largest , but because we 've done a lot of optimizations in this area it 's not that much . But for HW , it 's a whole different story . There is nothing but read QUIC header , read token , write token , write UDP packet . Read / write token will be , by far , the CPU bottleneck . 
 I suspect that by " read / write token " you mean the compute / look - up token step , which I suspect wo n't be cheap , and that 'll be there regardless of our decision on this issue . I have a hard time believing that a hardware - accelerated AES - GCM step will take much longer than read / writing UDP . Making the protocol less reliable for the sake of a hypothetical performance result sounds like premature optimization to me . 
 UDP can gain huge perf boosts from batch processing . Crypto does n't get the same boosts because each packet ends up needing a different key . 
 I agree with that I have never seen profiles indicating that AES with standard(ie : AES - NI or AVX ) acceleration is more costly than sending UDP packets . I 'm confused about whether you have an amazingly fast UDP path or a very slow AES - GCM path , relatively speaking . I 've seen CPU profiles where 50 % of CPU is spent sending UDP packets on two different platforms now , but I 've never seen a profile where AES - GCM consumed more than sending UDP packets . Can you share any measurements that might explain the large disparity between CPU profiles I 've seen and you 've seen or shed any light on why our experiences are so different ? 
 A hardware forwarding UDP device has a completely different path from any general purpose OS , so it ca n't be compared at all . AES has a number of roundtrips which requires a pipeline depth even if each step is fast and it might require custom specs if you have a fast simple core to start with . Another option is to use good enough pseudo - crypto like some key mixed into a splitmix64 algorithm with frequently changing keys , at least for a part of the token field that affects the hardware filter . 
 I agree with and Making the protocol robust against bit flips should be our first priority , even if it 's a ( little ? ) bit more expensive for the sender of Retry packets . 
 +1  is important . 
 To be fair , this is n't just about running an AEAD over a relatively short packet . It 's running HKDF as well . If you were n't inclined to process the packet , that 's a fairly big jump in cost . So it 's not just twice the cost .  tends to be more expensive than AES , even with AES and its fairly clunky key setup . You run multiple iterations of SHA-256 to generate the keys . I personally think that this is worth doing , but I do n't operate a service at scale . I 'd like something more concrete than " this is expensive " , but I 'm willing to concede that this is a considerable increase in cost for a DoS protection device . 
 I share the same view . We have discussed this previously , and the conclusion was to not apply AEAD to the Retry packets . I think this issue warrants us the chance to revisit the previous conclusion , but I think it 's worth wondering if we can find a solution without enforcing the use of AEAD . That 's why I am suggesting the use of optional GMAC . The straw - man is as follows . When a client receives a Retry packet , it checks the last 16 bytes of the packet . If the last 16 bytes are equal to the original DCID , the packet is considered to be not protected by the AEAD . The token is the rest of the packet payload . If the last 16 bytes is anything else , the packet is considered to be protected by the AEAD . The requirement to the server would be that it MUST either AEAD - protect the Retry packet , or use the UDP checksum . There would no longer be a ODCID field . I think this straw - man might be somewhat better than the " worst " of the two approaches being combined , and that it might be something that we can go with . 
 I 'm not exactly what more you need than " this is expensive " . Obviously the HW ca n't be built and then we decide what to do . I just did a quick and dirty perf test ( not sure if RSS was completely configured ) , and these were some numbers of our software QUIC stack for a CPU limited sender : The leftovers are for various smaller things . This is also on a machine that does n't support all the HW offloads we support in Windows , so the UDP number can go down ( drastically , giving numbers closer to the one I gave before ) . Also , while I am no crypto expert , I do not believe Windows bcrypt APIs are overly costly , compared to other libraries . I know UDP has been expensive in the past , but that 's no longer the case . I feel we should not use that past behavior as an excuse to default to computationally expensive solutions , where other solutions are available . And again , like mikkelfj said you ca n't directly compare HW and SW solutions very well . P.S. Receive side stats for the same test : 
 So to be clear , I do vote a Initial like authentication scheme in Retry , as I hope I have stated earlier . But concern is valid . I just do n't think they have to be mutually exclusive : You can have tail authentication tags and have other data that is fast for hardware to verify even if the filter is not perfect . I 'd rather not rely on UDP headers for anything but IP routing . 
 If we want to give servers options , we could allow servers to send Initial packets with no payload and the client would treat those identically to a Retry . 
 The benefit of tokens are that the state can be offloaded . Is that possible with Initial packets ? 
 Functionally , a Retry or Initial with no payload should be identical , minus the packet format ? Both have a Token Both have a way of validating the original CID Both have no other payload I believe we considered using the Initial packet as a Retry packet in the Stream0 design team , but the reason we decided not to was some members thought the encryption cost was too high . It looks like we 're still in the same place in that regard ? 
 Those numbers are very different from the ones I 've seen . In particular , I 've never seen UDP send lower than encrypt that I can recall . I will note that I 'm typically looking at profiles of a full process(ie : a load balancer / cache / etc ) , not just the QUIC stack , but even so your crypto is much more expensive than I 'd expect . Are you using AES - NI and/or AVX instructions ? 
 Yes , Bcrypt APIs make full use of CPU instruction offload where available . As I indicated , these numbers are from a QUIC layer performance test ( no HTTP involved ) between two machines on a 40Gbps switch ( i.e. unlimited bandwidth ) . The machines are CPU limited . UDP CPU costs are greatly reduced by batching many packets worth of data in a single send call . HW offloads improve this even more . 
 Ah thank you for the clarification . So the idea here is to allow the servers to send tokens in Initial packets ( which is currently prohibited ) , when the payload length is zero . I agree that that 's a possibility , and that we could have taken that path . OTOH , I am not sure if we should consider that at this late stage , even if we are to authenticate and encrypt the Retry packet . The reasons are : Use of the Initial packet does reduce the complexity . It just changes how the clients make the distinction between Initial vs. Retry . The existence of PN field is concerning , as the client can not ACK a Retry . From the server 's point of view , Retry is an action that happens before a connection is established , and I like the fact that it is given a distinct packet type rather than being defined as a subtype of a packet that could be part of a connection ( i.e. carrying PN ) . And we have enough bits to express the four long header packet types . 
 Thanks I forgot Retry did n't have a PN . That is a bit concerning . 
 I do n't really care much how Retry is protected , whether it is a checksum at the end or the Initial format , but aside from Version Negotiation , it is the only packet without self - contained integrity checks . That is a pain when forwarding packets , for example over a bluetooth or USB serial gateway . Now you have to add a packet version specific framing layer and since packets can be coalesced , that makes it all the more fun . 
 Even if we do nothing here , we need to note that UDP checksums are a good idea ( cite RFC 8085 , Section 3.4 ) . And we need to point out that a bad Retry token will result in the server dropping the packet . Also , corruption of Retry could make it hard to distinguish between Retry tokens and NEWTOKEN tokens unless the discriminator the server uses is in the clear ( in which case , the distinction is erased , but that seems unlikely , or maybe you can create markers with a Hamming distance 1 so that bit flips are less likely to erase that signal . Advice for clients would be to make a new connection if an Initial with a token times out . What is likely to happen for HTTP - at least in the near term - is that clients will fall back to TCP . suggests that we could allow a server to generate a CONNECTIONCLOSE in response to an Initial with a token that is probably an invalid Retry token . This would allow the server to signal to the client that the connection is busted . However , this wo n't work if we make the proposed change to retain the Initial keys after Retry ( ) , because the server wo n't be able to get the original keys . 
 This also becomes an attack vector , since an attacker could inject such a CONNECTIONCLOSE . 
 That sort of injection is already possible . 
 How is the bit flip error different from an attacker spoofing a Retry packet with made up values ? 
 Discussed in Cupertino . Agreement in the room for MANDATORY use of AES - GCM with zero key and zero nonce to protect the Retry token . The entire packet would be AAD . addition of potentially eliding DCID to be thought through by assignee of issue . 
 Created PR to try to address this . 
 Moving discussion back here to address to encrypt the token . Discussion thus far identified a very minor performance / value trade - off . Running AES might cost some . This is minor , as the key is fixed and so the AES key preparation costs are erased ; you might even pregenerate the fixed key stream and just run XOR . With that realization , we observed that the value provided by encryption was equivalent to the difficulty of applying that XOR . There is a step higher , which is to use a key derived from the connection ID , but that depends on running a KDF and setting up AES , which I think we all agreed was prohibitive . I think that the idea of moving from a 0 key to a fixed key is easy , so we should definitely do that even if we do nothing else . 
 I 've incorporated 's suggestion and switched the key from all - zeroes to the 
 In PR , wrote : In Singapore , we were debating whether to just do checksum , versus encrypt and use an initial vector based on the CID . I wanted to compare the two variants using a benchmark designed for Picotls , but I ran into a little bit of trouble because of the structure of the picotls encryption API , which looks like : 1 ) Initialize an encryption context with the key context , the IV , and the authenticated data 2 ) Encrypt the message 3 ) Compute the AEAD checksum With that API , there is no option to not use an IV , . Using a constant IV would have exactly the same cost as using a variable IV . I went on to compare the time to encrypt 64 bytes versus the time to just authenticate 64 bytes . Results are below for two implementations of AES128 GCM , one from OpenSSL crypto library and another from Windows Bcrypt library -- both measured using 64 bit code on my Windows laptop : verify -- -- bcrypt 0 . openssl 1.1.1c 0 . The times are measured here in microseconds -- these are the average on 100,000 operations . We see that the times are fractions of microseconds in both cases . The encryption operation is more expensive than a simple authentication , 45 % more with brcrypt , 24 % more with openssl . The decrypt operation is 12 % more expensive than the simple authenticate with brcypt , almost the same with openssl . 
 I appreciate your effort to coordinate the threads ; I think the discussion is mostly happening in . 
 Marking as proposal - ready on the basis that we can proceed with just GMAC and later encrypt if we decide . 
 If you remove the ODCID Len from the pseudo - packet , this might allow you to simplify the description such that you do n't need to define a pseudo - packet in the first place . Thank you for writing this up . Looks good to me . I do not have a strong opinion regarding if we should encipher the token ( using a fixed key ) to enforce the client to consult the integrity tag . It 's good in terms of enforcing verification . It does not change the complexity of the design . There would be a performance penalty when using a software cipher ( i.e. when you do not have AESNI).I may have sounded negative , but the protection against network errors alone justifies this PR . As long as we do n't make foolhardy statements about mitigating attacks , I am fine . Let 's get this in . If possible , I think it 'd be nice to add this to the transport draft , since if we ever changed crypto handshakes , I expect we 'd still want this . If that conversation is a wider discussion , I 'm happy to have it on a separate PR .
Paragraph (version A):
Each upper layer (handshake, alerts, and application data) is carried
as a series of typed TLS records.  Records are individually
cryptographically protected and then transmitted over a reliable
transport (typically TCP) which provides sequencing and guaranteed
delivery.
Paragraph (version A):
The TLS authenticated key exchange occurs between two entities:
client and server.  The client initiates the exchange and the server
responds.  If the key exchange completes successfully, both client
and server will agree on a secret.  TLS supports both pre-shared key
(PSK) and Diffie-Hellman (DH) key exchanges.  PSK is the basis for
0-RTT; the latter provides perfect forward secrecy (PFS) when the DH
keys are destroyed.
Paragraph (version B):
Internally, TLS is a layered protocol, with the structure shown in
tls-layers.
Paragraph (version B):
QUIC QUIC-TRANSPORT assumes responsibility for the confidentiality
and integrity protection of packets.  For this it uses keys derived
from a TLS handshake TLS13, but instead of carrying TLS records over
QUIC (as with TCP), TLS Handshake and Alert messages are carried
directly over the QUIC transport, which takes over the
responsibilities of the TLS record layer, as shown in quic-layers.
Paragraph (version B):
Rather than a strict layering, these two protocols cooperate: QUIC
uses the TLS handshake; TLS uses the reliability, ordered delivery,
and record layer provided by QUIC.
Paragraph (version B):
CRYPTO frames and CONNECTION_CLOSE frames signaling errors at the
QUIC layer (type 0x1c) MAY appear in packets of any encryption
level except 0-RTT.
Paragraph (version B):
CONNECTION_CLOSE frames signaling application errors (type 0x1d)
MUST only be sent in packets at the 1-RTT encryption level.
Paragraph (version A):
Due to reordering and loss, protected packets might be received by an
endpoint before the final TLS handshake messages are received.  A
client will be unable to decrypt 1-RTT packets from the server,
whereas a server will be able to decrypt 1-RTT packets from the
client.
Paragraph (version B):
Due to reordering and loss, protected packets might be received by an
endpoint before the final TLS handshake messages are received.  A
client will be unable to decrypt 1-RTT packets from the server,
whereas a server will be able to decrypt 1-RTT packets from the
client.  Endpoints in either role MUST NOT decrypt 1-RTT packets from
their peer prior to completing the handshake.
Paragraph (version B):
TLS implementations might provide all 1-RTT secrets prior to
handshake completion.  Even where QUIC implementations have 1-RTT
read keys, those keys cannot be used prior to completing the
handshake.
Paragraph (version B):
Retry packets (see the Retry Packet section of QUIC-TRANSPORT) carry
a Retry Integrity Tag that provides two properties: it allows
discarding packets that have accidentally been corrupted by the
network, and it diminishes off-path attackers' ability to send valid
Retry packets.
Paragraph (version B):
The secret key, K, is 128 bits equal to
0xf5ed4642e0e4c8d878bbbc8a828821c9.
Paragraph (version B):
The nonce, N, is 96 bits all set to zero.
Paragraph (version B):
The plaintext, P, is empty.
Paragraph (version B):
Once the handshake is confirmed (see handshake-confirmed), an
endpoint MAY initiate a key update.
Paragraph (version B):
The Key Phase bit indicates which packet protection keys are used to
protect the packet.  The Key Phase bit is initially set to 0 for the
first set of 1-RTT packets and toggled to signal each subsequent key
update.
Paragraph (version B):
The Key Phase bit allows a recipient to detect a change in keying
material without needing to receive the first packet that triggered
the change.  An endpoint that notices a changed Key Phase bit updates
keys and decrypts the packet that contains the changed value.
Paragraph (version A):
This ensures that once the handshake is complete, packets with the
same KEY_PHASE will have the same packet protection keys, unless
there are multiple key updates in a short time frame succession and
significant packet reordering.
Paragraph (version A):
A packet that triggers a key update could arrive after the receiving
endpoint successfully processed a packet with a higher packet number.
This is only possible if there is a key compromise and an attack, or
if the peer is incorrectly reverting to use of old keys.  Because the
latter cannot be differentiated from an attack, an endpoint MUST
immediately terminate the connection if it detects this condition.
Paragraph (version B):
ex-key-update shows a key update process, where the initial set of
keys used (identified with @M) are replaced by updated keys
(identified with @N).  The value of the Key Phase bit is indicated in
brackets [].
Paragraph (version B):
Endpoints maintain separate read and write secrets for packet
protection.  An endpoint initiates a key update by updating its
packet protection write secret and using that to protect new packets.
The endpoint creates a new write secret from the existing write
secret as performed in Section 7.2 of TLS13.  This uses the KDF
function provided by TLS with a label of "quic ku".  The
corresponding key and IV are created from that secret as defined in
protection-keys.  The header protection key is not updated.
Paragraph (version B):
The endpoint toggles the value of the Key Phase bit and uses the
updated key and IV to protect all subsequent packets.
Paragraph (version B):
Keys of packets other than the 1-RTT packets are never updated;
their keys are derived solely from the TLS handshake state.
Paragraph (version B):
The endpoint that initiates a key update also updates the keys that
it uses for receiving packets.  These keys will be needed to process
packets the peer sends after updating.
Paragraph (version B):
If a packet is successfully processed using the next key and IV, then
the peer has initiated a key update.  The endpoint MUST update its
send keys to the corresponding key phase in response, as described in
key-update-initiate.  Sending keys MUST be updated before sending an
acknowledgement for the packet that was received with updated keys.
By acknowledging the packet that triggered the key update in a packet
protected with the updated keys, the endpoint signals that the key
update is complete.
Paragraph (version B):
An endpoint can defer sending the packet or acknowledgement according
to its normal packet sending behaviour; it is not necessary to
immediately generate a packet in response to a key update.  The next
packet sent by the endpoint will use the updated keys.  The next
packet that contains an acknowledgement will cause the key update to
be completed.  If an endpoint detects a second update before it has
sent any packets with updated keys containing an acknowledgement for
the packet that initiated the key update, it indicates that its peer
has updated keys twice without awaiting confirmation.  An endpoint
MAY treat consecutive key updates as a connection error of type
KEY_UPDATE_ERROR.
Paragraph (version B):
An endpoint that receives an acknowledgement that is carried in a
packet protected with old keys where any acknowledged packet was
protected with newer keys MAY treat that as a connection error of
type KEY_UPDATE_ERROR.  This indicates that a peer has received and
acknowledged a packet that initiates a key update, but has not
updated keys in response.
Paragraph (version B):
Endpoints responding to an apparent key update MUST NOT generate a
timing side-channel signal that might indicate that the Key Phase bit
was invalid (see header-protect-analysis).  Endpoints can use dummy
packet protection keys in place of discarded keys when key updates
are not yet permitted.  Using dummy keys will generate no variation
in the timing signal produced by attempting to remove packet
protection, and results in all packets with an invalid Key Phase bit
being rejected.
Paragraph (version B):
Once generated, the next set of packet protection keys SHOULD be
retained, even if the packet that was received was subsequently
discarded.  Packets containing apparent key updates are easy to forge
and - while the process of key update does not require significant
effort - triggering this process could be used by an attacker for
DoS.
Paragraph (version B):
For this reason, endpoints MUST be able to retain two sets of packet
protection keys for receiving packets: the current and the next.
Retaining the previous keys in addition to these might improve
performance, but this is not essential.
Paragraph (version B):
Packets with higher packet numbers MUST be protected with either the
same or newer packet protection keys than packets with lower packet
numbers.  An endpoint that successfully removes protection with old
keys when newer keys were used for packets with lower packet numbers
MUST treat this as a connection error of type KEY_UPDATE_ERROR.
Paragraph (version B):
For receiving packets during a key update, packets protected with
older keys might arrive if they were delayed by the network.
Retaining old packet protection keys allows these packets to be
successfully processed.
Paragraph (version B):
As packets protected with keys from the next key phase use the same
Key Phase value as those protected with keys from the previous key
phase, it can be necessary to distinguish between the two.  This can
be done using packet numbers.  A recovered packet number that is
lower than any packet number from the current key phase uses the
previous packet protection keys; a recovered packet number that is
higher than any packet number from the current key phase requires the
use of the next packet protection keys.
Paragraph (version B):
Some care is necessary to ensure that any process for selecting
between previous, current, and next packet protection keys does not
expose a timing side channel that might reveal which keys were used
to remove packet protection.  See hp-side-channel for more
information.
Paragraph (version B):
Alternatively, endpoints can retain only two sets of packet
protection keys, swapping previous for next after enough time has
passed to allow for reordering in the network.  In this case, the Key
Phase bit alone can be used to select keys.
Paragraph (version B):
Endpoints need to allow for the possibility that a peer might not be
able to decrypt packets that initiate a key update during the period
when it retains old keys.  Endpoints SHOULD wait three times the PTO
before initiating a key update after receiving an acknowledgment that
confirms that the previous key update was received.  Failing to allow
sufficient time could lead to packets being discarded.
Paragraph (version B):
The KEY_UPDATE_ERROR error code (0xE) is used to signal errors
related to key updates.
Paragraph (version A):
QUIC transport parameters are carried in a TLS extension.  Different
versions of QUIC might define a different format for this struct.
Paragraph (version B):
QUIC transport parameters are carried in a TLS extension.  Different
versions of QUIC might define a different method for negotiating
transport configuration.
Paragraph (version B):
During a key update, the time taken to generate new keys could reveal
through timing side-channels that a key update has occurred.
Alternatively, where an attacker injects packets this side-channel
could reveal the value of the Key Phase on injected packets.  After
receiving a key update, an endpoint SHOULD generate and save the next
set of receive packet protection keys, as described in receive-key-
generation.  By generating new keys before a key update is received,
receipt of packets will not create timing signals that leak the value
of the Key Phase.
Paragraph (version B):
This depends on not doing this key generation during packet
processing and it can require that endpoints maintain three sets of
packet protection keys for receiving: for the previous key phase, for
the current key phase, and for the next key phase.  Endpoints can
instead choose to defer generation of the next receive packet
protection keys until they discard old keys so that only two sets of
receive keys need to be retained at any point in time.
Paragraph (version A):
TLS ExtensionsType Registry TLS-REGISTRIES - IANA is to register
the quic_transport_parameters extension found in quic_parameters.
The Recommended column is to be marked Yes.  The TLS 1.3 Column is
to include CH and EE.
Paragraph (version B):
QUIC Transport Error Codes Registry QUIC-TRANSPORT - IANA is to
register the KEY_UPDATE_ERROR (0xE), as described in key-update-
error.