minecraft-src/net/minecraft/util/Crypt.java
2025-07-04 01:41:11 +03:00

259 lines
8.5 KiB
Java

package net.minecraft.util;
import com.google.common.primitives.Longs;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import it.unimi.dsi.fastutil.bytes.ByteArrays;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.EncodedKeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Base64.Encoder;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import net.minecraft.network.FriendlyByteBuf;
public class Crypt {
private static final String SYMMETRIC_ALGORITHM = "AES";
private static final int SYMMETRIC_BITS = 128;
private static final String ASYMMETRIC_ALGORITHM = "RSA";
private static final int ASYMMETRIC_BITS = 1024;
private static final String BYTE_ENCODING = "ISO_8859_1";
private static final String HASH_ALGORITHM = "SHA-1";
public static final String SIGNING_ALGORITHM = "SHA256withRSA";
public static final int SIGNATURE_BYTES = 256;
private static final String PEM_RSA_PRIVATE_KEY_HEADER = "-----BEGIN RSA PRIVATE KEY-----";
private static final String PEM_RSA_PRIVATE_KEY_FOOTER = "-----END RSA PRIVATE KEY-----";
public static final String RSA_PUBLIC_KEY_HEADER = "-----BEGIN RSA PUBLIC KEY-----";
private static final String RSA_PUBLIC_KEY_FOOTER = "-----END RSA PUBLIC KEY-----";
public static final String MIME_LINE_SEPARATOR = "\n";
public static final Encoder MIME_ENCODER = Base64.getMimeEncoder(76, "\n".getBytes(StandardCharsets.UTF_8));
public static final Codec<PublicKey> PUBLIC_KEY_CODEC = Codec.STRING.comapFlatMap(string -> {
try {
return DataResult.success(stringToRsaPublicKey(string));
} catch (CryptException var2) {
return DataResult.error(var2::getMessage);
}
}, Crypt::rsaPublicKeyToString);
public static final Codec<PrivateKey> PRIVATE_KEY_CODEC = Codec.STRING.comapFlatMap(string -> {
try {
return DataResult.success(stringToPemRsaPrivateKey(string));
} catch (CryptException var2) {
return DataResult.error(var2::getMessage);
}
}, Crypt::pemRsaPrivateKeyToString);
/**
* Generate a new shared secret AES key from a secure random source
*/
public static SecretKey generateSecretKey() throws CryptException {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(128);
return keyGenerator.generateKey();
} catch (Exception var1) {
throw new CryptException(var1);
}
}
/**
* Generates RSA KeyPair
*/
public static KeyPair generateKeyPair() throws CryptException {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(1024);
return keyPairGenerator.generateKeyPair();
} catch (Exception var1) {
throw new CryptException(var1);
}
}
/**
* Compute a serverId hash for use by sendSessionRequest()
*/
public static byte[] digestData(String serverId, PublicKey publicKey, SecretKey secretKey) throws CryptException {
try {
return digestData(serverId.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
} catch (Exception var4) {
throw new CryptException(var4);
}
}
private static byte[] digestData(byte[]... data) throws Exception {
MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
for (byte[] bs : data) {
messageDigest.update(bs);
}
return messageDigest.digest();
}
private static <T extends Key> T rsaStringToKey(String keyBase64, String header, String footer, Crypt.ByteArrayToKeyFunction<T> keyFunction) throws CryptException {
int i = keyBase64.indexOf(header);
if (i != -1) {
i += header.length();
int j = keyBase64.indexOf(footer, i);
keyBase64 = keyBase64.substring(i, j + 1);
}
try {
return keyFunction.apply(Base64.getMimeDecoder().decode(keyBase64));
} catch (IllegalArgumentException var6) {
throw new CryptException(var6);
}
}
public static PrivateKey stringToPemRsaPrivateKey(String keyBase64) throws CryptException {
return rsaStringToKey(keyBase64, "-----BEGIN RSA PRIVATE KEY-----", "-----END RSA PRIVATE KEY-----", Crypt::byteToPrivateKey);
}
public static PublicKey stringToRsaPublicKey(String keyBase64) throws CryptException {
return rsaStringToKey(keyBase64, "-----BEGIN RSA PUBLIC KEY-----", "-----END RSA PUBLIC KEY-----", Crypt::byteToPublicKey);
}
public static String rsaPublicKeyToString(PublicKey key) {
if (!"RSA".equals(key.getAlgorithm())) {
throw new IllegalArgumentException("Public key must be RSA");
} else {
return "-----BEGIN RSA PUBLIC KEY-----\n" + MIME_ENCODER.encodeToString(key.getEncoded()) + "\n-----END RSA PUBLIC KEY-----\n";
}
}
public static String pemRsaPrivateKeyToString(PrivateKey key) {
if (!"RSA".equals(key.getAlgorithm())) {
throw new IllegalArgumentException("Private key must be RSA");
} else {
return "-----BEGIN RSA PRIVATE KEY-----\n" + MIME_ENCODER.encodeToString(key.getEncoded()) + "\n-----END RSA PRIVATE KEY-----\n";
}
}
private static PrivateKey byteToPrivateKey(byte[] keyBytes) throws CryptException {
try {
EncodedKeySpec encodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(encodedKeySpec);
} catch (Exception var3) {
throw new CryptException(var3);
}
}
/**
* Create a new PublicKey from encoded X.509 data
*/
public static PublicKey byteToPublicKey(byte[] encodedKey) throws CryptException {
try {
EncodedKeySpec encodedKeySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(encodedKeySpec);
} catch (Exception var3) {
throw new CryptException(var3);
}
}
/**
* Decrypt shared secret AES key using RSA private key
*/
public static SecretKey decryptByteToSecretKey(PrivateKey key, byte[] secretKeyEncrypted) throws CryptException {
byte[] bs = decryptUsingKey(key, secretKeyEncrypted);
try {
return new SecretKeySpec(bs, "AES");
} catch (Exception var4) {
throw new CryptException(var4);
}
}
/**
* Encrypt byte[] data with RSA public key
*/
public static byte[] encryptUsingKey(Key key, byte[] data) throws CryptException {
return cipherData(1, key, data);
}
/**
* Decrypt byte[] data with RSA private key
*/
public static byte[] decryptUsingKey(Key key, byte[] data) throws CryptException {
return cipherData(2, key, data);
}
/**
* Encrypt or decrypt byte[] data using the specified key
*/
private static byte[] cipherData(int opMode, Key key, byte[] data) throws CryptException {
try {
return setupCipher(opMode, key.getAlgorithm(), key).doFinal(data);
} catch (Exception var4) {
throw new CryptException(var4);
}
}
/**
* Creates the Cipher Instance.
*/
private static Cipher setupCipher(int opMode, String transformation, Key key) throws Exception {
Cipher cipher = Cipher.getInstance(transformation);
cipher.init(opMode, key);
return cipher;
}
/**
* Creates a Cipher instance using the AES/CFB8/NoPadding algorithm. Used for protocol encryption.
*/
public static Cipher getCipher(int opMode, Key key) throws CryptException {
try {
Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
cipher.init(opMode, key, new IvParameterSpec(key.getEncoded()));
return cipher;
} catch (Exception var3) {
throw new CryptException(var3);
}
}
interface ByteArrayToKeyFunction<T extends Key> {
T apply(byte[] bs) throws CryptException;
}
public record SaltSignaturePair(long salt, byte[] signature) {
public static final Crypt.SaltSignaturePair EMPTY = new Crypt.SaltSignaturePair(0L, ByteArrays.EMPTY_ARRAY);
public SaltSignaturePair(FriendlyByteBuf buffer) {
this(buffer.readLong(), buffer.readByteArray());
}
public boolean isValid() {
return this.signature.length > 0;
}
public static void write(FriendlyByteBuf buffer, Crypt.SaltSignaturePair signaturePair) {
buffer.writeLong(signaturePair.salt);
buffer.writeByteArray(signaturePair.signature);
}
public byte[] saltAsBytes() {
return Longs.toByteArray(this.salt);
}
}
public static class SaltSupplier {
private static final SecureRandom secureRandom = new SecureRandom();
public static long getLong() {
return secureRandom.nextLong();
}
}
}