Skip to content

Commit

Permalink
Add a Hasher class
Browse files Browse the repository at this point in the history
This abstraction, which includes ByteBuf-compatible methods, also fixes the invalid-hash bug from ChunkTile.computeDataHash(), since it's not hashing the whole underlying array, only the written bytes.
  • Loading branch information
Protonull committed Oct 13, 2023
1 parent 3178d69 commit 713218f
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 26 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gjum.minecraft.mapsync.common;

import gjum.minecraft.mapsync.common.data.*;
import gjum.minecraft.mapsync.common.utils.Hasher;
import io.netty.buffer.Unpooled;
import net.minecraft.client.Minecraft;
import net.minecraft.core.BlockPos;
Expand Down Expand Up @@ -31,7 +32,9 @@ public static ChunkTile chunkTileFromLevel(Level level, int cx, int cz) {
// TODO speedup: don't serialize twice (once here, once later when writing to network)
var columnsBuf = Unpooled.buffer();
ChunkTile.writeColumns(columns, columnsBuf);
byte[] dataHash = ChunkTile.computeDataHash(columnsBuf);
final byte[] dataHash = Hasher.sha1()
.update(columnsBuf)
.generateHash();

return new ChunkTile(dimension, cx, cz, timestamp, dataVersion, dataHash, columns);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,6 @@
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public record ChunkTile(
ResourceKey<Level> dimension,
int x, int z,
Expand Down Expand Up @@ -66,16 +63,4 @@ public static ChunkTile fromBuf(ByteBuf buf) {
}
return new ChunkTile(dimension, x, z, timestamp, dataVersion, hash, columns);
}

public static byte[] computeDataHash(ByteBuf columns) {
try {
// SHA-1 is faster than SHA-256, and other algorithms are not required to be implemented in every JVM
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(columns.array());
return md.digest();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return new byte[]{};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import gjum.minecraft.mapsync.common.net.encryption.EncryptionDecoder;
import gjum.minecraft.mapsync.common.net.encryption.EncryptionEncoder;
import gjum.minecraft.mapsync.common.net.packet.*;
import gjum.minecraft.mapsync.common.utils.Hasher;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
Expand Down Expand Up @@ -244,16 +245,12 @@ void setUpEncryption(ChannelHandlerContext ctx, ClientboundEncryptionRequestPack
byte[] sharedSecret = new byte[16];
ThreadLocalRandom.current().nextBytes(sharedSecret);

String shaHex;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(sharedSecret);
digest.update(packet.publicKey.getEncoded());
// note that this is different from minecraft (we get no negative hashes)
shaHex = HexFormat.of().formatHex(digest.digest());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
// note that this is different from minecraft (we get no negative hashes)
final String shaHex = HexFormat.of().formatHex(Hasher.sha1()
.update(sharedSecret)
.update(packet.publicKey.getEncoded())
.generateHash()
);

User session = Minecraft.getInstance().getUser();
Minecraft.getInstance().getMinecraftSessionService().joinServer(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package gjum.minecraft.mapsync.common.utils;

import io.netty.buffer.ByteBuf;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import org.jetbrains.annotations.NotNull;

public final class Hasher {
private final MessageDigest messageDigest;

private Hasher(
final @NotNull MessageDigest messageDigest
) {
this.messageDigest = Objects.requireNonNull(messageDigest);
}

/**
* Updates the digest with a single byte.
*/
public @NotNull Hasher update(
final byte input
) {
this.messageDigest.update((byte) input);
return this;
}

/**
* Updates the digest with an entire byte array.
*/
public @NotNull Hasher update(
final byte @NotNull [] input
) {
this.messageDigest.update((byte[]) input);
return this;
}

/**
* Updates the digest with a byte-array slice, defined by the given offset and length.
*/
public @NotNull Hasher update(
final byte @NotNull [] input,
final int offset,
final int length
) {
this.messageDigest.update((byte[]) input, offset, length);
return this;
}

/**
* Updates the digest with a ByteBuffer slice, using {@link ByteBuffer#position()} as the offset and
* {@link ByteBuffer#remaining()} as the length. If you have been writing to this ByteBuffer, you may wish to
* {@link ByteBuffer#flip()} it first before passing it into this method.
*/
public @NotNull Hasher update(
final @NotNull ByteBuffer input
) {
this.messageDigest.update((ByteBuffer) input);
return this;
}

/**
* Updates the digest with a ByteBuffer slice, defined by the given offset and length.
*/
public @NotNull Hasher update(
final @NotNull ByteBuffer input,
final int offset,
final int length
) {
return update((ByteBuffer) input.slice(offset, length));
}

/**
* Updates the digest with a ByteBuf slice, using {@link ByteBuf#readerIndex()} as the offset and
* {@link ByteBuf#readableBytes()} as the length.
*/
public @NotNull Hasher update(
final @NotNull ByteBuf input
) {
update((ByteBuffer) input.nioBuffer());
return this;
}

/**
* Updates the digest with a ByteBuf slice, defined by the given offset and length.
*/
public @NotNull Hasher update(
final @NotNull ByteBuf input,
final int offset,
final int length
) {
update((ByteBuffer) input.nioBuffer(offset, length));
return this;
}

public byte @NotNull [] generateHash() {
return this.messageDigest.digest();
}

/**
* Since every implementation of Java is required to support SHA-1
* (<a href="https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/MessageDigest.html">source</a>)
* it's a safe bet that the algorithm exists.
*/
public static @NotNull Hasher sha1() {
final MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA-1");
}
catch (final NoSuchAlgorithmException thrown) {
throw new IllegalStateException("This should never happen!", thrown);
}
return new Hasher(messageDigest);
}
}

0 comments on commit 713218f

Please sign in to comment.