259 lines
8.5 KiB
Java
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();
|
|
}
|
|
}
|
|
}
|