forked from I2P_Developers/i2p.i2p
Ratchet (proposal 144):
- Randomize high two bits of Elligator2 encoding (incompatible change) - Fix NPE in RatchetTagSet.toString() - Use zeros for padding block - Add more debug logging
This commit is contained in:
@ -242,6 +242,8 @@ public final class ECIESAEADEngine {
|
||||
state.getLocalKeyPair().setPublicKey(targetPrivateKey.toPublic().getData(), 0);
|
||||
state.getLocalKeyPair().setPrivateKey(targetPrivateKey.getData(), 0);
|
||||
state.start();
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before decrypt new session: " + state);
|
||||
|
||||
// Elg2
|
||||
byte[] tmp = new byte[KEYLEN];
|
||||
@ -269,8 +271,10 @@ public final class ECIESAEADEngine {
|
||||
|
||||
byte[] bobPK = new byte[KEYLEN];
|
||||
state.getRemotePublicKey().getPublicKey(bobPK, 0);
|
||||
if (_log.shouldDebug())
|
||||
if (_log.shouldDebug()) {
|
||||
_log.debug("NS decrypt success from PK " + Base64.encode(bobPK));
|
||||
_log.debug("State after decrypt new session: " + state);
|
||||
}
|
||||
if (Arrays.equals(bobPK, NULLPK)) {
|
||||
// TODO
|
||||
if (_log.shouldWarn())
|
||||
@ -351,8 +355,12 @@ public final class ECIESAEADEngine {
|
||||
_log.warn("Elg2 decode fail NSR");
|
||||
return null;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before decrypt new session reply: " + state);
|
||||
System.arraycopy(k.getData(), 0, data, TAGLEN, KEYLEN);
|
||||
state.mixHash(tag, 0, TAGLEN);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after mixhash tag before decrypt new session reply: " + state);
|
||||
try {
|
||||
state.readMessage(data, 8, 48, ZEROLEN, 0);
|
||||
} catch (GeneralSecurityException gse) {
|
||||
@ -363,6 +371,8 @@ public final class ECIESAEADEngine {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after decrypt new session reply: " + state);
|
||||
|
||||
// split()
|
||||
byte[] ck = state.getChainingKey();
|
||||
@ -622,6 +632,8 @@ public final class ECIESAEADEngine {
|
||||
state.getLocalKeyPair().setPublicKey(priv.toPublic().getData(), 0);
|
||||
state.getLocalKeyPair().setPrivateKey(priv.getData(), 0);
|
||||
state.start();
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before encrypt new session: " + state);
|
||||
|
||||
byte[] payload = createPayload(cloves, cloves.getExpiration());
|
||||
|
||||
@ -633,6 +645,8 @@ public final class ECIESAEADEngine {
|
||||
_log.warn("Encrypt fail NS", gse);
|
||||
return null;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after encrypt new session: " + state);
|
||||
|
||||
// overwrite eph. key with encoded key
|
||||
DHState eph = state.getLocalEphemeralKeyPair();
|
||||
@ -642,6 +656,8 @@ public final class ECIESAEADEngine {
|
||||
return null;
|
||||
}
|
||||
eph.getEncodedPublicKey(enc, 0);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Elligator2 encoded eph. key: " + Base64.encode(enc, 0, 32));
|
||||
|
||||
// tell the SKM
|
||||
keyManager.createSession(target, state);
|
||||
@ -669,8 +685,12 @@ public final class ECIESAEADEngine {
|
||||
*/
|
||||
private byte[] encryptNewSessionReply(CloveSet cloves, PublicKey target, HandshakeState state,
|
||||
RatchetSessionTag currentTag, RatchetSKM keyManager) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State before encrypt new session reply: " + state);
|
||||
byte[] tag = currentTag.getData();
|
||||
state.mixHash(tag, 0, TAGLEN);
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after mixhash tag before encrypt new session reply: " + state);
|
||||
|
||||
byte[] payload = createPayload(cloves, 0);
|
||||
|
||||
@ -684,6 +704,8 @@ public final class ECIESAEADEngine {
|
||||
_log.warn("Encrypt fail NSR part 1", gse);
|
||||
return null;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("State after encrypt new session reply: " + state);
|
||||
|
||||
// overwrite eph. key with encoded key
|
||||
DHState eph = state.getLocalEphemeralKeyPair();
|
||||
@ -847,7 +869,10 @@ public final class ECIESAEADEngine {
|
||||
len += block.getTotalLength();
|
||||
}
|
||||
int padlen = 1 + _context.random().nextInt(MAXPAD);
|
||||
Block block = new PaddingBlock(_context, padlen);
|
||||
// random data
|
||||
//Block block = new PaddingBlock(_context, padlen);
|
||||
// zeros
|
||||
Block block = new PaddingBlock(padlen);
|
||||
blocks.add(block);
|
||||
len += block.getTotalLength();
|
||||
byte[] payload = new byte[len];
|
||||
|
@ -82,21 +82,25 @@ class Elligator2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* From javascript version documentation:
|
||||
*
|
||||
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
||||
* Which one to return is determined by y coordinate.
|
||||
* Since Curve25519 doesn't use y due to optimizations, you should specify a Boolean value
|
||||
* as the second argument of the function.
|
||||
* It should be unpredictable, because it's recoverable from the representative.
|
||||
* Use for on-the-wire. Don't use for unit tests as output will be randomized
|
||||
* based on the 'alternative' and the high bits.
|
||||
* There are eight possible encodings for any point.
|
||||
* Output will look like 256 random bits.
|
||||
*
|
||||
* @return "representative", little endian or null on failure
|
||||
*/
|
||||
public byte[] encode(PublicKey point) {
|
||||
return encode(point, _context.random().nextBoolean());
|
||||
byte[] random = new byte[1];
|
||||
_context.random().nextBytes(random);
|
||||
byte rand = random[0];
|
||||
return encode(point, (rand & 0x01) == 0, rand);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use for unit tests. Don't use for on-the-wire; use one-arg version.
|
||||
* Output will look like 254 random bits.
|
||||
* High two bits of rv[31] will be zero.
|
||||
*
|
||||
* From javascript version documentation:
|
||||
*
|
||||
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
||||
@ -107,7 +111,26 @@ class Elligator2 {
|
||||
*
|
||||
* @return "representative", little endian or null on failure
|
||||
*/
|
||||
public static byte[] encode(PublicKey point, boolean alternative) {
|
||||
protected static byte[] encode(PublicKey point, boolean alternative) {
|
||||
return encode(point, alternative, (byte) 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output will look like 254 random bits. High two bits of highBits will be ORed in.
|
||||
*
|
||||
* From javascript version documentation:
|
||||
*
|
||||
* The algorithm can return two different values for a single x coordinate if it's not 0.
|
||||
* Which one to return is determined by y coordinate.
|
||||
* Since Curve25519 doesn't use y due to optimizations, you should specify a Boolean value
|
||||
* as the second argument of the function.
|
||||
* It should be unpredictable, because it's recoverable from the representative.
|
||||
*
|
||||
* @param highBits High two bits will be ORed into rv[31]
|
||||
* @return "representative", little endian or null on failure
|
||||
* @since 0.9.45 to add highBits arg
|
||||
*/
|
||||
private static byte[] encode(PublicKey point, boolean alternative, byte highBits) {
|
||||
if (DISABLE)
|
||||
return point.getData();
|
||||
|
||||
@ -152,6 +175,8 @@ class Elligator2 {
|
||||
|
||||
// little endian
|
||||
byte[] rv = ENCODING.encode(r);
|
||||
// randomize two high bits
|
||||
rv[REPRESENTATIVE_LENGTH - 1] |= highBits & (byte) 0xc0;
|
||||
return rv;
|
||||
}
|
||||
|
||||
@ -162,6 +187,7 @@ class Elligator2 {
|
||||
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
||||
*
|
||||
* @param representative the encoded data, little endian, 32 bytes
|
||||
* WILL BE MODIFIED by masking byte 31
|
||||
* @return x or null on failure
|
||||
*/
|
||||
public static PublicKey decode(byte[] representative) {
|
||||
@ -175,7 +201,8 @@ class Elligator2 {
|
||||
* It's also able to return null if the representative is invalid (there are only 10 invalid representatives).
|
||||
*
|
||||
* @param alternative out parameter, or null if you don't care
|
||||
* @param representative the encoded data, little endian, 32 bytes
|
||||
* @param representative the encoded data, little endian, 32 bytes;
|
||||
* WILL BE MODIFIED by masking byte 31
|
||||
* @return x or null on failure
|
||||
*/
|
||||
public static PublicKey decode(AtomicBoolean alternative, byte[] representative) {
|
||||
@ -185,6 +212,8 @@ class Elligator2 {
|
||||
return new PublicKey(EncType.ECIES_X25519, representative);
|
||||
|
||||
// r
|
||||
// Mask out two high bits, to get valid 254 bits.
|
||||
representative[REPRESENTATIVE_LENGTH - 1] &= (byte) 0x3f;
|
||||
BigInteger r = ENCODING.toBigInteger(representative);
|
||||
|
||||
// If r >= (p - 1) / 2
|
||||
@ -313,6 +342,7 @@ class Elligator2 {
|
||||
//00000010 d8 fa ec 68 e5 e6 7e f4 5e bb 82 ee ba 52 60 4f |...h..~.^....R`O|
|
||||
|
||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||
Elligator2 elg2 = new Elligator2(ctx);
|
||||
X25519KeyFactory xkf = new X25519KeyFactory(ctx);
|
||||
for (int i = 0; i < 10; i++) {
|
||||
PublicKey pub;
|
||||
@ -322,9 +352,10 @@ class Elligator2 {
|
||||
System.out.println("Trying encode " + ++j);
|
||||
KeyPair kp = xkf.getKeys();
|
||||
pub = kp.getPublic();
|
||||
enc = encode(pub, ctx.random().nextBoolean());
|
||||
enc = elg2.encode(pub);
|
||||
} while (enc == null);
|
||||
PublicKey pub2 = decode(null, enc);
|
||||
System.out.println("Encoded:\n" + HexDump.dump(enc));
|
||||
PublicKey pub2 = decode(enc);
|
||||
if (pub2 == null) {
|
||||
System.out.println("Decode FAIL");
|
||||
continue;
|
||||
@ -336,6 +367,20 @@ class Elligator2 {
|
||||
System.out.println("calc: " + pub2.toBase64());
|
||||
}
|
||||
}
|
||||
|
||||
System.out.println("Random decode test");
|
||||
byte[] enc = new byte[32];
|
||||
int fails = 0;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
ctx.random().nextBytes(enc);
|
||||
pk = decode(enc);
|
||||
if (pk == null)
|
||||
fails++;
|
||||
}
|
||||
if (fails > 0)
|
||||
System.out.println("FAIL decode " + fails + " / 1000");
|
||||
else
|
||||
System.out.println("PASS");
|
||||
}
|
||||
****/
|
||||
}
|
||||
|
@ -439,6 +439,8 @@ class RatchetTagSet implements TagSetHandle {
|
||||
for (int i = 0; i < sz; i++) {
|
||||
int n = _sessionTags.keyAt(i);
|
||||
RatchetSessionTag tag = _sessionTags.valueAt(i);
|
||||
if (tag == null)
|
||||
continue;
|
||||
buf.append("\n " + n + '\t' + Base64.encode(tag.getData()));
|
||||
if (_sessionKeys != null) {
|
||||
byte[] key = _sessionKeys.get(n);
|
||||
|
Reference in New Issue
Block a user