Overview

DH Key Exchange

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

Message Header

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:

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 [CRYPTO-HMAC]
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.

Session Key Details

If the most significant bit is 1 (i.e. array[0] & 0x80 != 0), prepend a 0x00
byte, as in Java's BigInteger.toByteArray() representation

If the byte array is greater than or equal to 32 bytes, use the first (most
significant) 32 bytes

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:

Take the exchanged DH key byte array, prepended with a 0x00 byte if
necessary, from step 2 in the Session Key Details above.

If that byte array is greater than or equal to 64 bytes, the MAC key is
bytes 33-64 from that byte array.

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).

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. Extended options have always been
part of the specification, but were unimplemented until release 0.9.24. When
present, the option format is specific to the message type. See message
documentation below on whether extended options are expected for the given
message, and the specified format. While Java routers have always recognized the
flag and options length, other implementations have not. Therefore, do not send
extended options to routers older than release 0.9.24.

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
[CRYPTO-AES].

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.

Both introduction keys and session keys are 32 bytes, and are defined by the
Common structures specification [SESSIONKEY]. 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

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 4-byte unsigned integer timestamps with one-second resolution. Because
these values are unsigned, they will not roll over until February 2106.

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.

Notes

If the relay tag is nonzero, Bob is offering to act as an introducer for
Alice. Alice may subsequently publish Bob's address and the relay tag in the
network database.

For the signature, Bob must use his external port, as that what Alice will
use to verify. If Bob's NAT/firewall has mapped his internal port to a
different external port, and Bob is unaware of it, the verification by Alice
will fail.

See the Keys section above for details on signatures. Alice already has
Bob's public signing key, from the network database.

Through release 0.9.15, the signature was always a 40 byte DSA signature and
the padding was always 8 bytes. As of release 0.9.16, the signature type and
length are implied by the type of the [SigningPublicKey] in Bob's
[RouterIdentity]. The padding is as necessary to a multiple of 16 bytes.

This is the only message that uses the sender's intro key. All others use the
receiver's intro key or the established session key.

Signed-on time appears to be unused or unverified in the current
implementation.

The uninterpreted data could possibly be used in the future for challenges.

Extended options in the header: Not expected, undefined.

SessionConfirmed (type 2)

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

Notes

In the current implementation, the maximum fragment size is 512 bytes. This
should be extended so that longer signatures will work without fragmentation.
The current implementation does not correctly process signatures split across
two fragments.

The typical [RouterIdentity] is 387 bytes, so no fragmentation is ever
necessary. If new crypto extends the size of the RouterIdentity, the
fragmentation scheme must be tested carefully.

There is no mechanism for requesting or redelivering missing fragments.

Signed-on time appears to be unused or unverified in the current
implementation.

Since the signature is at the end, the padding in the last or only packet
must pad the total packet to a multiple of 16 bytes, or the signature will
not get decrypted correctly. This is different from all the other message
types, where the padding is at the end.

Through release 0.9.15, the signature was always a 40 byte DSA signature. As
of release 0.9.16, the signature type and length are implied by the type of
the [SigningPublicKey] in Alice's [RouterIdentity]. The padding is as
necessary to a multiple of 16 bytes.

Extended options in the header: Not expected, undefined.

SessionDestroyed (type 8)

The SessionDestroyed 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

Destroy messages received with the sender's or receiver's intro key will be
ignored.

Extended options in the header: Not expected, undefined.

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, as received by
Alice in the SessionCreated message from Bob

Notes

The IP address is only included if it is be different than the packet's
source address and port. In the current implementation, the IP length is
always 0 and the port is always 0, and the receiver should use the packet's
source address and port.

This message may be sent via IPv4 or IPv6. If IPv6, Alice must include her
IPv4 address and port.

If Alice includes her address/port, Bob may perform additional validation
before continuing.

Prior to release 0.9.24, Java I2P rejected any address or port that was
different from the connection.

Challenge is unimplemented, challenge size is always zero

There are no plans to implement relaying for IPv6.

Prior to release 0.9.12, Bob's intro key was always used. As of release
0.9.12, the session key is used if there is an established session between
Alice and Bob. In practice, there must be an established session, as Alice
will only get the nonce (introduction tag) from the session created message,
and Bob will mark the introduction tag invalid once the session is destroyed.

Notes

This message may be sent via IPv4 or IPv6.

Alice's IP address/port are the apparent IP/port that Bob received the
RelayRequest on (not necessarily the IP Alice included in the RelayRequest),
and may be IPv4 or IPv6. Alice currently ignores these on receive.

Charlie's IP address must be IPv4, as that is the address that Alice will
send the SessionRequest to after the Hole Punch.

There are no plans to implement relaying for IPv6.

Prior to release 0.9.12, Alice's intro key was always used. As of release
0.9.12, the session key is used if there is an established session between
Alice and Bob.

