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:
zzz
2020-01-21 17:54:14 +00:00
parent 50c86147b0
commit 064e4046a6
3 changed files with 86 additions and 14 deletions

View File

@ -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];

View File

@ -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");
}
****/
}

View File

@ -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);