{% extends "global/layout.html" %} {% block title %}SSU Protocol Specification{% endblock %} {% block lastupdated %}October 2014{% endblock %} {% block accuratefor %}0.9.16{% endblock %} {% block content %}

See the SSU page for an overview of the SSU transport.

DH Key Exchange

The initial 2048-bit DH key exchange is described on the SSU page. This exchange uses the same shared prime as that used for I2P's ElGamal encryption.

All UDP datagrams begin with a 16 byte MAC (Message Authentication Code) and a 16 byte IV (Initialization Vector) followed by a variable-size payload encrypted with the appropriate key. The MAC used is HMAC-MD5, truncated to 16 bytes, while the key is a full 32 byte AES256 key. The specific construct of the MAC is the first 16 bytes from:

  HMAC-MD5(encryptedPayload + IV + (payloadLength ^ protocolVersion), macKey)
where '+' means append and '^' means exclusive-or.

The IV is generated randomly for each packet. The encryptedPayload is the encrypted version of the message starting with the flag byte (encrypt-then-MAC). The payloadLength used in the MAC is a 2 byte unsigned integer. Note that protocolVersion is 0, so the exclusive-or is a no-op. The macKey is either the introduction key or is constructed from the exchanged DH key (see details below), as specified for each message below. WARNING - the HMAC-MD5-128 used here is non-standard, see the cryptography page for details.

The payload itself (that is, the message starting with the flag byte) is AES256/CBC encrypted with the IV and the sessionKey, with replay prevention addressed within its body, explained below.

The protocolVersion is a 2 byte unsigned integer and is currently set to 0. Peers using a different protocol version will not be able to communicate with this peer, though earlier versions not using this flag are.

HMAC Specification

Session Key Details

