use the first 16 bytes of the SHA256 for the columns & verification block, rather than all 32 bytes.
(AES won't let us go smaller. oh well)
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
<code>$Id: tunnel.html,v 1.8 2005/01/15 01:43:35 jrandom Exp $</code>
|
||||
<code>$Id: tunnel.html,v 1.9 2005/01/15 19:08:14 jrandom Exp $</code>
|
||||
<pre>
|
||||
1) <a href="#tunnel.overview">Tunnel overview</a>
|
||||
2) <a href="#tunnel.operation">Tunnel operation</a>
|
||||
@ -146,8 +146,8 @@ verify the integrity of the checksum block. The specific details follow.</p>
|
||||
|
||||
<p>The encryption used is such that decryption
|
||||
merely requires running over the data with AES in CBC mode, calculating the
|
||||
SHA256 of a certain fixed portion of the message (bytes 16 through $size-288),
|
||||
and searching for that hash in the checksum block. There is a fixed number
|
||||
SHA256 of a certain fixed portion of the message (bytes 16 through $size-144),
|
||||
and searching for the first 16 bytes of that hash in the checksum block. There is a fixed number
|
||||
of hops defined (8 peers) so that we can verify the message
|
||||
without either leaking the position in the tunnel or having the message
|
||||
continually "shrink" as layers are peeled off. For tunnels shorter than 8
|
||||
@ -239,7 +239,7 @@ To visualize this a bit:</p>
|
||||
</table>
|
||||
|
||||
<p>In the above, P[7] is the same as the original data being passed through the
|
||||
tunnel (the preprocessed messages), and V[7] is the SHA256 of eH[0-7] as seen on
|
||||
tunnel (the preprocessed messages), and V[7] is the first 16 bytes of the SHA256 of eH[0-7] as seen on
|
||||
peer7 after decryption. For
|
||||
cells in the matrix "higher up" than the hash, their value is derived by encrypting
|
||||
the cell below it with the key for the peer below it, using the end of the column
|
||||
@ -268,8 +268,8 @@ peer who is the first hop (usually the peer1.recv row) and forward that entirely
|
||||
|
||||
<p>When a participant in a tunnel receives a message, they decrypt a layer with their
|
||||
tunnel key using AES256 in CBC mode with the first 16 bytes as the IV. They then
|
||||
calculate the hash of what they see as the payload (bytes 16 through $size-288) and
|
||||
search for that hash within the decrypted checksum block. If no match is found, the
|
||||
calculate the hash of what they see as the payload (bytes 16 through $size-144) and
|
||||
search for that first 16 bytes of that hash within the decrypted checksum block. If no match is found, the
|
||||
message is discarded. Otherwise, the IV is updated by decrypting it, XORing that value
|
||||
with the IV_WHITENER, and replacing it with the first 16 bytes of its hash. The
|
||||
resulting message is then forwarded on to the next peer for processing.</p>
|
||||
@ -287,7 +287,7 @@ contained in the tunnel.</p>
|
||||
<p>When a message reaches the tunnel endpoint, they decrypts and verifies it like
|
||||
a normal participant. If the checksum block has a valid match, the endpoint then
|
||||
computes the hash of the checksum block itself (as seen after decryption) and compares
|
||||
that to the decrypted verification hash (the last 32 bytes). If that verification
|
||||
that to the decrypted verification hash (the last 16 bytes). If that verification
|
||||
hash does not match, the endpoint takes note of the tagging attempt by one of the
|
||||
tunnel participants and perhaps discards the message.</p>
|
||||
|
||||
@ -328,7 +328,7 @@ and handling fragmentation will not immediately be implemented.</p>
|
||||
|
||||
<p>One alternative to the above process is to remove the checksum block
|
||||
completely and replace the verification hash with a plain hash of the payload.
|
||||
This would simplify processing at the tunnel gateway and save 256 bytes of
|
||||
This would simplify processing at the tunnel gateway and save 144 bytes of
|
||||
bandwidth at each hop. On the other hand, attackers within the tunnel could
|
||||
trivially adjust the message size to one which is easily traceable by
|
||||
colluding external observers in addition to later tunnel participants. The
|
||||
@ -344,7 +344,7 @@ there are three alternatives that can be explored:</p>
|
||||
<ul>
|
||||
<li>Delay a message within a tunnel at an arbitrary hop for either a specified
|
||||
amount of time or a randomized period. This could be achieved by replacing the
|
||||
hash in the checksum block with e.g. the first 16 bytes of the hash, followed by
|
||||
hash in the checksum block with e.g. the first 8 bytes of the hash, followed by
|
||||
some delay instructions. Alternately, the instructions could tell the
|
||||
participant to actually interpret the raw payload as it is, and either discard
|
||||
the message or continue to forward it down the path (where it would be
|
||||
@ -378,12 +378,14 @@ minimize the worries of the predecessor attack, though if it were desired,
|
||||
it wouldn't be much trouble to build both the inbound and outbound tunnels
|
||||
along the same peers.</p>
|
||||
|
||||
<h4>2.7.4) <a name="tunnel.smallerhashes">Use smaller hashes</a></h4>
|
||||
<h4>2.7.4) <a name="tunnel.smallerhashes">Use smaller blocksize</a></h4>
|
||||
|
||||
<p>At the moment, the plan is to reuse the existing SHA256 code and build
|
||||
all of the checksum and verification hashes as 32 byte SHA256 values. 20
|
||||
byte SHA1 would likely be more than sufficient, or perhaps smaller - first
|
||||
4 bytes of the SHA256.</p>
|
||||
<p>At the moment, our use of AES limits our block size to 16 bytes, which
|
||||
in turn provides the minimum size for each of the checksum block columns.
|
||||
If another algorithm was used with a smaller block size, or could otherwise
|
||||
allow the safe building of the checksum block with smaller portions of the
|
||||
hash, it might be worth exploring. The 16 bytes used now at each hop should
|
||||
be more than sufficient.</p>
|
||||
|
||||
<h2>3) <a name="tunnel.building">Tunnel building</a></h2>
|
||||
|
||||
|
@ -53,6 +53,10 @@ public class GatewayMessage {
|
||||
private static final byte EMPTY[] = new byte[0];
|
||||
private static final int COLUMNS = HOPS;
|
||||
private static final int HASH_ROWS = HOPS;
|
||||
/** # bytes of the hash to maintain in each column */
|
||||
static final int COLUMN_WIDTH = IV_SIZE;
|
||||
/** # bytes of the verification hash to maintain */
|
||||
static final int VERIFICATION_WIDTH = IV_SIZE;
|
||||
|
||||
/** used to munge the IV during per-hop translations */
|
||||
static final byte IV_WHITENER[] = new byte[] { (byte)0x31, (byte)0xd6, (byte)0x74, (byte)0x17,
|
||||
@ -70,9 +74,9 @@ public class GatewayMessage {
|
||||
_iv = new byte[HOPS-1][IV_SIZE];
|
||||
_eIV = new byte[HOPS-1][IV_SIZE];
|
||||
_H = new byte[HOPS][Hash.HASH_LENGTH];
|
||||
_eH = new byte[COLUMNS][HASH_ROWS][Hash.HASH_LENGTH];
|
||||
_preV = new byte[HOPS*Hash.HASH_LENGTH];
|
||||
_V = new byte[Hash.HASH_LENGTH];
|
||||
_eH = new byte[COLUMNS][HASH_ROWS][COLUMN_WIDTH];
|
||||
_preV = new byte[HOPS*COLUMN_WIDTH];
|
||||
_V = new byte[VERIFICATION_WIDTH];
|
||||
_order = new int[HOPS];
|
||||
_encrypted = false;
|
||||
_payloadSet = false;
|
||||
@ -224,24 +228,19 @@ public class GatewayMessage {
|
||||
// which _H[hash] value are we rendering in this column?
|
||||
int hash = _order[column];
|
||||
// fill in the cleartext version for this column
|
||||
System.arraycopy(_H[hash], 0, _eH[column][hash], 0, Hash.HASH_LENGTH);
|
||||
System.arraycopy(_H[hash], 0, _eH[column][hash], 0, COLUMN_WIDTH);
|
||||
|
||||
// now fill in the "earlier" _eH[column][row-1] values for earlier hops
|
||||
// by encrypting _eH[column][row] with the peer's key, using the end
|
||||
// of the previous column (or _eIV[row-1]) as the IV
|
||||
// by encrypting _eH[column][row] with the peer's key, using the
|
||||
// previous column (or _eIV[row-1]) as the IV
|
||||
for (int row = hash; row > 0; row--) {
|
||||
SessionKey key = cfg.getSessionKey(row);
|
||||
// first half
|
||||
if (column == 0) {
|
||||
DataHelper.xor(_eIV[row-1], 0, _eH[column][row], 0, _eH[column][row-1], 0, IV_SIZE);
|
||||
} else {
|
||||
DataHelper.xor(_eH[column-1][row-1], IV_SIZE, _eH[column][row], 0, _eH[column][row-1], 0, IV_SIZE);
|
||||
DataHelper.xor(_eH[column-1][row-1], 0, _eH[column][row], 0, _eH[column][row-1], 0, IV_SIZE);
|
||||
}
|
||||
_context.aes().encryptBlock(_eH[column][row-1], 0, key, _eH[column][row-1], 0);
|
||||
|
||||
// second half
|
||||
DataHelper.xor(_eH[column][row-1], 0, _eH[column][row], IV_SIZE, _eH[column][row-1], IV_SIZE, IV_SIZE);
|
||||
_context.aes().encryptBlock(_eH[column][row-1], IV_SIZE, key, _eH[column][row-1], IV_SIZE);
|
||||
}
|
||||
|
||||
// fill in the "later" rows by encrypting the previous rows with the
|
||||
@ -255,10 +254,7 @@ public class GatewayMessage {
|
||||
if (column == 0)
|
||||
DataHelper.xor(_eIV[row-1], 0, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE);
|
||||
else
|
||||
DataHelper.xor(_eH[column-1][row-1], IV_SIZE, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE);
|
||||
|
||||
_context.aes().decryptBlock(_eH[column][row-1], IV_SIZE, key, _eH[column][row], IV_SIZE);
|
||||
DataHelper.xor(_eH[column][row-1], 0, _eH[column][row], IV_SIZE, _eH[column][row], IV_SIZE, IV_SIZE);
|
||||
DataHelper.xor(_eH[column-1][row-1], 0, _eH[column][row], 0, _eH[column][row], 0, IV_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +264,7 @@ public class GatewayMessage {
|
||||
for (int column = 0; column < COLUMNS; column++) {
|
||||
try {
|
||||
_log.debug("_eH[" + column + "][" + peer + "] = " + Base64.encode(_eH[column][peer])
|
||||
+ (peer == 0 ? "" : DataHelper.eq(_H[peer-1], _eH[column][peer]) ? " CLEARTEXT" : ""));
|
||||
+ (peer == 0 ? "" : DataHelper.eq(_H[peer-1], 0, _eH[column][peer], 0, COLUMN_WIDTH) ? " CLEARTEXT" : ""));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
System.out.println("column="+column + " peer=" + peer);
|
||||
@ -285,9 +281,9 @@ public class GatewayMessage {
|
||||
*/
|
||||
private final void encryptVerificationHash(GatewayTunnelConfig cfg) {
|
||||
for (int i = 0; i < COLUMNS; i++)
|
||||
System.arraycopy(_eH[i][HASH_ROWS-1], 0, _preV, i * Hash.HASH_LENGTH, Hash.HASH_LENGTH);
|
||||
System.arraycopy(_eH[i][HASH_ROWS-1], 0, _preV, i * COLUMN_WIDTH, COLUMN_WIDTH);
|
||||
Hash v = _context.sha().calculateHash(_preV);
|
||||
System.arraycopy(v.getData(), 0, _V, 0, Hash.HASH_LENGTH);
|
||||
System.arraycopy(v.getData(), 0, _V, 0, VERIFICATION_WIDTH);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("_V final = " + Base64.encode(_V));
|
||||
@ -296,10 +292,8 @@ public class GatewayMessage {
|
||||
SessionKey key = cfg.getSessionKey(i);
|
||||
// xor the last block of the encrypted payload with the first block of _V to
|
||||
// continue the CTR operation
|
||||
DataHelper.xor(_V, 0, _eH[COLUMNS-1][i-1], IV_SIZE, _V, 0, IV_SIZE);
|
||||
DataHelper.xor(_V, 0, _eH[COLUMNS-1][i-1], 0, _V, 0, IV_SIZE);
|
||||
_context.aes().encryptBlock(_V, 0, key, _V, 0);
|
||||
DataHelper.xor(_V, 0, _V, IV_SIZE, _V, IV_SIZE, IV_SIZE);
|
||||
_context.aes().encryptBlock(_V, IV_SIZE, key, _V, IV_SIZE);
|
||||
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("_V at peer " + i + " = " + Base64.encode(_V));
|
||||
@ -317,8 +311,8 @@ public class GatewayMessage {
|
||||
public final int getExportedSize() {
|
||||
return IV_SIZE +
|
||||
_payload.length +
|
||||
COLUMNS * Hash.HASH_LENGTH +
|
||||
Hash.HASH_LENGTH; // verification hash
|
||||
COLUMNS * COLUMN_WIDTH +
|
||||
VERIFICATION_WIDTH; // verification hash
|
||||
}
|
||||
|
||||
/**
|
||||
@ -343,11 +337,11 @@ public class GatewayMessage {
|
||||
System.arraycopy(_payload, 0, target, cur, _payload.length);
|
||||
cur += _payload.length;
|
||||
for (int column = 0; column < COLUMNS; column++) {
|
||||
System.arraycopy(_eH[column][0], 0, target, cur, Hash.HASH_LENGTH);
|
||||
cur += Hash.HASH_LENGTH;
|
||||
System.arraycopy(_eH[column][0], 0, target, cur, COLUMN_WIDTH);
|
||||
cur += COLUMN_WIDTH;
|
||||
}
|
||||
System.arraycopy(_V, 0, target, cur, Hash.HASH_LENGTH);
|
||||
cur += Hash.HASH_LENGTH;
|
||||
System.arraycopy(_V, 0, target, cur, VERIFICATION_WIDTH);
|
||||
cur += VERIFICATION_WIDTH;
|
||||
return cur;
|
||||
}
|
||||
|
||||
@ -360,13 +354,13 @@ public class GatewayMessage {
|
||||
Log log = ctx.logManager().getLog(GatewayMessage.class);
|
||||
boolean match = true;
|
||||
|
||||
int off = message.length - (COLUMNS + 1) * Hash.HASH_LENGTH;
|
||||
int off = message.length - (COLUMNS + 1) * COLUMN_WIDTH;
|
||||
for (int column = 0; column < COLUMNS; column++) {
|
||||
boolean ok = DataHelper.eq(_eH[column][peer], 0, message, off, Hash.HASH_LENGTH);
|
||||
boolean ok = DataHelper.eq(_eH[column][peer], 0, message, off, COLUMN_WIDTH);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
log.debug("checksum[" + column + "][" + (peer) + "] matches? " + ok);
|
||||
|
||||
off += Hash.HASH_LENGTH;
|
||||
off += COLUMN_WIDTH;
|
||||
match = match && ok;
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,8 @@ import net.i2p.util.Log;
|
||||
public class TunnelMessageProcessor {
|
||||
private static final int IV_SIZE = GatewayMessage.IV_SIZE;
|
||||
private static final int HOPS = GatewayMessage.HOPS;
|
||||
private static final int COLUMN_WIDTH = GatewayMessage.COLUMN_WIDTH;
|
||||
private static final int VERIFICATION_WIDTH = GatewayMessage.VERIFICATION_WIDTH;
|
||||
|
||||
/**
|
||||
* Unwrap the tunnel message, overwriting it with the decrypted version.
|
||||
@ -28,8 +30,8 @@ public class TunnelMessageProcessor {
|
||||
|
||||
int payloadLength = data.length
|
||||
- IV_SIZE // IV
|
||||
- HOPS * Hash.HASH_LENGTH // checksum blocks
|
||||
- Hash.HASH_LENGTH; // verification of the checksum blocks
|
||||
- HOPS * COLUMN_WIDTH // checksum blocks
|
||||
- VERIFICATION_WIDTH; // verification of the checksum blocks
|
||||
|
||||
Hash recvPayloadHash = ctx.sha().calculateHash(data, IV_SIZE, payloadLength);
|
||||
if (log.shouldLog(Log.DEBUG))
|
||||
@ -60,7 +62,7 @@ public class TunnelMessageProcessor {
|
||||
|
||||
int numBlocks = (data.length - IV_SIZE) / IV_SIZE;
|
||||
// for debugging, so we can compare eIV
|
||||
int numPayloadBlocks = (data.length - IV_SIZE - 2 * IV_SIZE * (GatewayMessage.HOPS + 1)) / IV_SIZE;
|
||||
int numPayloadBlocks = (data.length - IV_SIZE - COLUMN_WIDTH * HOPS - VERIFICATION_WIDTH) / IV_SIZE;
|
||||
|
||||
// prev == previous encrypted block (or IV for the first block)
|
||||
byte prev[] = new byte[IV_SIZE];
|
||||
@ -103,23 +105,23 @@ public class TunnelMessageProcessor {
|
||||
Log log = getLog(ctx);
|
||||
int matchFound = -1;
|
||||
|
||||
int off = data.length - (GatewayMessage.HOPS + 1) * Hash.HASH_LENGTH;
|
||||
for (int i = 0; i < GatewayMessage.HOPS; i++) {
|
||||
if (DataHelper.eq(payloadHash.getData(), 0, data, off, Hash.HASH_LENGTH)) {
|
||||
int off = data.length - HOPS * COLUMN_WIDTH - VERIFICATION_WIDTH;
|
||||
for (int i = 0; i < HOPS; i++) {
|
||||
if (DataHelper.eq(payloadHash.getData(), 0, data, off, COLUMN_WIDTH)) {
|
||||
matchFound = i;
|
||||
break;
|
||||
}
|
||||
|
||||
off += Hash.HASH_LENGTH;
|
||||
off += COLUMN_WIDTH;
|
||||
}
|
||||
|
||||
if (log.shouldLog(Log.DEBUG)) {
|
||||
off = data.length - (GatewayMessage.HOPS + 1) * Hash.HASH_LENGTH;
|
||||
off = data.length - HOPS * COLUMN_WIDTH - VERIFICATION_WIDTH;
|
||||
for (int i = 0; i < HOPS; i++)
|
||||
log.debug("checksum[" + i + "] = " + Base64.encode(data, off + i*Hash.HASH_LENGTH, Hash.HASH_LENGTH)
|
||||
log.debug("checksum[" + i + "] = " + Base64.encode(data, off + i*COLUMN_WIDTH, COLUMN_WIDTH)
|
||||
+ (i == matchFound ? " * MATCH" : ""));
|
||||
|
||||
log.debug("verification = " + Base64.encode(data, data.length - Hash.HASH_LENGTH, Hash.HASH_LENGTH));
|
||||
log.debug("verification = " + Base64.encode(data, data.length - VERIFICATION_WIDTH, VERIFICATION_WIDTH));
|
||||
}
|
||||
|
||||
return matchFound != -1;
|
||||
@ -132,15 +134,15 @@ public class TunnelMessageProcessor {
|
||||
* @return true if the checksum is valid, false if it has been modified
|
||||
*/
|
||||
public boolean verifyChecksum(I2PAppContext ctx, byte message[]) {
|
||||
int checksumSize = GatewayMessage.HOPS * Hash.HASH_LENGTH;
|
||||
int offset = message.length - (checksumSize + Hash.HASH_LENGTH);
|
||||
int checksumSize = HOPS * COLUMN_WIDTH;
|
||||
int offset = message.length - (checksumSize + VERIFICATION_WIDTH);
|
||||
Hash checksumHash = ctx.sha().calculateHash(message, offset, checksumSize);
|
||||
getLog(ctx).debug("Measured checksum: " + checksumHash.toBase64());
|
||||
byte expected[] = new byte[Hash.HASH_LENGTH];
|
||||
System.arraycopy(message, message.length-Hash.HASH_LENGTH, expected, 0, Hash.HASH_LENGTH);
|
||||
byte expected[] = new byte[VERIFICATION_WIDTH];
|
||||
System.arraycopy(message, message.length-VERIFICATION_WIDTH, expected, 0, VERIFICATION_WIDTH);
|
||||
getLog(ctx).debug("Expected checksum: " + Base64.encode(expected));
|
||||
|
||||
return DataHelper.eq(checksumHash.getData(), 0, message, message.length-Hash.HASH_LENGTH, Hash.HASH_LENGTH);
|
||||
return DataHelper.eq(checksumHash.getData(), 0, message, message.length-VERIFICATION_WIDTH, VERIFICATION_WIDTH);
|
||||
}
|
||||
|
||||
private static final Log getLog(I2PAppContext ctx) {
|
||||
|
Reference in New Issue
Block a user