Ratchet: Adaptive order of muxed decrypt based on previous traffic

This commit is contained in:
zzz
2020-04-19 14:21:24 +00:00
parent cd035e1247
commit 57cd4c5843
2 changed files with 90 additions and 19 deletions

View File

@ -35,12 +35,15 @@ final class MuxedEngine {
throw new IllegalArgumentException(); throw new IllegalArgumentException();
CloveSet rv = null; CloveSet rv = null;
// Try in-order from fastest to slowest // Try in-order from fastest to slowest
// Ratchet Tag boolean preferRatchet = keyManager.preferRatchet();
rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM()); if (preferRatchet) {
if (rv != null) // Ratchet Tag
return rv; rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
if (_log.shouldDebug()) if (rv != null)
_log.debug("Ratchet tag not found"); return rv;
if (_log.shouldDebug())
_log.debug("Ratchet tag not found before AES");
}
// AES Tag // AES Tag
if (data.length >= 128 && (data.length & 0x0f) == 0) { if (data.length >= 128 && (data.length & 0x0f) == 0) {
byte[] dec = _context.elGamalAESEngine().decryptFast(data, elgKey, keyManager.getElgSKM()); byte[] dec = _context.elGamalAESEngine().decryptFast(data, elgKey, keyManager.getElgSKM());
@ -48,39 +51,65 @@ final class MuxedEngine {
try { try {
rv = _context.garlicMessageParser().readCloveSet(dec, 0); rv = _context.garlicMessageParser().readCloveSet(dec, 0);
if (rv == null && _log.shouldInfo()) if (rv == null && _log.shouldInfo())
_log.info("AES cloveset error"); _log.info("AES cloveset error after AES? " + preferRatchet);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldInfo()) if (_log.shouldInfo())
_log.info("AES cloveset error", dfe); _log.info("AES cloveset error after AES? " + preferRatchet, dfe);
} }
return rv; return rv;
} else { } else {
if (_log.shouldDebug()) if (_log.shouldDebug())
_log.debug("AES tag not found"); _log.debug("AES tag not found after ratchet? " + preferRatchet);
} }
} }
// Ratchet DH if (!preferRatchet) {
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM()); // Ratchet Tag
if (rv != null) rv = _context.eciesEngine().decryptFast(data, ecKey, keyManager.getECSKM());
return rv; if (rv != null)
if (_log.shouldDebug()) return rv;
_log.debug("Ratchet NS decrypt failed"); if (_log.shouldDebug())
_log.debug("Ratchet tag not found after AES");
}
if (preferRatchet) {
// Ratchet DH
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
boolean ok = rv != null;
keyManager.reportDecryptResult(true, ok);
if (ok)
return rv;
if (_log.shouldDebug())
_log.debug("Ratchet NS decrypt failed before ElG");
}
// ElG DH // ElG DH
if (data.length >= 514 && (data.length & 0x0f) == 2) { if (data.length >= 514 && (data.length & 0x0f) == 2) {
byte[] dec = _context.elGamalAESEngine().decryptSlow(data, elgKey, keyManager.getElgSKM()); byte[] dec = _context.elGamalAESEngine().decryptSlow(data, elgKey, keyManager.getElgSKM());
if (dec != null) { if (dec != null) {
try { try {
rv = _context.garlicMessageParser().readCloveSet(dec, 0); rv = _context.garlicMessageParser().readCloveSet(dec, 0);
if (rv == null && _log.shouldInfo()) boolean ok = rv != null;
_log.info("ElG cloveset error"); keyManager.reportDecryptResult(false, ok);
if (ok)
return rv;
if (_log.shouldInfo())
_log.info("ElG cloveset error after ratchet? " + preferRatchet);
} catch (DataFormatException dfe) { } catch (DataFormatException dfe) {
if (_log.shouldInfo()) if (_log.shouldInfo())
_log.info("ElG cloveset error", dfe); _log.info("ElG cloveset error afterRatchet? " + preferRatchet, dfe);
} }
} else { } else {
if (_log.shouldInfo()) if (_log.shouldInfo())
_log.info("ElG decrypt failed"); _log.info("ElG decrypt failed after Ratchet? " + preferRatchet);
} }
keyManager.reportDecryptResult(false, false);
}
if (!preferRatchet) {
// Ratchet DH
rv = _context.eciesEngine().decryptSlow(data, ecKey, keyManager.getECSKM());
boolean ok = rv != null;
keyManager.reportDecryptResult(true, ok);
if (!ok && _log.shouldDebug())
_log.debug("Ratchet NS decrypt failed after ElG");
} }
return rv; return rv;
} }

View File

@ -3,6 +3,7 @@ package net.i2p.router.crypto.ratchet;
import java.io.IOException; import java.io.IOException;
import java.io.Writer; import java.io.Writer;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.crypto.EncType; import net.i2p.crypto.EncType;
@ -22,6 +23,11 @@ public class MuxedSKM extends SessionKeyManager {
private final TransientSessionKeyManager _elg; private final TransientSessionKeyManager _elg;
private final RatchetSKM _ec; private final RatchetSKM _ec;
private final AtomicInteger _elgCounter = new AtomicInteger();
private final AtomicInteger _ecCounter = new AtomicInteger();
// ElG is about this much slower than EC
private static final int ELG_SLOW_FACTOR = 5;
private static final int RESTART_COUNTERS = 500;
public MuxedSKM(TransientSessionKeyManager elg, RatchetSKM ec) { public MuxedSKM(TransientSessionKeyManager elg, RatchetSKM ec) {
_elg = elg; _elg = elg;
@ -32,6 +38,42 @@ public class MuxedSKM extends SessionKeyManager {
public RatchetSKM getECSKM() { return _ec; } public RatchetSKM getECSKM() { return _ec; }
/**
* Should we try the Ratchet slow decrypt before ElG slow decrypt?
* Adaptive test based on previous mix of traffic for this SKM,
* as reported by reportDecryptResult().
*
* @since 0.9.46
*/
boolean preferRatchet() {
int ec = _ecCounter.get();
int elg = _elgCounter.get();
if (ec > RESTART_COUNTERS / 10 &&
elg > RESTART_COUNTERS / 10 &&
ec + elg > RESTART_COUNTERS) {
_ecCounter.set(0);
_elgCounter.set(0);
return true;
}
return ec >= elg / ELG_SLOW_FACTOR;
}
/**
* Report the result of a slow decrypt attempt.
*
* @param isRatchet true for EC, false for ElG
* @param success true for successful decrypt
* @since 0.9.46
*/
void reportDecryptResult(boolean isRatchet, boolean success) {
if (success) {
if (isRatchet)
_ecCounter.incrementAndGet();
else
_elgCounter.incrementAndGet();
}
}
/** /**
* ElG only * ElG only
*/ */