The 32-byte session key is created as follows:
  1. Take the exchanged DH key, represented as a positive minimal-length BigInteger byte array (two's complement big-endian)
  2. If the most significant bit is 1 (i.e. array[0] & 0x80 != 0), prepend a 0x00 byte, as in Java's BigInteger.toByteArray() representation
  3. If the byte array is greater than or equal to 32 bytes, use the first (most significant) 32 bytes
  4. If the byte array is less than 32 bytes, append 0x00 bytes to extend to 32 bytes. Very unlikely - See note below.

MAC Key Details

The 32-byte MAC key is created as follows:
  1. Take the exchanged DH key byte array, prepended with a 0x00 byte if necessary, from step 2 in the Session Key Details above.
  2. If that byte array is greater than or equal to 64 bytes, the MAC key is bytes 33-64 from that byte array.
  3. If that byte array is less than 64 bytes, the MAC key is the SHA-256 Hash of that byte array. As of release 0.9.8. See note below.
Important note: Code before release 0.9.8 was broken and did not correctly handle DH key byte arrays between 32 and 63 bytes (steps 3 and 4 above) and the connection will fail. As these cases didn't ever work, they were redefined as described above for release 0.9.8, and the 0-32 byte case was redefined as well. Since the nominal exchanged DH key is 256 bytes, the chances of the mininimal representation being less than 64 bytes is vanishingly small.

Header Format

Within the AES encrypted payload, there is a minimal common structure to the various messages - a one byte flag and a four byte sending timestamp (seconds since the unix epoch). The flag byte contains the following bitfields:

{% highlight %} Bit order: 76543210 (bit 7 is MSB) bits 7-4: payload type bit 3: rekey, always 0, unimplemented bit 2: extended options included, always 0, unimplemented bits 1-0: reserved, set to 0 for compatibility with future uses {% endhighlight %}

The header format is:

{% highlight lang='dataspec' %} Header: 37+ bytes Encryption starts with the flag byte. +----+----+----+----+----+----+----+----+ | MAC | + + | | +----+----+----+----+----+----+----+----+ | IV | + + | | +----+----+----+----+----+----+----+----+ |flag| time | | +----+----+----+----+----+ + | keying material (optional) | + + | | ~ ~ | | + +----+----+----+ | |#opt| | +----+----+----+----+----+----+ + | #opt extended option bytes (optional) | ~ ~ ~ ~ +----+----+----+----+----+----+----+----+ {% endhighlight %}

Note that rekeying and extended options are unimplemented, so the current header size is exactly 37 bytes.

Rekeying

If the rekey flag is set, 64 bytes of keying material follow the timestamp.

When rekeying, the first 32 bytes of the keying material is fed into a SHA256 to produce the new MAC key, and the next 32 bytes are fed into a SHA256 to produce the new session key, though the keys are not immediately used. The other side should also reply with the rekey flag set and that same keying material. Once both sides have sent and received those values, the new keys should be used and the previous keys discarded. It may be useful to keep the old keys around briefly, to address packet loss and reordering.

NOTE: Rekeying is currently unimplemented.

Extended Options

If the extended options flag is set, a one byte option size value is appended, followed by that many extended option bytes.

NOTE: Extended options is currently unimplemented.

Padding

All messages contain 0 or more bytes of padding. Each message must be padded to a 16 byte boundary, as required by the AES256 encryption layer. Through release 0.9.7, messages were only padded to the next 16 byte boundary, and messages not a multiple of 16 bytes could possibly be invalid. As of release 0.9.7, messages may be padded to any length as long as the current MTU is honored. Any extra 1-15 padding bytes beyond the last block of 16 bytes cannot be encrypted or decrypted and will be ignored. However, the full length and all padding is included in the MAC calculation. As of release 0.9.8, transmitted messages are not necessarily a multiple of 16 bytes. The SessionConfirmed message is an exception, see below.

Keys

Signatures in the SessionCreated and SessionConfirmed messages are generated using the Signing Public Key from the Router Identity which is distributed out-of-band by publishing in the network database, and the associated Signing Private Key. Through release 0.9.15, the signature algorithm was always DSA, with a 40 byte signature. As of release 0.9.16, the signature algorithm may be specified by a a Key Certificate in Bob's Router Identity.

Both introduction keys and session keys are 32 bytes, and are defined by the Common structures specification. The key used for the MAC and encryption is specified for each message below.

Introduction keys are delivered through an external channel (the network database, where they are identical to the router Hash for now).

Notes

IPv6 Notes

The protocol specification allows both 4-byte IPv4 and 16-byte IPv6 addresses. SSU-over-IPv6 is supported as of version 0.9.8. See the documentation of individual messages below for details on IPv6 support.

Timestamps

While most of I2P uses 8-byte Date timestamps with millisecond resolution, SSU uses a 4-byte timestamp with one-second resolution.

Messages

There are 10 messages (payload types) defined:

TypeMessageNotes
0SessionRequest
1SessionCreated
2SessionConfirmed
3RelayRequest
4RelayResponse
5RelayIntro
6Data
7PeerTest
8SessionDestroyedImplemented as of 0.8.9
n/aHolePunch

SessionRequest (type 0)

This is the first message sent to establish a session.

Peer: Alice to Bob
Data:
  • 256 byte X, to begin the DH agreement
  • 1 byte IP address size
  • that many byte representation of Bob's IP address
  • N bytes, currently uninterpreted
Crypto Key used: Bob's introKey, as retrieved from the network database
MAC Key used: Bob's introKey, as retrieved from the network database

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | X, as calculated from DH | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ |size| that many byte IP address (4-16) | +----+----+----+----+----+----+----+----+ | arbitrary amount of uninterpreted data| ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 304 (IPv4) or 320 (IPv6) bytes (before non-mod-16 padding)

Notes

SessionCreated (type 1)

This is the response to a Session Request.

Peer: Bob to Alice
Data:
  • 256 byte Y, to complete the DH agreement
  • 1 byte IP address size
  • that many byte representation of Alice's IP address
  • 2 byte Alice's port number
  • 4 byte relay (introduction) tag which Alice can publish (else 0x00000000)
  • 4 byte timestamp (seconds from the epoch) for use in the DSA signature
  • Bob's Signature of the critical exchanged data (X + Y + Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's new relay tag + Bob's signed on time), encrypted with another layer of encryption using the negotiated sessionKey. The IV is reused here. See notes for length information.
  • 0-15 bytes of padding of the signature, using random data, to a multiple of 16 bytes, so that the signature + padding may be encrypted with an additional layer of encryption using the negotiated session key as part of the DSA block.
  • N bytes, currently uninterpreted
Crypto Key used: Bob's introKey, with an additional layer of encryption over the 40 byte signature and the following 8 bytes padding.
MAC Key used: Bob's introKey

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | Y, as calculated from DH | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ |size| that many byte IP address (4-16) | +----+----+----+----+----+----+----+----+ | Port (A)| public relay tag | signed +----+----+----+----+----+----+----+----+ on time | | +----+----+ + | | + + | signature | + + | | + + | | + +----+----+----+----+----+----+ | | (0-15 bytes of padding) +----+----+----+----+----+----+----+----+ | | +----+----+ + | arbitrary amount | ~ of uninterpreted data ~ ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 368 bytes (IPv4 or IPv6) (before non-mod-16 padding)

Notes

SessionConfirmed (type 2)

This is the response to a Session Created message and the last step in establishing a session. There may be multiple Session Confirmed messages required if the Router Identity must be fragmented.

Peer: Alice to Bob
Data:
  • 1 byte identity fragment info:
    Bit order: 76543210 (bit 7 is MSB)
    bits 7-4: current identity fragment # 0-14
    bits 3-0: total identity fragments (F) 1-15
  • 2 byte size of the current identity fragment
  • that many byte fragment of Alice's Router Identity
  • After the last identity fragment only:
    • 4 byte signed-on time
  • N bytes padding, currently uninterpreted
  • After the last identity fragment only:
    • The remaining bytes contain Alice's Signature of the critical exchanged data (X + Y + Alice's IP + Alice's port + Bob's IP + Bob's port + Alice's new relay tag + Alice's signed on time) See notes for length information.
Crypto Key used: Alice/Bob sessionKey, as generated from the DH exchange
MAC Key used: Alice/Bob MAC Key, as generated from the DH exchange

Fragment 0 through F-2 (only if F > 1; currently unused, see notes below) :

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ |info| cursize | | +----+----+----+ + | fragment of Alice's full | ~ Router Identity ~ ~ . . . ~ | | +----+----+----+----+----+----+----+----+ | arbitrary amount of uninterpreted data| ~ . . . ~ {% endhighlight %}

Fragment F-1 (last or only fragment):

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ |info| cursize | | +----+----+----+ + | last fragment of Alice's full | ~ Router Identity ~ ~ . . . ~ | | +----+----+----+----+----+----+----+----+ | signed on time | | +----+----+----+----+ + | arbitrary amount of uninterpreted | ~ data, until the signature at ~ ~ end of the current packet ~ | Packet length must be mult. of 16 | +----+----+----+----+----+----+----+----+ + + | | + + | signature | + + | | + + | | +----+----+----+----+----+----+----+----+ {% endhighlight %}

Typical size including header, in current implementation: 480 bytes (before non-mod-16 padding)

Notes

SessionDestroyed (type 8)

The Session Destroyed message was implemented (reception only) in release 0.8.1, and is sent as of release 0.8.9.

Peer: Alice to Bob or Bob to Alice
Data: none
Crypto Key used: Alice/Bob sessionKey
MAC Key used: Alice/Bob MAC Key

This message does not contain any data. Typical size including header, in current implementation: 48 bytes (before non-mod-16 padding)

Notes

RelayRequest (type 3)

This is the first message sent from Alice to Bob to request an introduction to Charlie.

Peer: Alice to Bob
Data:
  • 4 byte relay (introduction) tag, nonzero
  • 1 byte IP address size
  • that many byte representation of Alice's IP address
  • 2 byte port number (of Alice)
  • 1 byte challenge size
  • that many bytes to be relayed to Charlie in the intro
  • Alice's 32-byte introduction key (so Bob can reply with Charlie's info)
  • 4 byte nonce of Alice's relay request
  • N bytes, currently uninterpreted
