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 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 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 rsaStringToKey(String keyBase64, String header, String footer, Crypt.ByteArrayToKeyFunction 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 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(); } } }