Build AsminVoicechat / prepare-release (push) Successful in 14s
Build AsminVoicechat / build-and-test (17) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (18) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (19) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (20) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (21) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (22) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (23) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (24) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (25) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (26) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (27-ea) (push) Canceled after 0s
Build AsminVoicechat / build-and-test (16) (push) Canceled after 38s
138 lines
5.2 KiB
Java
138 lines
5.2 KiB
Java
package de.maxhenkel.voicechat.voice.common;
|
|
|
|
import io.netty.buffer.ByteBuf;
|
|
import java.security.InvalidAlgorithmParameterException;
|
|
import java.security.InvalidKeyException;
|
|
import java.security.Key;
|
|
import java.security.NoSuchAlgorithmException;
|
|
import java.security.SecureRandom;
|
|
import java.util.Arrays;
|
|
import javax.crypto.BadPaddingException;
|
|
import javax.crypto.Cipher;
|
|
import javax.crypto.IllegalBlockSizeException;
|
|
import javax.crypto.NoSuchPaddingException;
|
|
import javax.crypto.spec.GCMParameterSpec;
|
|
import javax.crypto.spec.IvParameterSpec;
|
|
import javax.crypto.spec.SecretKeySpec;
|
|
|
|
public class Secret {
|
|
public static final int SECRET_SIZE_BYTES = 16;
|
|
public static final int IV_SIZE_BYTES = 12;
|
|
public static final int TAG_LEN_BITS = 128;
|
|
public static final String CIPHER = "AES/GCM/NoPadding";
|
|
private static final SecureRandom RANDOM = new SecureRandom();
|
|
private final byte[] secret;
|
|
private final SecretKeySpec keySpec;
|
|
private volatile boolean legacyProtocol;
|
|
|
|
protected Secret(byte[] secret) {
|
|
if (secret == null || secret.length != SECRET_SIZE_BYTES) {
|
|
throw new IllegalArgumentException("Voice chat secret must contain exactly 16 bytes");
|
|
}
|
|
this.secret = secret.clone();
|
|
this.keySpec = new SecretKeySpec(this.secret, "AES");
|
|
}
|
|
|
|
public static Secret generateNewRandomSecret() {
|
|
byte[] secret = new byte[SECRET_SIZE_BYTES];
|
|
RANDOM.nextBytes(secret);
|
|
return new Secret(secret);
|
|
}
|
|
|
|
public static Secret fromBytes(byte[] secret) {
|
|
return new Secret(secret);
|
|
}
|
|
|
|
public static Secret fromBytes(ByteBuf buf) {
|
|
byte[] secretBytes = new byte[SECRET_SIZE_BYTES];
|
|
buf.readBytes(secretBytes);
|
|
return Secret.fromBytes(secretBytes);
|
|
}
|
|
|
|
public void toBytes(ByteBuf buf) {
|
|
buf.writeBytes(this.secret);
|
|
}
|
|
|
|
public byte[] getSecret() {
|
|
return this.secret.clone();
|
|
}
|
|
|
|
public SecretKeySpec getKeySpec() {
|
|
return this.keySpec;
|
|
}
|
|
|
|
public void setLegacyProtocol(boolean legacyProtocol) {
|
|
this.legacyProtocol = legacyProtocol;
|
|
}
|
|
|
|
public boolean isLegacyProtocol() {
|
|
return this.legacyProtocol;
|
|
}
|
|
|
|
public static byte[] generateIV() {
|
|
byte[] iv = new byte[IV_SIZE_BYTES];
|
|
RANDOM.nextBytes(iv);
|
|
return iv;
|
|
}
|
|
|
|
private static byte[] generateLegacyIV() {
|
|
byte[] iv = new byte[16];
|
|
RANDOM.nextBytes(iv);
|
|
return iv;
|
|
}
|
|
|
|
public byte[] encrypt(byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException,
|
|
InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
if (this.legacyProtocol) {
|
|
byte[] iv = Secret.generateLegacyIV();
|
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
cipher.init(Cipher.ENCRYPT_MODE, (Key) this.keySpec, new IvParameterSpec(iv));
|
|
byte[] encrypted = cipher.doFinal(data);
|
|
byte[] payload = new byte[iv.length + encrypted.length];
|
|
System.arraycopy(iv, 0, payload, 0, iv.length);
|
|
System.arraycopy(encrypted, 0, payload, iv.length, encrypted.length);
|
|
return payload;
|
|
}
|
|
byte[] iv = Secret.generateIV();
|
|
Cipher cipher = Cipher.getInstance(CIPHER);
|
|
cipher.init(Cipher.ENCRYPT_MODE, (Key) this.keySpec, new GCMParameterSpec(TAG_LEN_BITS, iv));
|
|
byte[] encrypted = cipher.doFinal(data);
|
|
byte[] payload = new byte[iv.length + encrypted.length];
|
|
System.arraycopy(iv, 0, payload, 0, iv.length);
|
|
System.arraycopy(encrypted, 0, payload, iv.length, encrypted.length);
|
|
return payload;
|
|
}
|
|
|
|
public byte[] decrypt(byte[] payload) throws NoSuchPaddingException, NoSuchAlgorithmException,
|
|
InvalidAlgorithmParameterException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
|
|
if (this.legacyProtocol) {
|
|
if (payload.length < 32 || (payload.length - 16) % 16 != 0) {
|
|
throw new IllegalBlockSizeException("Invalid legacy AES-CBC packet size");
|
|
}
|
|
byte[] iv = Arrays.copyOfRange(payload, 0, 16);
|
|
byte[] encrypted = Arrays.copyOfRange(payload, 16, payload.length);
|
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
|
|
cipher.init(Cipher.DECRYPT_MODE, (Key) this.keySpec, new IvParameterSpec(iv));
|
|
return cipher.doFinal(encrypted);
|
|
}
|
|
if (payload.length < IV_SIZE_BYTES + 16) {
|
|
throw new IllegalBlockSizeException("Invalid AES-GCM packet size");
|
|
}
|
|
byte[] iv = Arrays.copyOfRange(payload, 0, IV_SIZE_BYTES);
|
|
byte[] encrypted = Arrays.copyOfRange(payload, IV_SIZE_BYTES, payload.length);
|
|
Cipher cipher = Cipher.getInstance(CIPHER);
|
|
cipher.init(Cipher.DECRYPT_MODE, (Key) this.keySpec, new GCMParameterSpec(TAG_LEN_BITS, iv));
|
|
return cipher.doFinal(encrypted);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
return o instanceof Secret && Arrays.equals(this.secret, ((Secret) o).secret);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode() {
|
|
return Arrays.hashCode(this.secret);
|
|
}
|
|
}
|