Extended options in the header: Not expected, undefined.

RelayIntro (type 5)

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

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:

Notes

The current implementation adds a limited number of duplicate acks for
messages previously acked, if space is available.

If the number of fragments is zero, this is an ack-only or keepalive message.

The ECN feature is unimplemented, and the bit is never set.

In the current implementation, the want reply bit is set when the number of
fragments is greater then zero, and not set when there are no fragments.

Extended data is unimplemented and never present.

Reception of multiple fragments is supported in all releases. Transmission of
multiple fragments is implemented in release 0.9.16.

As currently implemented, maximum fragments is 64 (maximum fragment number =
63).

As currently implemented, maximum fragment size is of course less than the
MTU.

Take care not to exceed the maximum MTU even if there is a large number of
ACKs to send.

The protocol allows zero-length fragments but there's no reason to send them.

In SSU, the data uses a short 5-byte I2NP header followed by the payload of
the I2NP message instead of the standard 16-byte I2NP header. The short I2NP
header consists only of the one-byte I2NP type and 4-byte expiration in
seconds. The I2NP message ID is used as the message ID for the fragment. The
I2NP size is assembled from the fragment sizes. The I2NP checksum is not
required as UDP message integrity is ensured by decryption.

Message IDs are not sequence numbers and are not consecutive. SSU does not
guarantee in-order delivery. While we use the I2NP message ID as the SSU
message ID, from the SSU protocol view, they are random numbers. In fact,
since the router uses a single Bloom filter for all peers, the message ID
must be an actual random number.

Because there are no sequence numbers, there is no way to be sure an ACK was
received. The current implementation routinely sends a large amount of
duplicate ACKs. Duplicate ACKs should not be taken as an indication of
congestion.

ACK Bitfield notes: The receiver of a data packet does not know how many
fragments are in the message unless it has received the last fragment.
Therefore, the number of bitfield bytes sent in response may be less or more
than the number of fragments divided by 7. For example, if the highest
fragment the receiver has seen is number 4, only one byte is required to be
sent, even if there may be 13 fragments total. Up to 10 bytes (i.e. (64 / 7)
+ 1) may be included for each message ID acked.

(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.)

When sent from Bob to Charlie: Bob/Charlie sessionKey

When sent from Charlie to Bob: Bob/Charlie sessionKey

When sent from Bob to Alice: Alice's introKey, as
received in the PeerTest message from Alice

When sent from Charlie to Alice: Alice's introKey, as
received in the PeerTest message from Bob

When sent from Alice to Charlie: Charlie's introKey, as
received in the PeerTest message from Charlie

MAC Key used:

Listed in order of occurrence:

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.)

When sent from Bob to Charlie: Bob/Charlie MAC Key

When sent from Charlie to Bob: Bob/Charlie MAC Key

When sent from Bob to Alice: Alice's introKey, as
received in the PeerTest message from Alice

When sent from Charlie to Alice: Alice's introKey, as
received in the PeerTest message from Bob

When sent from Alice to Charlie: Charlie's introKey, as
received in the PeerTest message from Charlie

Notes

When sent by Alice, IP address size is 0, IP address is not present, and port
is 0, as Bob and Charlie do not use the data; the point is to determine
Alice's true IP address/port and tell Alice; Bob and Charlie don't care what
Alice thinks her address is.

When sent by Bob or Charlie, IP and port are present, and IP address is
always 4 bytes in the current implementation. IPv6 testing is not currently
supported.

IPv6 Notes: Through release 0.9.26, only testing of IPv4 addresses is supported. Therefore, all
Alice-Bob and Alice-Charlie communication must be via IPv4. Bob-Charlie
communication, however, may be via IPv4 or IPv6. Alice's address, when
specified in the PeerTest message, must be 4 bytes.
As of release 0.9.27, testing of IPv6 addresses is supported,
and Alice-Bob and Alice-Charlie communication may be via IPv6,
if Bob and Charlie indicate support with a 'B' capability in their published IPv6 address.
See Proposal 126 for details.

Alice sends the request to Bob using an existing session over the transport (IPv4 or IPv6) that she wishes to test.
When Bob receives a request from Alice via IPv4, Bob must select a Charlie that advertises an IPv4 address.
When Bob receives a request from Alice via IPv6, Bob must select a Charlie that advertises an IPv6 address.
The actual Bob-Charlie communication may be via IPv4 or IPv6 (i.e., independent of Alice's address type).

A peer must maintain a table of active test states (nonces). On reception of
a PeerTest message, look up the nonce in the table. If found, it's an
existing test and you know your role (Alice, Bob, or Charlie). Otherwise, if
the IP is not present and the port is 0, this is a new test and you are Bob.
Otherwise, this is a new test and you are Charlie.

As of release 0.9.15, Alice must have an established session with Bob and use
the session key.

Extended options in the header: Not expected, undefined.

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.