Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
UDP for games – encryption and DDoS protection (ithare.com)
104 points by based2 on April 27, 2016 | hide | past | favorite | 44 comments


>As a side bonus, with proper encryption you can be sure that network errors which corrupt your packets are not going undetected

Shouldn't you be verifying a MAC before decrypting anyways? Relying on decrypting scrambled data, especially for a bitflip, seems like a bad idea.

>Potential attack here is about attacker modifying the (unencrypted/unsigned) data coming to the victim’s Client,

So establish a session key, and include an HMAC on each message? Or not even a session key, if you're worried about re-establishing sessions, but a long-term per-client key (or per-install or whatever). Hardly seems like you need QUIC/DTLS for this simple scenario.

Then all the part about DDoS'ing a UDP connection establishment... Why not just use TCP for sign-in and key establishment, and get 3-way handshake for free. That eliminates spoofing IPs. From there, just have monitors that determine when you're "under attack" and rate limit by IP address, just like any anti-DDoS does. Coming up with all this other stuff seems like an overly complex way.


> Shouldn't you be verifying a MAC before decrypting anyways? Relying on decrypting scrambled data, especially for a bitflip, seems like a bad idea.

Oh, we're going into the realm of MAC-then-encrypt vs Encrypt-then-MAC, which is waaaay out of scope of this book. As it's mentioned in some footnote, under "encryption" I've meant "the whole package, including encryption, integrity authentication, etc." (which is currently routinely done by some kind of AEAD for wired data). I certainly don't want to go into a deep crypto-level discussion of AEAD etc. in a book for app-level developers; what I'm aiming for here, is a simple recipe of "just do it this way, it will work".

> Why not just use TCP for sign-in and key establishment, and get 3-way handshake for free. That eliminates spoofing IPs. From there, just have monitors that determine when you're "under attack" and rate limit by IP address, just like any anti-DDoS does.