Crypto Key used: Bob's introKey, as retrieved from the network database (or Alice/Bob sessionKey, if established)
MAC Key used: Bob's introKey, as retrieved from the network database (or Alice/Bob MAC Key, if established)

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | relay tag |size| Alice IP addr +----+----+----+----+----+----+----+----+ | Port (A)|size| challenge bytes | +----+----+----+----+ + | to be delivered to Charlie | +----+----+----+----+----+----+----+----+ | Alice's intro key | + + | | + + | | + + | | +----+----+----+----+----+----+----+----+ | nonce | | +----+----+----+----+ + | arbitrary amount of uninterpreted data| ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 96 bytes (no Alice IP included) or 112 bytes (4-byte Alice IP included) (before non-mod-16 padding)

Notes

RelayResponse (type 4)

This is the response to a Relay Request and is sent from Bob to Alice.

Peer: Bob to Alice
Data:
  • 1 byte IP address size
  • that many byte representation of Charlie's IP address
  • 2 byte Charlie's port number
  • 1 byte IP address size
  • that many byte representation of Alice's IP address
  • 2 byte Alice's port number
  • 4 byte nonce sent by Alice
  • N bytes, currently uninterpreted
Crypto Key used: Alice's introKey, as received in the Relay Request (or Alice/Bob sessionKey, if established)
MAC Key used: Alice's introKey, as received in the Relay Request (or Alice/Bob MAC Key, if established)

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ |size| Charlie IP | Port (C)|size| +----+----+----+----+----+----+----+----+ | Alice IP | Port (A)| nonce +----+----+----+----+----+----+----+----+ | arbitrary amount of | +----+----+ + | uninterpreted data | ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 64 (Alice IPv4) or 80 (Alice IPv6) bytes (before non-mod-16 padding)

