Complete step 2 of AuthKey generation
This commit is contained in:
parent
80ef876d92
commit
e404594ad5
|
@ -70,7 +70,7 @@ msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDeta
|
||||||
|
|
||||||
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
|
||||||
|
|
||||||
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
|
req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:bytes q:bytes public_key_fingerprint:long encrypted_data:bytes = Server_DH_Params;
|
||||||
|
|
||||||
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;
|
set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:bytes = Set_client_DH_params_answer;
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,6 @@ import io.github.lonamiwebs.overgram.crypto.Authenticator;
|
||||||
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
|
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
|
||||||
import io.github.lonamiwebs.overgram.network.connection.Connection;
|
import io.github.lonamiwebs.overgram.network.connection.Connection;
|
||||||
import io.github.lonamiwebs.overgram.network.connection.TcpFull;
|
import io.github.lonamiwebs.overgram.network.connection.TcpFull;
|
||||||
import io.github.lonamiwebs.overgram.utils.BinaryWriter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,82 @@
|
||||||
package io.github.lonamiwebs.overgram.crypto;
|
package io.github.lonamiwebs.overgram.crypto;
|
||||||
|
|
||||||
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
|
import io.github.lonamiwebs.overgram.network.MTProtoPlainSender;
|
||||||
|
import io.github.lonamiwebs.overgram.tl.Abstract;
|
||||||
import io.github.lonamiwebs.overgram.tl.Functions;
|
import io.github.lonamiwebs.overgram.tl.Functions;
|
||||||
import io.github.lonamiwebs.overgram.tl.TLObject;
|
|
||||||
import io.github.lonamiwebs.overgram.tl.Types;
|
import io.github.lonamiwebs.overgram.tl.Types;
|
||||||
|
import io.github.lonamiwebs.overgram.utils.RSA;
|
||||||
|
import io.github.lonamiwebs.overgram.utils.Utils;
|
||||||
|
import javafx.util.Pair;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.SecureRandom;
|
|
||||||
|
|
||||||
public class Authenticator {
|
public class Authenticator {
|
||||||
|
|
||||||
public static void doAuthentication(final MTProtoPlainSender sender) throws IOException, ClassNotFoundException {
|
public static void doAuthentication(final MTProtoPlainSender sender) throws IOException, ClassNotFoundException {
|
||||||
final byte[] nonceBytes = new byte[16];
|
final BigInteger nonce = new BigInteger(Utils.randomBytes(16));
|
||||||
new SecureRandom().nextBytes(nonceBytes);
|
final Types.ResPQ resPq = sender.send(
|
||||||
final BigInteger nonce = new BigInteger(nonceBytes);
|
new Functions.ReqPqMulti().nonce(nonce));
|
||||||
|
|
||||||
final TLObject result = sender.send(new Functions.ReqPqMulti().nonce(nonce));
|
if (!resPq.nonce().equals(nonce)) {
|
||||||
assert result instanceof Types.ResPQ;
|
throw new SecurityException("Step 1 response nonce invalid");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that Java uses big endian, which is correct here
|
||||||
|
final BigInteger pq = new BigInteger(resPq.pq());
|
||||||
|
final Pair<BigInteger, BigInteger> pqPair = Utils.factorize(pq);
|
||||||
|
final BigInteger p = pqPair.getKey();
|
||||||
|
final BigInteger q = pqPair.getValue();
|
||||||
|
|
||||||
|
// Once again, as big endian
|
||||||
|
final byte[] pBytes = p.toByteArray();
|
||||||
|
final byte[] qBytes = q.toByteArray();
|
||||||
|
|
||||||
|
final BigInteger newNonce = new BigInteger(Utils.randomBytes(32));
|
||||||
|
final byte[] pqInnerData = new Types.PQInnerData()
|
||||||
|
.pq(resPq.pq())
|
||||||
|
.p(pBytes)
|
||||||
|
.q(qBytes)
|
||||||
|
.nonce(resPq.nonce())
|
||||||
|
.serverNonce(resPq.serverNonce())
|
||||||
|
.newNonce(newNonce)
|
||||||
|
.serializeToBytes();
|
||||||
|
|
||||||
|
byte[] cipherText = null;
|
||||||
|
long targetFingerprint = 0;
|
||||||
|
for (final long fingerprint : resPq.serverPublicKeyFingerprints()) {
|
||||||
|
cipherText = RSA.encrypt(fingerprint, pqInnerData);
|
||||||
|
if (cipherText != null) {
|
||||||
|
targetFingerprint = fingerprint;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cipherText == null) {
|
||||||
|
throw new SecurityException("Step 2 could not find a known RSA key");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Abstract.ServerDHParams abstractDhParams = sender.send(
|
||||||
|
new Functions.ReqDHParams()
|
||||||
|
.nonce(resPq.nonce())
|
||||||
|
.serverNonce(resPq.serverNonce())
|
||||||
|
.p(pBytes)
|
||||||
|
.q(qBytes)
|
||||||
|
.publicKeyFingerprint(targetFingerprint)
|
||||||
|
.encryptedData(cipherText)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (abstractDhParams instanceof Types.ServerDHParamsFail) {
|
||||||
|
// We could also check its nonce etc. but not needed
|
||||||
|
throw new SecurityException("Step 2 generation of DH params failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
final Types.ServerDHParamsOk dhParams = (Types.ServerDHParamsOk) abstractDhParams;
|
||||||
|
if (!dhParams.nonce().equals(resPq.nonce())) {
|
||||||
|
throw new SecurityException("Step 2 DH params have invalid nonce");
|
||||||
|
}
|
||||||
|
if (!dhParams.serverNonce().equals(resPq.serverNonce())) {
|
||||||
|
throw new SecurityException("Step 2 DH params have invalid nonce");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,6 @@ import io.github.lonamiwebs.overgram.utils.BinaryWriter;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
public class MTProtoPlainSender {
|
public class MTProtoPlainSender {
|
||||||
private final Connection connection;
|
private final Connection connection;
|
||||||
|
@ -19,7 +18,7 @@ public class MTProtoPlainSender {
|
||||||
this.state = new MTProtoState();
|
this.state = new MTProtoState();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TLObject send(final TLRequest request) throws IOException, ClassNotFoundException {
|
public <T extends TLObject> T send(final TLRequest request) throws IOException, ClassNotFoundException {
|
||||||
final long msgId = state.getNewMsgId();
|
final long msgId = state.getNewMsgId();
|
||||||
|
|
||||||
final BinaryWriter writer = new BinaryWriter();
|
final BinaryWriter writer = new BinaryWriter();
|
||||||
|
@ -42,7 +41,7 @@ public class MTProtoPlainSender {
|
||||||
throw new RuntimeException();
|
throw new RuntimeException();
|
||||||
}
|
}
|
||||||
|
|
||||||
final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body).order(ByteOrder.LITTLE_ENDIAN));
|
final BinaryReader reader = new BinaryReader(ByteBuffer.wrap(body));
|
||||||
final long authKeyId = reader.readLong();
|
final long authKeyId = reader.readLong();
|
||||||
assert authKeyId == 0;
|
assert authKeyId == 0;
|
||||||
|
|
||||||
|
@ -52,6 +51,7 @@ public class MTProtoPlainSender {
|
||||||
final int length = reader.readInt();
|
final int length = reader.readInt();
|
||||||
assert length > 0;
|
assert length > 0;
|
||||||
|
|
||||||
return reader.readTl();
|
//noinspection unchecked
|
||||||
|
return (T) reader.readTl();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,4 +7,10 @@ public abstract class TLObject {
|
||||||
public abstract void serialize(final BinaryWriter writer);
|
public abstract void serialize(final BinaryWriter writer);
|
||||||
|
|
||||||
public abstract void deserialize(final BinaryReader reader) throws ClassNotFoundException;
|
public abstract void deserialize(final BinaryReader reader) throws ClassNotFoundException;
|
||||||
|
|
||||||
|
public byte[] serializeToBytes() {
|
||||||
|
final BinaryWriter writer = new BinaryWriter();
|
||||||
|
serialize(writer);
|
||||||
|
return writer.toBytes();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import io.github.lonamiwebs.overgram.tl.Types;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -13,7 +14,7 @@ public class BinaryReader {
|
||||||
private final ByteBuffer buffer;
|
private final ByteBuffer buffer;
|
||||||
|
|
||||||
public BinaryReader(final ByteBuffer buffer) {
|
public BinaryReader(final ByteBuffer buffer) {
|
||||||
this.buffer = buffer;
|
this.buffer = buffer.order(ByteOrder.LITTLE_ENDIAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readInt() {
|
public int readInt() {
|
||||||
|
@ -47,13 +48,13 @@ public class BinaryReader {
|
||||||
public BigInteger readInt128() {
|
public BigInteger readInt128() {
|
||||||
final byte[] bytes = new byte[16];
|
final byte[] bytes = new byte[16];
|
||||||
buffer.get(bytes);
|
buffer.get(bytes);
|
||||||
return new BigInteger(bytes);
|
return new BigInteger(Utils.reversed(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigInteger readInt256() {
|
public BigInteger readInt256() {
|
||||||
final byte[] bytes = new byte[32];
|
final byte[] bytes = new byte[32];
|
||||||
buffer.get(bytes);
|
buffer.get(bytes);
|
||||||
return new BigInteger(bytes);
|
return new BigInteger(Utils.reversed(bytes));
|
||||||
}
|
}
|
||||||
|
|
||||||
public double readDouble() {
|
public double readDouble() {
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
package io.github.lonamiwebs.overgram.utils;
|
||||||
|
|
||||||
|
import com.sun.xml.internal.messaging.saaj.util.ByteOutputStream;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
|
||||||
|
public class RSA {
|
||||||
|
// TODO Load keys from "-----BEGIN RSA PUBLIC KEY-----" properely
|
||||||
|
|
||||||
|
public final BigInteger n;
|
||||||
|
public final BigInteger e;
|
||||||
|
|
||||||
|
private RSA(final BigInteger n, final BigInteger e) {
|
||||||
|
this.n = n;
|
||||||
|
this.e = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static RSA getKey(final long fingerprint) {
|
||||||
|
// Java can't into switch(long) apparently
|
||||||
|
if (fingerprint == -7306692244673891685L) {
|
||||||
|
return new RSA(new BigInteger("25081407810410225030931722734886059247598515157516470397242545867550116598436968553551465554653745201634977779380884774534457386795922003815072071558370597290368737862981871277312823942822144802509055492512145589734772907225259038113414940384446493111736999668652848440655603157665903721517224934142301456312994547591626081517162758808439979745328030376796953660042629868902013177751703385501412640560275067171555763725421377065095231095517201241069856888933358280729674273422117201596511978645878544308102076746465468955910659145532699238576978901011112475698963666091510778777356966351191806495199073754705289253783"), new BigInteger("65537"));
|
||||||
|
} else if (fingerprint == -5738946642031285640L) {
|
||||||
|
return new RSA(new BigInteger("22347337644621997830323797217583448833849627595286505527328214795712874535417149457567295215523199212899872122674023936713124024124676488204889357563104452250187725437815819680799441376434162907889288526863223004380906766451781702435861040049293189979755757428366240570457372226323943522935844086838355728767565415115131238950994049041950699006558441163206523696546297006014416576123345545601004508537089192869558480948139679182328810531942418921113328804749485349441503927570568778905918696883174575510385552845625481490900659718413892216221539684717773483326240872061786759868040623935592404144262688161923519030977"), new BigInteger("65537"));
|
||||||
|
} else if (fingerprint == -4344800451088585951L) {
|
||||||
|
return new RSA(new BigInteger("24403446649145068056824081744112065346446136066297307473868293895086332508101251964919587745984311372853053253457835208829824428441874946556659953519213382748319518214765985662663680818277989736779506318868003755216402538945900388706898101286548187286716959100102939636333452457308619454821845196109544157601096359148241435922125602449263164512290854366930013825808102403072317738266383237191313714482187326643144603633877219028262697593882410403273959074350849923041765639673335775605842311578109726403165298875058941765362622936097839775380070572921007586266115476975819175319995527916042178582540628652481530373407"), new BigInteger("65537"));
|
||||||
|
} else if (fingerprint == 8205599988028290019L) {
|
||||||
|
return new RSA(new BigInteger("24573455207957565047870011785254215390918912369814947541785386299516827003508659346069416840622922416779652050319196701077275060353178142796963682024347858398319926119639265555410256455471016400261630917813337515247954638555325280392998950756512879748873422896798579889820248358636937659872379948616822902110696986481638776226860777480684653756042166610633513404129518040549077551227082262066602286208338952016035637334787564972991208252928951876463555456715923743181359826124083963758009484867346318483872552977652588089928761806897223231500970500186019991032176060579816348322451864584743414550721639495547636008351"), new BigInteger("65537"));
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encrypt(final long fingerprint, final byte[] data) throws SecurityException {
|
||||||
|
final RSA key = getKey(fingerprint);
|
||||||
|
if (key == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteOutputStream toEncrypt = new ByteOutputStream(256);
|
||||||
|
toEncrypt.write(0); // big endian unsigned big integer needs the sign bit 0
|
||||||
|
toEncrypt.write(Utils.sha1digest(data)); // 20 bytes
|
||||||
|
toEncrypt.write(data); // length
|
||||||
|
toEncrypt.write(Utils.randomBytes(235 - data.length)); // left random padding
|
||||||
|
|
||||||
|
final BigInteger payload = new BigInteger(toEncrypt.getBytes());
|
||||||
|
final BigInteger encrypted = payload.modPow(key.e, key.n);
|
||||||
|
|
||||||
|
return encrypted.toByteArray();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
package io.github.lonamiwebs.overgram.utils;
|
||||||
|
|
||||||
|
import javafx.util.Pair;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
|
public class Utils {
|
||||||
|
|
||||||
|
private static MessageDigest sha1;
|
||||||
|
private static SecureRandom random;
|
||||||
|
|
||||||
|
static {
|
||||||
|
random = new SecureRandom();
|
||||||
|
try {
|
||||||
|
sha1 = MessageDigest.getInstance("SHA-1");
|
||||||
|
} catch (NoSuchAlgorithmException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BigInteger randomBigInteger(final BigInteger max) {
|
||||||
|
BigInteger result;
|
||||||
|
do {
|
||||||
|
result = new BigInteger(max.bitLength(), random);
|
||||||
|
} while (result.compareTo(max) >= 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] randomBytes(final int size) {
|
||||||
|
final byte[] result = new byte[size];
|
||||||
|
random.nextBytes(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that this reverses IN PLACE and returns the same pointer
|
||||||
|
public static byte[] reversed(final byte[] bytes) {
|
||||||
|
for (int i = bytes.length / 2; i-- != 0; ) {
|
||||||
|
final byte temp = bytes[i];
|
||||||
|
bytes[i] = bytes[bytes.length - i - 1];
|
||||||
|
bytes[bytes.length - i - 1] = temp;
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Pair<BigInteger, BigInteger> factorize(final BigInteger pq) {
|
||||||
|
final BigInteger two = new BigInteger("2");
|
||||||
|
|
||||||
|
if (pq.mod(two).equals(BigInteger.ZERO)) {
|
||||||
|
return new Pair<>(two, pq.divide(two));
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger y = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
|
||||||
|
BigInteger c = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
|
||||||
|
BigInteger m = BigInteger.ONE.add(randomBigInteger(pq.subtract(two)));
|
||||||
|
BigInteger g = BigInteger.ONE;
|
||||||
|
BigInteger r = BigInteger.ONE;
|
||||||
|
BigInteger q = BigInteger.ONE;
|
||||||
|
BigInteger x = BigInteger.ZERO;
|
||||||
|
BigInteger ys = BigInteger.ZERO;
|
||||||
|
|
||||||
|
BigInteger countDown;
|
||||||
|
|
||||||
|
while (g.equals(BigInteger.ONE)) {
|
||||||
|
x = y;
|
||||||
|
|
||||||
|
countDown = r;
|
||||||
|
while (!countDown.equals(BigInteger.ZERO)) {
|
||||||
|
countDown = countDown.subtract(BigInteger.ONE);
|
||||||
|
y = (y.modPow(two, pq).add(c)).mod(pq);
|
||||||
|
}
|
||||||
|
|
||||||
|
BigInteger k = BigInteger.ZERO;
|
||||||
|
while (k.compareTo(r) < 0 && g.equals(BigInteger.ONE)) {
|
||||||
|
ys = y;
|
||||||
|
|
||||||
|
countDown = m.min(r.subtract(k));
|
||||||
|
while (!countDown.equals(BigInteger.ZERO)) {
|
||||||
|
countDown = countDown.subtract(BigInteger.ONE);
|
||||||
|
y = y.modPow(two, pq).add(c).mod(pq);
|
||||||
|
q = q.multiply(x.subtract(y).abs()).mod(pq);
|
||||||
|
}
|
||||||
|
|
||||||
|
g = q.gcd(pq);
|
||||||
|
k = k.add(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
r = r.multiply(two);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (g.equals(pq)) {
|
||||||
|
do {
|
||||||
|
ys = ys.modPow(two, pq).add(c).mod(pq);
|
||||||
|
g = x.subtract(ys).abs().gcd(pq);
|
||||||
|
} while (g.compareTo(BigInteger.ONE) <= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
x = g;
|
||||||
|
y = pq.divide(g);
|
||||||
|
return new Pair<>(x.min(y), x.max(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] sha1digest(final byte[] data) throws SecurityException {
|
||||||
|
if (sha1 == null) {
|
||||||
|
throw new SecurityException("SHA-1 is required but not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
final byte[] result = sha1.digest(data);
|
||||||
|
sha1.reset();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue