Dust: A Censorship-Resistant Internet Transport Protocol Brandon Wiley The University of Texas at Austin School of Information Introduction Dust is an Internet protocol designed to resist a number of attacks currently in active use to censor Internet communication. While adherence to the theoretical maxims of cryptographic security are observed where possible, the focus of Dust is on real solutions to real attacks. Use Case The use case for Dust assumes that there is an area of the Internet surrounded by a packet filtering firewall. All normal Internet traffic passing between the inside and outside of the zone is filtered using standard packet filtering techniques, disallowing the transmission of certain banned content. It is also assumed that there is a secure, but perhaps expensive and high latency, channel into the filtered zone. This channel is considered to be out of band, i.e. it is not over the Internet (phone, letter, messenger, etc.). Given this scenario, Dust creates an inexpensive, unfiltered channel from inside the firewall to outside. Packet Filtering Techniques The packet filtering techniques currently in use attempt to keep as little state as possible in order to be scalable. Filtering can happen either at the individual packet level (drop banned packets) or at the stream level (block or throttle streams containing banned packets). For stream level filtering it is common to sample only the initial packets of the stream, or to do random statistical sampling of packets. Filters do not keep persistent state about streams other than whether they have been marked as banned. Technique for defeating filtering can therefore concentrate on not sending packets which will be marked as banned. There are two general classificiations of techniques for determining of a whether a packet is banned. Shallow packet inspection (SPI) uses just the headers of the packet. This is less expensive because the headers need to be examined anyway in order to route the packet. Deep packet inspection (DPI) simply refers to examining the packet contents as well as the headers. DPI used to be considered too expensive to be practical, but is now in widespread use by some filters. SPI techniques in active use for marking packets are as follows: source IP and port, destination IP and port, and packet length. DPI techniques in active use for marking packets are as follows: examing packet for connection headers and handshakes of known protocols, examining packet contents for banned static strings, and examining packet contents for banned string patterns. How Dust Circumvents Filters Dust is an Internet protocol used to send packets which defeat the various filtering techniques currently in use. IP and port blacklists - This is outside of the scope of the protocol. Communication over the Internet requires a destination IP and port which has not been blacklisted by the filter. For the source IP and port, packet spoofing could be used. For more information on how to circumvent IP blacklists, see the Arcadia paper. Packet length - Dust packets have randomized lengths. Connection headers and handshakes - Unlike SSH and SSL (both now filtered but some filters), Dust contains no plaintext handshake. Static strings and string patterns - Dust packets are encrypted, so the contents cannot be examined. In a sense, each Dust packet is unique in that the contents are entirely encrypted using a random initialization vector and the length is randomized as well. Dust packets can therefore not be classified by techniques that examines static characteristics of a packet. Mechanics Overview The Dust protocol is a unidirectional UDP-based protocol. A Dust conversation consists of a single intro packet followed by any number of data packets. The minimal Dust conversation therefore consists of two packets: one intro packet, and one data packet. While the use of a unidirectional, unreliable protocol is unconventional, it was chosen because Dust has one purpose: to get data past filters. Special care must be taken when adding additional features to the protocol as every packet which is added provides another opportunity for information to be leaked which could be used in fingerprinting the protocol. Future work will consider ways to add additional desirable features to the protocol without compromising its core purpose. The Key Exchange Protocols such as SSL and SSH initiate a public key exchange using a plaintext handshake. They are therefore susceptible to protocol fingerprinting and filtering. Dust also requires a public key exchange, but not utilize a plaintext handshake. Instead, Dust uses a novel half in-band, half out-of-band key exchange protocol.. In order to accept a connection from an unknown host, a Dust server must first complete a key exchange with the client. The Dust server first creates an id-secret pair, which is stored securely on the server. The server then creates an invite, which contains the server's IP, port, public key, and the id-secret pair. The invite can be optionally encrypted using a password. The server operator then distributes the invite and optional password to the client out-of-band. This constitutes the first half of the public key exchange. The client uses the IP and port information from the invite to send an intro packet to the server. The intro packet is encrypted with the secret from the invite and contains the public key of the client. The id portion of the id-secret pair is prepended unencrypted to the encrypted packet. The server retrieves the id and uses it to lookup the associated secret. It uses the secret to decrypt the packet and retrieves the public key of the client. It adds the public key to its list of known hosts, associated with the IP and port from which the intro packet was sent. This completes the second phase of the public key exchange. Sending Data Once the key exchange has taken place, data packets can be sent. To send a data packet, the client generates an encryption key using its public key and the public key of the server. The client also generates a random initialization vector. This encryption key and the initialization vector are used to encrypt the data packet. The intialization vector is prepended, unencrypted, to the encrypted data packet. A random number of random bytes are appended to the encrypted data packet in order to give it a random length. When the server receives a packet, it looks at the source IP address and port to determine the identity of the client. It then looks to see if it has a public key stored for that IP address and port. If so, it assumes the packet to be a data packet and decodes it. Otherwise, it assumes it to be an intro packet and decodes it as specified in the Key Exchange section. To decode a data packet, an encryptionl key is generated using the server's public key and the client's public key. The initialization vector is taken from the front of the packet. The remaining packet is decrypted. The extra random bytes that were added by the client are discarded. The result is the original unecrypted data. Randomness A key consideration in the Dust protocol is that the only information in a Dust packet should always be effectively random to an observer. There are only two types of information in a Dust packet: encrypted (should appear random) and single-use random bytes (actually is random). The apparent randomness is essential to the protocol's ability to avoid detection. Therefore, care should be taken to use a good random number generator and to never reuse random bytes. For instance, the id from an invite id-secret pair, which is sent in an intro packet is intended for a single use and should never be reused. Similarly, the initialization vector used in data packets should always be randomly generated and never reused between data packets. Packet Format There are three types of Dust packets: invite, intro, and data packets. All three types of packets build upon the basic Dust packet format. Dust Packet Any dust packet has the following format: Structure dustPacket = mac(key, ciphertext) ciphertext padding ciphertext: iv encrypt(key, iv, payload) payload = timestamp length(data) length(padding) data Layout 32 bytes - mac(key, ciphertext) 16 bytes - iv 4 bytes - timestamp 2 bytes - length(data) 1 byte - length(padding) variable - data 0-31 bytes - padding (random) mac(key, ciphertext) is the message authentication code of the ciphertext, used to ensure that the ciphertext is not tampered with or corrupted iv is the initialization vector, random bytes used in the encryption to ensure that encrypting the same payload twice will yield a different packet encrypt(key, iv, payload) encrypts the payload with the key and iv padding is random bytes used to randomize the length of the packet timestamp is a timestamp used to ensure that old packets are not replayed later by an attacker length(data) is the length of the data, used to distinguish the actual data from the padding length(padding) is the length of the padding, used to determine the total length of the packet when multiple packets are in the same byte stream data is the actual data of the packet Packet Chaining Multiple Dust packets can be chained together inside of a single UDP packet. After decoding a Dust packet, the length of the headers, data, and padding should be summed to determine the total length of the Dust packet. If this is less than the length of the UDP packet then this is a packet chain and the additional packet should be parsed as well. As many packets can be chained as can fit inside a UDP packet. The practical reason for allowing packet chaining is so that, if the message is small enough, a single UDP packet can be set carrying both an intro packet and a data packet. The smallest Dust conversation is therefore one packet. The other useful effect of packet chaining is that it allows for Dust packets to be transported over other channels, such as TCP. Invite Packet Invite packets are not sent directly over the Internet between the client and server like Intro and Data packets. They are sent out-of-band and so are in an ASCII-compatible format for ease of use in email, IM, or for printing. However, they do reuse the Data packet format as a way to securely encode them for transportation. Invite packets use a hashed password for encryption. After the packet is encrypted, it's converted to an ASCII-friendly hex encoding for transportation. Multiple invites can be sent in the same file by separating them with newlines. A multiple invite package containing three invites would look like this: f94fbacac6b9573cd8a905519b55f3044a7b96bc81c3deb5ce049807e3270108d0a08f437362ba87993e46e00587a9dffb90757bc563fe5eda75496ca87038fdf3f76c8b357997352f1a782c5ace7824ec34bb60368c7befd6da7a7888a382f2956ef67c7f063d35105dd3abd56297a59f0411c5b9fe2c3db486030c0296ad878b65c789233a584e01fa47ea74055a3adc014b7c96b985200a2750 f371d2f68101e126abedcf2d1429a2b7c601c2ac05fb1534d1c62bca825631e86145f0e7256886fd62d90a967730aa497ad076ee3b1391e5ebce33ef4e710b07b8fbe8f3df3052584ca24c445e9b341525802940f125783e04ecd6a1d0a19f22a2805e5bdeb48247f6307243d9e6f15d2b0512b92fa324a4084d643a04fbdfe99056ed7a55fee8e4234d5b0144b48714655d fb0454da089c92a7d83778ccf666d9ae143641977647a241fbadadc7b8c234117acce0e6b1ff86d8ce245f9629011707b972c606250cf7c98597b970bbb5266ea2cd8c3c63e344bef7e7d631e86f9959503d3e8f710b5d85a38d1b0945b2e241414ea95dc033cbc1d27455df8fb4c4a129a4ed322f638d550c4caf113f48d3142290f89738e139c7c0ab6dcdad254dad7023c3a38e57371b1e2d271434c87fa187a5a901a6692d5f Note that you cannot decode the invites without the password. An invite packet has the following format: Structure invitePacket = salt dataPacket(pbkdf(password, salt, iterations), inviteMessage) inviteMessage = pubkey flags ip port id secret Layout 32 bytes - salt ... # data packet headers precede data 32 bytes - pubkey 1 byte - flags 16 bytes - ip 2 bytes - port 16 bytes - id 32 bytes - secret ... # data packet padding follows data salt is random bytes used in the PBKDF function pbkdf(password, salt) is a Password-Based Key Derivation Function which produces a key from a password and random salt pubkey is the public key of the server flags is 8 bits of flags for different protocol options. The flag bits are defined as followed: * 0 - IP version: 0 is IPv4, 1 is IPv6 * 1 - transport protocol: 0 is UDP, 1 is TCP * 2-7 - reserved for future flags ip is the IP of the server, either IPv4 or IPv6 depending on the IP version flag. If IPv4, only the first 4 bytes are used and the renaming 12 are zeros port is the port that the server is listening on id is an id that can be sent in an intro packet secret is the corresponding encryption key to use to encrypt an intro packet with a given id Intro Packets An intro packet has the following format: Structure introPacket = identifier dustPacket(secret, sender-pubkey) Layout 16 bytes - identifier variable - dustPacket (sender-pubkey is 32 bytes) identifier is the first part of the identifier-secret pair from an invite dustPacket(secret, sender-pubkey) is a Dust packet, using secret as the encryption key and sender-pubkey as the data secret is the second part of the identifier-secret pair from an invite sender-pubkey is the public key of the sender Data Packet A data packet has the following format: Structure dataPacket = dustPacket(sessionKey, data) Layout variable - dustPacket (sessionKey is 32 bytes) dustPacket(sessionKey, data) is a Dust packet, using sessionKey as the encryption key sessionKey is the encryption key used for the Dust packet encryption. It's defined hash(session(sender-privkey, receiver-pubkey)) Extensions The basic Dust protocol is designed to counteract current Internet filters techniques. With extensions, the Dust protocol can be expanded to deal with a larger range of filtering situations. Dust Lite The Dust protocol creates a secure channel. This is a desirable quality because it means that Dust packets cannot be decoded by listeners and therefore cannot be flagged by filters. However, there are some situations where a secure channel is not necessary. In a situation where the goal is to bypass filters in the short-term only, the lightweight version of the Dust Lite can be used. Dust Lite uses only Data packets, skipping the Invite and Intro steps. Dust Lite packets differ from normal Data packets in how the sessionKey is generated. A Dust Lite sessionKey is defined as xor(hash(sender-address), hash(receiver-address)) where sender-address and receiver-address are formatted in the following ways: * for an IPv4 address: x.x.x.x:port * for an IPv6 address: [x:x:x:x:x]:port Since Dust List packets are encrypted and padded like any Dust packet, they will bypass any filters that are not specifically looking for Dust Lite packets. Additionally, the filter will need to calculate different sessionKey values for every pair of communicating addresses. This will increase the cost of filtering Dust Lite packets as the number of users increases. Unfortunately, once the sessionKey has been computed traffic can be blocked as well as forged. At that point, it's time to switch to the full Dust protocol. Algorithms The mechanics and format of Dust are presented in a general manner such that any suitable public key cryptography system and cipher could be used. However, actual implementation requires specific choices and choices for implementation of Dust have been made which are small, fast, and have libraries for which it is easy to create bindings. * Public key system: Curve25519 * Session key exchange: Curve25519 implementation of ECDH * MAC: Skein-MAC(key, data) * Internal state: 256 bits * Output size: 256 bits * MAC Input: key * Message Input: data * Personalization Input: "1978-10-26 dust@blanu.net Dust/MAC" * Hash: Skein(data) * Internal state: 256 bits * Output size: 256 bits * Message Input: data * Personalization Input: "1978-10-26 dust@blanu.net Dust/hash" * PBKDF: Skein-PBKDF(password, salt) * Internal state: 256 bits * Output size: 256 bits * Iterations: 13000 * Message Input: (salt+password)*iterations * Personalization Input: "1978-10-26 dust@blanu.net Dust/PBKDF" * PRNG: Skein-PRNG.init(seed); Skein-PRNG.getBytes(n) * Internal state: 256 bits * Output size: SEED_SIZE+n * init: set S to seed * getBytes: Skein(S) -» R; S=R[0..SEED_SIZE]; bytes=R[SEED_SIZE+1...]; return bytes; * SEED_SIZE: 256 bits * Personalization Input: "1978-10-26 dust@blanu.net Dust/PRNG" * Cipher: Skein-Cipher(key, iv, data) * Internal state: 256 bits * Output size: len(data) * Cipher mode: OFB * IV size: 128 bits * Key size: 256 bits * Block size: 256 bits * Personalization Input: "1978-10-26 dust@blanu.net Dust/cipher" Open Questions * Justification of cipher algorithm * Justification of hash algorithm * Failure modes and recovery * When to discard key-IP bindings * Appropriate windows for timestamps Recent Changes * Switched invite format to use data packets as a wrapper * Password-based encryption of invites made mandatory * Added flags byte to invite packet with IP version and protocol flags defined * Added newline-delimited invite package format * Switched hash from skein-256-256 to skein-512-256 * Use of Skein-PRNG as PRNG algorithm * Switched from using skein512-256 hash for passwords to using Skein-PBKDF with random salt and 13000 iterations * Added explicit paddingLength field * Specified chainable packet semantics * Switched cipher from Threefish to Skein-Cipher * Removed filler field * Added personalization * Changed cipher mode to OFB * Changed MAC from MAC of plaintext to MAC of ciphertext and IV, moved MAC before IV * * TCP compatibility * Switched hash from skein-512-256 to skein-256-256 for Java compatiblity * Java port Future Work * Bidirectional extensions: acks and key exchange for forward secrecy * Steganographic extension * C port * Key rotation * Separate cipher and MAC keys * Certificates Acknowledgements Special thanks goes to Drake Wilson for all of his excellent feedback regarding the Dust mechanics and algorithms, including the suggestion of both Skein and Curve25519, as well as the initial idea for Dust Lite. Thanks to Greg Hazel for his contributions on the original version of the protocol and his continuing suggestions.