Notes

RelayIntro (type 5)

This is the introduction for Alice, which is sent from Bob to Charlie.

Peer: Bob to Charlie
Data:
  • 1 byte IP address size
  • that many byte representation of Alice's IP address
  • 2 byte port number (of Alice)
  • 1 byte challenge size
  • that many bytes relayed from Alice
  • N bytes, currently uninterpreted
Crypto Key used: Bob/Charlie sessionKey
MAC Key used: Bob/Charlie MAC Key

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ |size| Alice IP | Port (A)|size| +----+----+----+----+----+----+----+----+ | that many bytes of challenge | + + | data relayed from Alice | +----+----+----+----+----+----+----+----+ | arbitrary amount of uninterpreted data| ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 48 bytes (before non-mod-16 padding)

Notes

Data (type 6)

This message is used for data transport and acknowledgment.

Peer: Any
Data:
  • 1 byte flags:
       Bit order: 76543210 (bit 7 is MSB)
       bit 7: explicit ACKs included
       bit 6: ACK bitfields included
       bit 5: reserved
       bit 4: explicit congestion notification (ECN)
       bit 3: request previous ACKs
       bit 2: want reply
       bit 1: extended data included (unused, never set)
       bit 0: reserved
  • if explicit ACKs are included:
    • a 1 byte number of ACKs
    • that many 4 byte MessageIds being fully ACKed
  • if ACK bitfields are included:
    • a 1 byte number of ACK bitfields
    • that many 4 byte MessageIds + a 1 or more byte ACK bitfield. The bitfield uses the 7 low bits of each byte, with the high bit specifying whether an additional bitfield byte follows it (1 = true, 0 = the current bitfield byte is the last). These sequence of 7 bit arrays represent whether a fragment has been received - if a bit is 1, the fragment has been received. To clarify, assuming fragments 0, 2, 5, and 9 have been received, the bitfield bytes would be as follows:
      byte 0:
         Bit order: 76543210 (bit 7 is MSB)
         bit 7: 1 (further bitfield bytes follow)
         bit 6: 0 (fragment 6 not received)
         bit 5: 1 (fragment 5 received)
         bit 4: 0 (fragment 4 not received)
         bit 3: 0 (fragment 3 not received)
         bit 2: 1 (fragment 2 received)
         bit 1: 0 (fragment 1 not received)
         bit 0: 1 (fragment 0 received)
      byte 1:
         Bit order: 76543210 (bit 7 is MSB)
         bit 7: 0 (no further bitfield bytes)
         bit 6: 0 (fragment 13 not received)
         bit 5: 0 (fragment 12 not received)
         bit 4: 0 (fragment 11 not received)
         bit 3: 0 (fragment 10 not received)
         bit 2: 1 (fragment 9 received)
         bit 1: 0 (fragment 8 not received)
         bit 0: 0 (fragment 7 not received)
      
  • If extended data included:
    • 1 byte data size
    • that many bytes of extended data (currently uninterpreted)
  • 1 byte number of fragments (can be zero)
  • If nonzero, that many message fragments. Each fragment contains:
    • 4 byte messageId
    • 3 byte fragment info:
         Bit order: 76543210 (bit 7 is MSB)
         bits 23-17: fragment # 0 - 127
         bit 16: isLast (1 = true)
         bits 15-14: unused, set to 0 for compatibility with future uses
         bits 13-0: fragment size 0 - 16383
    • that many bytes
  • N bytes padding, uninterpreted