But then, for UDP (and you DO need UDP for fast-paced games) you will need your own protected protocol (probably using keys exchanged over TLS-over-TCP). There are many ways to do your own protocol wrong, and only a few to do it right; and if an average app-level developer will try doing it - it is much more often a disaster than not :-(. As a result, I am arguing for using standard security protocols wherever possible (and the anti-DDoS hack described there keeps DTLS security intact, this IS important).


BTW, when I've wrote about those "undetected network errors", I've meant those-network-errors-which-arise-in-UNENCRYPTED-packets (as 16-bit UDP checksum is certainly not enough for this purpose). And DTLS etc. take care of packet corruption themselves (actually, they consider it an attack).


The UDP checksum is twice unreliable since it's optional.


Since IPv6 it is mandatory, but it still sucks :-(


"encryption is to prevent eavesdropping and session hijacks by the third party [...] it is Really Important for stock exchanges"

Stock (and other financial) exchanges are major users of UDP, but in general they do not encrypt it.

Exchanges give clients direct connections to their networks using dedicated Ethernet lines, one per customer. This, combined with datacenter security, is what prevents eavesdropping and session hijacking. They do not use UDP over the Internet at all.


> ...if some artifact within your game costs $20K+ of real-world dollars – you should start thinking about [encryption] seriously. In these cases, game account becomes as important as (and for quite a few people out there – much more important than) a bank account. Which carries all the security implications of the bank, including (but not limited to) encryption.

Sorry to nitpick but UDP seems like the wrong protocol for authentication. TCP is connection oriented. It's a better choice for logging in, refreshing a server list or any transaction where reliability is more important than latency. Most online multiplayer games use both TCP and UDP for this reason.

As for encryption, of course you should always use it when transmitting sensitive data. For a game where the client has no control over the state of the world and all packets are at the discretion of the server (`move here`, `click that`, etc.), encryption might be overkill. The source port value (first 16 bits of a UDP packet) can be used to identify the source of a packet and associate it with a client's server side session. But I'm not saying that encryption is a bad idea (it never is); it just depends on how much you can afford.

[1] https://en.wikipedia.org/wiki/Datagram_Transport_Layer_Secur...


TCP and UDP occurring over the same route on a WAN can wreak havoc with UDP packet loss.[1] Using both is only a good idea up until you dig deep into socket performance. A network is not a piece of software where you can isolate things and expect them to behave in an isolated way, everything you push over it interacts with everything else that you are pushing over it. This "use UDP and TCP" is a dangerous anti-pattern because it seems to be an obvious solution from a software perspective, but is in reality a problem from the electron perspective. To paraphrase a famous quote:

> Some game developers, when confronted with a TCP problem, think "I know, I'll add UDP." Now they have two problems.

Games that implement their own protocol over UDP typically have packet modes (frequently a "channel" akin to WebSocket channels): reliable, ordered delivery or neither or either. There is almost always the notion of a connection, where appropriate. This is the correct way to do things, and with Raknet now being free you really have no excuse to do it any other way.

For authentication you could just use the reliable/ordered channel and carry on as though you were using TCP - which somewhat defeats the purpose of this article.

[1]: https://www.isoc.org/inet97/proceedings/F3/F3_1.HTM


With my example for login, my assumption was that one would use short-lived TCP connections (i.e. server receives login packet, sends success/fail and finally closes the connection), not a simultaneous, persistent connection where TCP congestion (and thus UDP packet loss) becomes an issue. But maybe I'm wrong on this one.

Thanks for sharing by the way, great article (although as an aside, some of those graphs are really hard to read).


> some of those graphs are really hard to read

I have to admit that I had to squint a bit while reading it myself.


I took it to mean that you might use UDP for doing stuff in game that might result in losing items. Like, I dunno, maybe an attacker on campus or on WiFi proxies your connection then makes you lose or drop expensive items.

I'm not sure how realistic a threat this is. Most likely crypto just helps obfuscate the game to make cheating/RE a bit harder.


When I used to play games like this the popular technique was to dos your connection, then just kill you and take your stuff while you're trying to reconnect.


This was pretty common in the early days of Ultima Online, since your character persisted for about three minutes after you logged out (unless you were in your house or an inn).


I don't understand - DTLS is covered in depth in the article (as are your or concerns)


> Sorry to nitpick but UDP seems like the wrong protocol for authentication.

While authentication over UDP is indeed a bit more difficult if implementing it yourself, fortunately there is DTLS which is a very official and well-recognised way to implement authentication over UDP (very briefly - DTLS does make a handshake, and it does have a session, but this session operates in a packet-oriented mode which supports reordering and packet loss).


> Both known-to-me implementations of CurveCP (NaCl and libchlorium) seem to be pretty much abandoned as of beginning of 2016,

libsodium (the continuation of NaCl) is actively maintained.


Last thing I've heard was that specifically CurveCP was separated from libsodium into libchlorium, and it is not maintained now.


This talks about a "proof of work" system to mitigate DDoS. What's the "hello world" of such a system look like? It came up in a CTF recently and I had no idea where to start.


Here's a really simple proof of work system: during connection, the server tells the client some randomly generated bytes and a number of bits, and then the client has to come up with a string whose MD5 hash (or whatever algorithm you want to use) has its first N bits match the first N bits of the bytestring that the server gave.

It's easy for the server to adjust the difficulty of the proof-of-work: it just needs to given a larger bitcount to force the client to match a longer prefix.


How is the client expected to proceed ? Should it try a,b, ... aa, ab, ... ? Couldn't a rainbow table allow to cheat with such algorithm ?

It seam that the computation of a MAC or an encryption where the key serves as salt could prevent such cheating. But this computations are now hardcoded in the cpu which won't be the same proof of work for everybody.


Its not possible to maintain a rainbow table because the server would specify the string to start with.

Server sends "randomstringthatalsoreferencescurrenttime"

Client needs to add strings to this such that the MD5 hash of the result has the first N bits as 0 where N specifies the difficulty. The client is expected to send what it added to the server's string and the server verifies the proof of work.


Oh, I left that out of my description. Having the server specify the salt or the text required to be at the front of the value to be hashed does make things better.


This is some old code, based off of the hashcash algorithm, that prototypes protecting DNS servers using a proof of work (also UDP). https://github.com/jevinskie/SlothNS/blob/master/pow/pow.c#L...


Maybe I'm misunderstanding things - is this a particular algorithm (that I could look up in a textbook for example?)


After looking into it more, I think the implementation is more Coelho et al than hashcash. Sorry, this was long ago for me.

http://www.cri.ensmp.fr/classement/doc/A-370-v2.pdf



Can someone please explain me as if I was 5 years old how the proof of work is performed ?


Like Bitcoin, you can ask clients to find a hash of a string (in the format of, say, 'server_key' + 'random_number' + 'client_adjusted_nonce') with the value of the last four bytes being higher than say 50,000. You can increase the value until 0xFFFFFFFE, where only 1 in 4294967296 hashes would 'pass' the test.

Your client will basically be working on passing the test for a predictable amount of time, dependent on the difficulty threshold.

The neat thing about this method is that although it takes a while to pass the test, it's very fast and easy to check if a test is passed. You'll simply ask the client for the client_adjusted_nonce which gets a value higher than 50,000 (or whatever difficulty threshold you choose). As the author has stated, you can adjust the threshold depending on the strength of the DDOS.


Sorry, my age may be lower than five. I don't understand. You explain that a hash is computed over a string that is the concatenation of a server key, a random number and a client adjusted nonce. you then lost me with the four bytes. I thought that we were talking about a string of chars, not an array of bytes. The nonce is an integer value ? The client then increases the nonce value I guess. But how does the client know it has finished the work ? In the article it is said that the hash value should start with a number of 0 bits. But this occurence is random by definition of the hash. It could never occur with the nonce tested. So sorry, I still don't understand it.


The basic idea is: The client has to create a hash that satisfies a certain condition: Like the first n-digits must be 0, or maybe "the last bytes (when interpreted as a single 32 number) must be larger than x".

So the client does this:

    from itertools import count
    import hashlib

    for i in count():
        h = hashlib.md5("some-nonce:%d" % i).hexdigest()
        if h.startswith("000000"):
            print i
            break
The value "some-nonce" is provided by the server. It also provides the difficulty by saying: I need you to find i which generates a hash that starts with 6 zeros.

So the client calculates hashes for a while, and then submits the value 2652076 to the server. The server can the trivially check if the client did its work correctly:

    $ echo -en "some-nonce:2652076" | md5sum
    00000066adb2fb37a8460da553721c39 # ok, the first 6 digits are 0
The server can simply increase the difficulty by requiring the client to return more leading zeros. Or, if it should be more finegrained, a value below a certain threshold.


Thank you very much. That was very clear. In the mean time I searched around on google.

The property of this proof of work is that the duration of the work is random.

How is it possible to know the average work time duration, without doing the measurement ? Is it possible to provide a work time limit so that if a client has really bad luck and can't find the solution in that period can he can issue another question ?

From the ur perspective, this varying work time duration may be an unpleasant experience.


Maybe I'm taking your example a bit far, but it seems like you'd have to choose the hashing function carefully, correct?

To use your example, it must be 100% certain that a hash with 6 leading zeros is possible to generate with md5.

Also, I'm assuming you don't want clients spending too long on the problem, so it seems like you'd want to have a prediction of roughly how long it would take to compute the answer. Otherwise one client may get lucky after 10 iterations whist another may take 10 million. Are hashing functions predictable in that manner?


Reliable hash functions must be able to map to all combinations of 256, 512, or however many bits they are using.


Your explanation is fine but this summary will confuse people:

> The client has to create a hash that satisfies a certain condition

What you really want is to create a string whose hash matches the PoW challenge hash.


> The nonce is an integer value?

An array of bytes is an integer value - just an arbitrarily large one. You could also just tack the bytes of a 64-bit uint onto the end of the nonce instead of incrementing the nonce (I'm not sure if that is secure, it's just an explanation).

A real example of this is RSA. The length of the key is in bits (and therefore bytes) but is still a representation of a really big integer.

> how does the client know it has finished the work ?

Let's remove hashing from the equation. I, as the server, say to you I've got the following equation:

    Y = (5 + X) * 10
Please solve it so that the last 3 digits of Y are greater than 100 and tell me what you used for X. The simplicity of this equation means that you can easily solve for Y and respond with `12335`. With that response I can just plug X into the equation and see that you've done the work. Now, imagine that there is no such thing as division. Being unable to solve the equation, you'd be forced to brute force each value of X - but proving that you've found X only requires that you run the equation once because we can still multiply:

    Y = (5 + 12335) * 10
With POW, you say that you are looking for a specific pattern of bits or bytes. The pattern can be quite arbitrary: "I want the last 8 bytes to be a 64-bit integer larger than 12345" or even just "the last 64 bits must all be 0." The client will have to try multiple values of X to see which satisfies that constraint. Once the client knows X you no longer need to go through the effort of finding out the value for yourself, you just need to plug X into the equation and see that they found a valid value for X:

    POW = HASH(NONCE & X)
It's like salting a password, but requiring that a portion of the final result has a specific pattern. This forces the client to do a lot of work in a way that is cheap to verify from a computational standpoint.


I remember first seeing such a proof-of-work in the network protocol layer of the game Tribes 2. They opened up the source code for that, named OpenTNL (seems abandoned now) where you could see how it's done. Here's the part that verifies a puzzle:

https://github.com/kocubinski/opentnl/blob/master/tnl/client...


If you encode each packet in a QUIC stream with it's own stream ID, and close QUIC streams after a timeout, you can use QUIC for unreliable unordered transports as well.


Good idea, THANKS A LOT! It has crossed my mind too, but I've discarded without giving it enough thought. The reason why it MIGHT work, is because of the implicit stream creation in QUIC (no handshake, nothing, just single UDP packet). There will still be some overhead, but probably it won't be "too bad".


> DTLS and QUIC

And, of course, the mother of all datagram encryption protocols - the IPsec suite (ESP, IKE 2). It works with IP packets, but it can be very easily repurposed for UDP.


You're right, I forgot about it. But is there a ready-to-use library doing it?


On the end encryption is useless but it blocks 90% of the curious people that don't have the skills to go further ( ie: reverse engineer the game )


Yes, use DTLS, the protocol that is not a bastard stepchild of TLS and NEVER gets blasted open by protocol-level security holes, and certainly never by implementation flaws.

Yes, use that.


The more I look at this website, the more I think they should go write a children's book on coding.

That being said, pretty good read.


Doing a lot of crypto at work so this article was super interesting. My god though, those illustrations are gorgeous.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: