From 50ee30b133363bf544179cbd7af1ad6d6a1f59d7 Mon Sep 17 00:00:00 2001 From: zzz Date: Wed, 23 Nov 2022 11:49:00 -0500 Subject: [PATCH] Router: KeysAndCert compressible padding (Proposal 161, Gitlab MR !66) Update some KeysAndCert and PKF debug output ref: http://zzz.i2p/topics/3279 Replaces the 256-byte ElG key in dests with padding. Make all padding in dests and router identities a repeating random 32-byte pattern. This will make gzipped dests and router identities be much smaller: Dests: appx. 320 bytes smaller (82% reduction) RIs: appx. 288 bytes smaller (74% reduction) Expected to primarily benefit database store messages and streaming SYNs. Does not rekey or affect existing destinations or router identities. Testers running this patch may be identifiable via transient destinations. New installs with this patch will be identifiable via router identities. This also will significantly speed up Destination creation as we will no longer generate an ElG keypair. Tested for several months. --- .../net/i2p/client/impl/I2PClientImpl.java | 32 ++++++++-- core/java/src/net/i2p/data/KeysAndCert.java | 10 +++- .../java/src/net/i2p/data/PrivateKeyFile.java | 58 +++++++++++++++---- .../router/startup/CreateRouterInfoJob.java | 14 +++-- 4 files changed, 93 insertions(+), 21 deletions(-) diff --git a/core/java/src/net/i2p/client/impl/I2PClientImpl.java b/core/java/src/net/i2p/client/impl/I2PClientImpl.java index dfd957a98..c9bc25687 100644 --- a/core/java/src/net/i2p/client/impl/I2PClientImpl.java +++ b/core/java/src/net/i2p/client/impl/I2PClientImpl.java @@ -41,6 +41,8 @@ import net.i2p.util.RandomSource; */ public class I2PClientImpl implements I2PClient { + private static final int PADDING_ENTROPY = 32; + /** * Create a destination with a DSA 1024/160 signature type and a null certificate. * This is not bound to the I2PClient, you must supply the data back again @@ -93,9 +95,25 @@ public class I2PClientImpl implements I2PClient { */ public Destination createDestination(OutputStream destKeyStream, Certificate cert) throws I2PException, IOException { Destination d = new Destination(); - Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair(); - PublicKey publicKey = (PublicKey) keypair[0]; - PrivateKey privateKey = (PrivateKey) keypair[1]; + // Don't generate ElGamal keys anymore, they are unused since release 0.6 2005 + //Object keypair[] = KeyGenerator.getInstance().generatePKIKeypair(); + //PublicKey publicKey = (PublicKey) keypair[0]; + //PrivateKey privateKey = (PrivateKey) keypair[1]; + // repeating pattern to be used in pubkey and padding, so the + // destination will be compressible + byte[] rand = new byte[PADDING_ENTROPY]; + RandomSource.getInstance().nextBytes(rand); + byte[] pk = new byte[PublicKey.KEYSIZE_BYTES]; + for (int i = 0; i < pk.length; i += PADDING_ENTROPY) { + System.arraycopy(rand, 0, pk, i, Math.min(PADDING_ENTROPY, pk.length - i)); + } + PublicKey publicKey = new PublicKey(pk); + // Unused private key. + // Could be all zeros, but make it random so SAM doesn't show a string of AAAA + byte[] prk = new byte[PrivateKey.KEYSIZE_BYTES]; + RandomSource.getInstance().nextBytes(prk); + PrivateKey privateKey = new PrivateKey(prk); + SimpleDataStructure signingKeys[]; if (cert.getCertificateType() == Certificate.CERTIFICATE_TYPE_KEY) { KeyCertificate kcert = cert.toKeyCertificate(); @@ -118,8 +136,12 @@ public class I2PClientImpl implements I2PClient { SigType type = kcert.getSigType(); int len = type.getPubkeyLen(); if (len < 128) { - byte[] pad = new byte[128 - len]; - RandomSource.getInstance().nextBytes(pad); + int padLen = 128 - len; + byte[] pad = new byte[padLen]; + // pad with the same pattern as in the public key + for (int i = 0; i < padLen; i += PADDING_ENTROPY) { + System.arraycopy(rand, 0, pad, i, Math.min(PADDING_ENTROPY, padLen - i)); + } d.setPadding(pad); } else if (len > 128) { System.arraycopy(signingPubKey.getData(), 128, kcert.getPayload(), KeyCertificate.HEADER_LENGTH, len - 128); diff --git a/core/java/src/net/i2p/data/KeysAndCert.java b/core/java/src/net/i2p/data/KeysAndCert.java index e4eba9dc6..0a09eb929 100644 --- a/core/java/src/net/i2p/data/KeysAndCert.java +++ b/core/java/src/net/i2p/data/KeysAndCert.java @@ -85,6 +85,10 @@ public class KeysAndCert extends DataStructureImpl { return EncType.ELGAMAL_2048; } + /** + * Valid for RouterIdentities. May contain random padding for Destinations. + * @since 0.9.42 + */ public PublicKey getPublicKey() { return _publicKey; } @@ -209,7 +213,11 @@ public class KeysAndCert extends DataStructureImpl { buf.append('[').append(getClass().getSimpleName()).append(": "); buf.append("\n\tHash: ").append(getHash().toBase64()); buf.append("\n\tCertificate: ").append(_certificate); - buf.append("\n\tPublicKey: ").append(_publicKey); + if ((_publicKey != null && _publicKey.getType() != EncType.ELGAMAL_2048) || + !(this instanceof Destination)) { + // router identities only + buf.append("\n\tPublicKey: ").append(_publicKey); + } buf.append("\n\tSigningPublicKey: ").append(_signingKey); if (_padding != null) buf.append("\n\tPadding: ").append(_padding.length).append(" bytes"); diff --git a/core/java/src/net/i2p/data/PrivateKeyFile.java b/core/java/src/net/i2p/data/PrivateKeyFile.java index 9a18542bf..f8f0a3e36 100644 --- a/core/java/src/net/i2p/data/PrivateKeyFile.java +++ b/core/java/src/net/i2p/data/PrivateKeyFile.java @@ -49,12 +49,13 @@ import net.i2p.util.SecureFileOutputStream; * The format is: *
  *  - Destination (387 bytes if no certificate, otherwise longer)
- *     - Public key (256 bytes)
+ *     - Public key (256 bytes), random data as of 0.9.57 (except for RouterPrivateKeyFile)
  *     - Signing Public key (128 bytes)
  *     - Cert. type (1 byte)
  *     - Cert. length (2 bytes)
  *     - Certificate if length != 0
  *  - Private key (256 bytes for ElGamal, or length specified by key certificate)
+ *     -          All zeros as of 0.9.57 (except for RouterPrivateKeyFile)
  *  - Signing Private key (20 bytes, or length specified by key certificate)
  *  - As of 0.9.38, if the Signing Private Key is all zeros,
  *    the offline signature section (see proposal 123):
@@ -67,6 +68,13 @@ import net.i2p.util.SecureFileOutputStream;
  * Total: 663 or more bytes for ElGamal, may be smaller for other enc. types
  *
* + * Destination encryption keys have been unused since 0.6 (2005). + * As of 0.9.57, new Destination encryption public keys are simply random data, + * and encryption private keys may be random data or all zeros. + * + * This class is extended by net.i2p.data.router.RouterPrivateKeyFile. + * RouterIdentity encryption keys ARE used and must be valid. + * * @author welterde, zzz */ @@ -84,6 +92,8 @@ public class PrivateKeyFile { private SigningPrivateKey _transientSigningPrivKey; private SigningPublicKey _transientSigningPubKey; + private static final int PADDING_ENTROPY = 32; + /** * Create a new PrivateKeyFile, or modify an existing one, with various * types of Certificates. @@ -591,9 +601,25 @@ public class PrivateKeyFile { // no support for this in I2PClient, // so we modify code from CreateRouterInfoJob.createRouterInfo() I2PAppContext ctx = I2PAppContext.getGlobalContext(); - KeyPair keypair = ctx.keyGenerator().generatePKIKeys(ptype); - PublicKey pub = keypair.getPublic(); - PrivateKey priv = keypair.getPrivate(); + byte[] rand = new byte[PADDING_ENTROPY]; + ctx.random().nextBytes(rand); + PublicKey pub; + PrivateKey priv; + if (getClass().equals(PrivateKeyFile.class)) { + // destinations don't use the encryption key + byte[] bpub = new byte[ptype.getPubkeyLen()]; + for (int i = 0; i < bpub.length; i += PADDING_ENTROPY) { + System.arraycopy(rand, 0, bpub, i, Math.min(PADDING_ENTROPY, bpub.length - i)); + } + pub = new PublicKey(ptype, bpub); + byte[] bpriv = new byte[ptype.getPrivkeyLen()]; + priv = new PrivateKey(ptype, bpriv); + } else { + // routers use the encryption key + KeyPair keypair = ctx.keyGenerator().generatePKIKeys(ptype); + pub = keypair.getPublic(); + priv = keypair.getPrivate(); + } SimpleDataStructure signingKeypair[] = ctx.keyGenerator().generateSigningKeys(type); SigningPublicKey spub = (SigningPublicKey)signingKeypair[0]; SigningPrivateKey spriv = (SigningPrivateKey)signingKeypair[1]; @@ -611,7 +637,9 @@ public class PrivateKeyFile { (PublicKey.KEYSIZE_BYTES - pub.length()); if (padLen > 0) { padding = new byte[padLen]; - ctx.random().nextBytes(padding); + for (int i = 0; i < padLen; i += PADDING_ENTROPY) { + System.arraycopy(rand, 0, padding, i, Math.min(PADDING_ENTROPY, padLen - i)); + } } else { padding = null; } @@ -793,6 +821,8 @@ public class PrivateKeyFile { } /** + * Private key may be random data or all zeros for Destinations as of 0.9.57 + * * @return null on error or if not initialized */ public PrivateKey getPrivKey() { @@ -941,6 +971,8 @@ public class PrivateKeyFile { * Verify that the PublicKey matches the PrivateKey, and * the SigningPublicKey matches the SigningPrivateKey. * + * NOTE this will fail for Destinations containing random padding for the enc. key + * * @return success * @since 0.9.16 */ @@ -956,12 +988,14 @@ public class PrivateKeyFile { @Override public String toString() { - StringBuilder s = new StringBuilder(128); - s.append("Destination: "); + StringBuilder s = new StringBuilder(1024); + boolean isRI = !getClass().equals(PrivateKeyFile.class) || + (privKey != null && privKey.getType() != EncType.ELGAMAL_2048); + s.append(isRI ? "RouterIdentity: " : "Destination: "); s.append(this.dest != null ? this.dest.toBase64() : "null"); s.append("\nB32 : "); s.append(this.dest != null ? this.dest.toBase32() : "null"); - if (dest != null) { + if (!isRI && dest != null) { SigningPublicKey spk = dest.getSigningPublicKey(); SigType type = spk.getType(); if (type == SigType.EdDSA_SHA512_Ed25519 || @@ -974,10 +1008,12 @@ public class PrivateKeyFile { } s.append("\nContains: "); s.append(this.dest); - s.append("\nPrivate Key: "); - s.append(this.privKey); + if (isRI) { + s.append("\nPrivate Key: "); + s.append(this.privKey); + } s.append("\nSigining Private Key: "); - if (isOffline()) { + if (!isRI && isOffline()) { s.append("offline\nOffline Signature Expires: "); s.append(new Date(getOfflineExpiration())); s.append("\nTransient Signing Public Key: "); diff --git a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java index c6892c7db..4bad6deca 100644 --- a/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java +++ b/router/java/src/net/i2p/router/startup/CreateRouterInfoJob.java @@ -61,9 +61,8 @@ public class CreateRouterInfoJob extends JobImpl { /** @since 0.9.48 */ static final String PROP_ROUTER_ENCTYPE = "router.encType"; private static final SigType DEFAULT_SIGTYPE = SigType.EdDSA_SHA512_Ed25519; - private static final EncType DEFAULT_ENCTYPE = (VersionComparator.comp(CoreVersion.VERSION, "0.9.49") >= 0) ? - EncType.ECIES_X25519 : - EncType.ELGAMAL_2048; + private static final EncType DEFAULT_ENCTYPE = EncType.ECIES_X25519; + private static final int PADDING_ENTROPY = 32; CreateRouterInfoJob(RouterContext ctx, Job next) { super(ctx); @@ -129,7 +128,14 @@ public class CreateRouterInfoJob extends JobImpl { (PublicKey.KEYSIZE_BYTES - pubkey.length()); if (padLen > 0) { padding = new byte[padLen]; - ctx.random().nextBytes(padding); + if (padLen <= PADDING_ENTROPY) { + ctx.random().nextBytes(padding); + } else { + ctx.random().nextBytes(padding, 0, PADDING_ENTROPY); + for (int i = PADDING_ENTROPY; i < padLen; i += PADDING_ENTROPY) { + System.arraycopy(padding, 0, padding, i, Math.min(PADDING_ENTROPY, padLen - i)); + } + } ident.setPadding(padding); } else { padding = null;