Crypto Key used: Alice/Bob sessionKey
MAC Key used: Alice/Bob MAC Key

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ |flag| (additional headers, determined | +----+ + ~ by the flags, such as ACKs or ~ | bitfields | +----+----+----+----+----+----+----+----+ |#frg| messageId | frag info | +----+----+----+----+----+----+----+----+ | that many bytes of fragment data | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ | messageId | frag info | | +----+----+----+----+----+----+----+ + | that many bytes of fragment data | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ | messageId | frag info | | +----+----+----+----+----+----+----+ + | that many bytes of fragment data | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ | arbitrary amount of uninterpreted data| ~ . . . ~ {% endhighlight %}

Notes

PeerTest (type 7)

See the SSU overview page for details.

Peer: Any
Data:
  • 4 byte nonce
  • 1 byte IP address size (may be zero)
  • that many byte representation of Alice's IP address, if size > 0
  • 2 byte Alice's port number
  • Alice's or Charlie's 32-byte introduction key
  • N bytes, currently uninterpreted
Crypto Key used: Listed in order of occurrence:
  1. When sent from Alice to Bob: Alice/Bob sessionKey (The protocol also permits Bob's introKey if Alice and Bob do not have an established session, but in the current implementation Alice always selects a Bob that is established. As of release 0.9.15, Bob will reject PeerTests from peers without an established session.)
  2. When sent from Bob to Charlie: Bob/Charlie sessionKey
  3. When sent from Charlie to Bob: Bob/Charlie sessionKey
  4. When sent from Bob to Alice: Alice's introKey, as received in the Peer Test message from Alice
  5. When sent from Charlie to Alice: Alice's introKey, as received in the Peer Test message from Bob
  6. When sent from Alice to Charlie: Charlie's introKey, as received in the Peer Test message from Charlie
MAC Key used: Listed in order of occurrence:
  1. When sent from Alice to Bob: Alice/Bob MAC Key (The protocol also permits Bob's introKey if Alice and Bob do not have an established session, but in the current implementation Alice always selects a Bob that is established. As of release 0.9.15, Bob will reject PeerTests from peers without an established session.)
  2. When sent from Bob to Charlie: Bob/Charlie MAC Key
  3. When sent from Charlie to Bob: Bob/Charlie MAC Key
  4. When sent from Bob to Alice: Alice's introKey, as received in the Peer Test message from Alice
  5. When sent from Charlie to Alice: Alice's introKey, as received in the Peer Test message from Bob
  6. When sent from Alice to Charlie: Charlie's introKey, as received in the Peer Test message from Charlie

Message format:

{% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | test nonce |size| Alice IP addr +----+----+----+----+----+----+----+----+ | Port (A)| | +----+----+----+ + | Alice or Charlie's | + introduction key (Alice's is sent to + | Bob and Charlie, while Charlie's is | + sent to Alice) + | | + +----+----+----+----+----+ | | arbitrary amount of | +----+----+----+ | | uninterpreted data | ~ . . . ~ {% endhighlight %}

Typical size including header, in current implementation: 80 bytes (before non-mod-16 padding)

Notes

HolePunch

A HolePunch is simply a UDP packet with no data. It is unauthenticated and unencrypted. It does not contain a SSU header, so it does not have a message type number. It is sent from Charlie to Alice as a part of the Introduction sequence.

Sample datagrams

Minimal data message (no fragments, no ACKs, no NACKs, etc)
(Size: 39 bytes) {% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | MAC | + + | | +----+----+----+----+----+----+----+----+ | IV | + + | | +----+----+----+----+----+----+----+----+ |flag| time |flag|#frg| | +----+----+----+----+----+----+----+ + | padding to fit a full AES256 block | +----+----+----+----+----+----+----+----+ {% endhighlight %} Minimal data message with payload
(Size: 46+fragmentSize bytes) {% highlight lang='dataspec' %} +----+----+----+----+----+----+----+----+ | MAC | + + | | +----+----+----+----+----+----+----+----+ | IV | + + | | +----+----+----+----+----+----+----+----+ |flag| time |flag|#frg| +----+----+----+----+----+----+----+----+ messageId | frag info | | +----+----+----+----+----+----+ + | that many bytes of fragment data | ~ . . . ~ | | +----+----+----+----+----+----+----+----+ {% endhighlight %} {% endblock %}