From 38eb7c6acd1f8a2fb2fc179cff18eec8d5eaee66 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 5 Jan 2025 04:15:33 -0700 Subject: [PATCH] Add encryption routines to the server --- lib/consts.dart | 2 +- lib/encryption/aes.dart | 80 ++++++++++++++++++++ lib/encryption/rsa.dart | 151 +++++++++++++++++++++++++++++++++++++ lib/encryption/xxtea.dart | 152 ++++++++++++++++++++++++++++++++++++++ lib/packets/packets.dart | 93 +++++++++++++++++++++-- pubspec.yaml | 7 +- 6 files changed, 472 insertions(+), 13 deletions(-) create mode 100644 lib/encryption/aes.dart create mode 100644 lib/encryption/rsa.dart create mode 100644 lib/encryption/xxtea.dart diff --git a/lib/consts.dart b/lib/consts.dart index d47d699..f02fdd4 100644 --- a/lib/consts.dart +++ b/lib/consts.dart @@ -1,3 +1,3 @@ class Constants { - static const VERSION = "1.2.112524+1156"; + static const VERSION = "1.3.010525+0414"; } diff --git a/lib/encryption/aes.dart b/lib/encryption/aes.dart new file mode 100644 index 0000000..1af5ff3 --- /dev/null +++ b/lib/encryption/aes.dart @@ -0,0 +1,80 @@ +import 'dart:typed_data'; +import 'dart:math'; + +class AES { + final Uint8List _aesKey; + + AES._(this._aesKey); + + static Future generate({int aesKeySize = 256}) async { + final random = Random.secure(); + + // Generate AES Key + final aesKey = Uint8List(aesKeySize ~/ 8); + for (int i = 0; i < aesKey.length; i++) { + aesKey[i] = random.nextInt(256); + } + + return AES._(aesKey); + } + + static AES useKey(Uint8List key) { + AES aes = AES._(key); + + return aes; + } + + Uint8List getKey() { + return Uint8List.fromList(_aesKey); + } + + Uint8List encrypt(Uint8List data) { + return _aesEncrypt(data, _aesKey); + } + + Uint8List decrypt(Uint8List encryptedData) { + return _aesDecrypt(encryptedData, _aesKey); + } + + static Uint8List _aesEncrypt(Uint8List data, Uint8List key) { + final blockSize = 16; + final paddedData = _pad(data, blockSize); + final encrypted = Uint8List(paddedData.length); + + for (int i = 0; i < paddedData.length; i += blockSize) { + for (int j = 0; j < blockSize; j++) { + encrypted[i + j] = paddedData[i + j] ^ key[j % key.length]; + } + } + + return encrypted; + } + + static Uint8List _aesDecrypt(Uint8List data, Uint8List key) { + final blockSize = 16; + final decrypted = Uint8List(data.length); + + for (int i = 0; i < data.length; i += blockSize) { + for (int j = 0; j < blockSize; j++) { + decrypted[i + j] = data[i + j] ^ key[j % key.length]; + } + } + + return _unpad(decrypted); + } + + static Uint8List _pad(Uint8List data, int blockSize) { + final padLength = blockSize - (data.length % blockSize); + final paddedData = Uint8List(data.length + padLength); + paddedData.setAll(0, data); + for (int i = data.length; i < paddedData.length; i++) { + paddedData[i] = padLength; + } + return paddedData; + } + + static Uint8List _unpad(Uint8List data) { + final padLength = data[data.length - 1]; + return Uint8List.sublistView(data, 0, data.length - padLength); + } +} diff --git a/lib/encryption/rsa.dart b/lib/encryption/rsa.dart new file mode 100644 index 0000000..bc73ec0 --- /dev/null +++ b/lib/encryption/rsa.dart @@ -0,0 +1,151 @@ +import 'dart:typed_data'; +import 'dart:math'; + +class RSA { + final RSAPublicKey publicKey; + final RSAPrivateKey privateKey; + + RSA._(this.publicKey, this.privateKey); + + static RSA generate({int keySize = 2048}) { + final random = Random.secure(); + + // Generate RSA Key Pair + final p = _generatePrime(keySize ~/ 2, random); + final q = _generatePrime(keySize ~/ 2, random); + final n = p * q; + final phi = (p - BigInt.one) * (q - BigInt.one); + final e = BigInt.from(65537); // Common public exponent + final d = _modInverse(e, phi); + + final publicKey = RSAPublicKey(n, e); + final privateKey = RSAPrivateKey(n, d); + + return RSA._(publicKey, privateKey); + } + + static RSA fromKeyPair(RSAPrivateKey priv, RSAPublicKey pub) { + RSA rsa = RSA._(pub, priv); + return rsa; + } + + Uint8List encrypt(Uint8List data) { + return publicKey.encrypt(data); + } + + Uint8List decrypt(Uint8List encryptedData) { + return privateKey.decrypt(encryptedData); + } + + Uint8List sign(Uint8List data) { + return privateKey.sign(data); + } + + bool verify(Uint8List data, Uint8List signature) { + return publicKey.verify(data, signature); + } + + static BigInt _generatePrime(int bitLength, Random random) { + while (true) { + final candidate = BigInt.from(random.nextInt(1 << (bitLength - 1)) | 1); + if (_isPrime(candidate)) { + return candidate; + } + } + } + + static bool _isPrime(BigInt n) { + if (n < BigInt.two) return false; + for (BigInt i = BigInt.two; i * i <= n; i += BigInt.one) { + if (n % i == BigInt.zero) return false; + } + return true; + } + + static BigInt _modInverse(BigInt a, BigInt m) { + BigInt m0 = m; + BigInt y = BigInt.zero, x = BigInt.one; + + while (a > BigInt.one) { + BigInt q = a ~/ m; + BigInt t = m; + + m = a % m; + a = t; + t = y; + + y = x - q * y; + x = t; + } + + if (x < BigInt.zero) { + x += m0; + } + + return x; + } +} + +class RSAPublicKey { + final BigInt modulus; + final BigInt exponent; + + RSAPublicKey(this.modulus, this.exponent); + + Uint8List encrypt(Uint8List data) { + final message = BigInt.parse( + data.toList().map((b) => b.toRadixString(16).padLeft(2, '0')).join(), + radix: 16); + final encrypted = message.modPow(exponent, modulus); + return Uint8List.fromList(encrypted + .toRadixString(16) + .padLeft((modulus.bitLength + 7) ~/ 8 * 2, '0') + .codeUnits); + } + + bool verify(Uint8List data, Uint8List signature) { + final signedBigInt = BigInt.parse( + signature + .toList() + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(), + radix: 16); + final decryptedSignature = signedBigInt.modPow(exponent, modulus); + final message = BigInt.parse( + data.toList().map((b) => b.toRadixString(16).padLeft(2, '0')).join(), + radix: 16); + return message == decryptedSignature; + } +} + +class RSAPrivateKey { + final BigInt modulus; + final BigInt exponent; + + RSAPrivateKey(this.modulus, this.exponent); + + Uint8List decrypt(Uint8List encryptedData) { + final encryptedBigInt = BigInt.parse( + encryptedData + .toList() + .map((b) => b.toRadixString(16).padLeft(2, '0')) + .join(), + radix: 16); + final decrypted = encryptedBigInt.modPow(exponent, modulus); + return Uint8List.fromList(decrypted + .toRadixString(16) + .padLeft((modulus.bitLength + 7) ~/ 8 * 2, '0') + .codeUnits); + } + + Uint8List sign(Uint8List data) { + final message = BigInt.parse( + data.toList().map((b) => b.toRadixString(16).padLeft(2, '0')).join(), + radix: 16); + final signed = message.modPow(exponent, modulus); + return Uint8List.fromList(signed + .toRadixString(16) + .padLeft((modulus.bitLength + 7) ~/ 8 * 2, '0') + .codeUnits); + } +} diff --git a/lib/encryption/xxtea.dart b/lib/encryption/xxtea.dart new file mode 100644 index 0000000..eb38fd6 --- /dev/null +++ b/lib/encryption/xxtea.dart @@ -0,0 +1,152 @@ +import 'dart:convert'; +import 'dart:typed_data'; + +class XXTEA { + final Uint8List _key; + + XXTEA._(this._key); + + static Future fromPassword(String password, + {String salt = 'defaultSalt'}) async { + final keyBytes = Uint8List.fromList(utf8.encode(password + salt)); + final key = _fixKeyLength(keyBytes); + return XXTEA._(key); + } + + Uint8List encryptBytes(Uint8List data) { + final paddedData = _padData(data); + return _xxteaEncrypt(paddedData, _key); + } + + Uint8List decryptBytes(Uint8List encryptedData) { + final decryptedData = _xxteaDecrypt(encryptedData, _key); + return _unpadData(decryptedData); + } + + String encryptString(String plaintext) { + final encryptedBytes = + encryptBytes(Uint8List.fromList(utf8.encode(plaintext))); + return base64.encode(encryptedBytes); + } + + String decryptString(String encryptedText) { + final encryptedBytes = base64.decode(encryptedText); + final decryptedBytes = decryptBytes(encryptedBytes); + return utf8.decode(decryptedBytes); + } + + Uint8List signBytes(Uint8List data) { + final signature = _xxteaEncrypt(data, _key); + return signature; + } + + bool verifySignature(Uint8List data, Uint8List signature) { + final expectedSignature = signBytes(data); + return _constantTimeEquals(expectedSignature, signature); + } + + String signString(String data) { + final signature = signBytes(Uint8List.fromList(utf8.encode(data))); + return base64.encode(signature); + } + + bool verifyStringSignature(String data, String signature) { + final dataBytes = Uint8List.fromList(utf8.encode(data)); + final signatureBytes = base64.decode(signature); + return verifySignature(dataBytes, signatureBytes); + } + + static Uint8List _fixKeyLength(Uint8List key) { + final fixedKey = Uint8List(16); + for (int i = 0; i < key.length && i < 16; i++) { + fixedKey[i] = key[i]; + } + return fixedKey; + } + + static Uint8List _padData(Uint8List data) { + final padLength = 4 - (data.length % 4); + final paddedData = Uint8List(data.length + padLength); + paddedData.setAll(0, data); + return paddedData; + } + + static Uint8List _unpadData(Uint8List data) { + int unpaddedLength = data.length; + while (unpaddedLength > 0 && data[unpaddedLength - 1] == 0) { + unpaddedLength--; + } + return Uint8List.sublistView(data, 0, unpaddedLength); + } + + static Uint8List _xxteaEncrypt(Uint8List data, Uint8List key) { + final n = data.length ~/ 4; + final v = Uint32List.view(data.buffer); + final k = Uint32List.view(key.buffer); + int sum = 0; + const delta = 0x9E3779B9; + + for (int i = 0; i < 6 + 52 ~/ n; i++) { + sum = (sum + delta) & 0xFFFFFFFF; + final e = (sum >> 2) & 3; + for (int p = 0; p < n - 1; p++) { + v[p] = (v[p] + _mx(sum, v, k, p, e, n)) & 0xFFFFFFFF; + } + v[n - 1] = (v[n - 1] + _mx(sum, v, k, n - 1, e, n)) & 0xFFFFFFFF; + } + + return Uint8List.view(v.buffer); + } + + static Uint8List _xxteaDecrypt(Uint8List data, Uint8List key) { + final n = data.length ~/ 4; + final v = Uint32List.view(data.buffer); + final k = Uint32List.view(key.buffer); + const delta = 0x9E3779B9; + int sum = (6 + 52 ~/ n) * delta; + + while (sum != 0) { + final e = (sum >> 2) & 3; + for (int p = n - 1; p > 0; p--) { + v[p] = (v[p] - _mx(sum, v, k, p, e, n)) & 0xFFFFFFFF; + } + v[0] = (v[0] - _mx(sum, v, k, 0, e, n)) & 0xFFFFFFFF; + sum = (sum - delta) & 0xFFFFFFFF; + } + + return Uint8List.view(v.buffer); + } + + static int _mx(int sum, Uint32List v, Uint32List k, int p, int e, int n) { + return ((v[(p + 1) % n] ^ v[p]) + (k[p & 3 ^ e] ^ sum)) & 0xFFFFFFFF; + } + + static bool _constantTimeEquals(Uint8List a, Uint8List b) { + if (a.length != b.length) return false; + int result = 0; + for (int i = 0; i < a.length; i++) { + result |= a[i] ^ b[i]; + } + return result == 0; + } +} + +// Example usage: +// +// void main() async { +// final password = 'securePassword'; +// final secure = await XXTEA.fromPassword(password); +// +// final plaintext = 'Hello, secure world!'; +// final encrypted = secure.encryptString(plaintext); +// print('Encrypted: \$encrypted'); +// +// final decrypted = secure.decryptString(encrypted); +// print('Decrypted: \$decrypted'); +// +// final signature = secure.signString(plaintext); +// print('Signature: \$signature'); +// +// final isValid = secure.verifyStringSignature(plaintext, signature); +// print('Signature valid: \$isValid'); +// } diff --git a/lib/packets/packets.dart b/lib/packets/packets.dart index bb11756..26acff1 100644 --- a/lib/packets/packets.dart +++ b/lib/packets/packets.dart @@ -3,7 +3,9 @@ import 'dart:convert'; import 'dart:io'; import 'dart:typed_data'; -import 'package:libac_dart/utils/Hashing.dart'; +import 'package:libac_dart/encryption/aes.dart'; +import 'package:libac_dart/encryption/rsa.dart'; +import 'package:libac_dart/encryption/xxtea.dart'; import '../nbt/NbtIo.dart'; import '../nbt/Stream.dart'; @@ -11,23 +13,67 @@ import '../nbt/Tag.dart'; import '../nbt/impl/CompoundTag.dart'; import '../nbt/impl/StringTag.dart'; +enum EncryptionType { + RSA(value: 3), + AES(value: 2), + XXTEA(value: 1), + NONE(value: 0); + + final int value; + const EncryptionType({required this.value}); + + static EncryptionType valueOf(int val) { + switch (val) { + case 1: + { + return EncryptionType.XXTEA; + } + case 2: + { + return EncryptionType.AES; + } + case 3: + { + return EncryptionType.RSA; + } + default: + { + return EncryptionType.NONE; + } + } + } +} + class PacketServer { static ServerSocket? socket; static bool shouldRestart = true; + static EncryptionType encryptionType = EncryptionType.NONE; + static Uint8List AESKey = Uint8List(0); + static RSAPrivateKey rsaPrivateKey = RSAPrivateKey(BigInt.zero, BigInt.zero); + static RSAPublicKey rsaPublicKey = RSAPublicKey(BigInt.zero, BigInt.zero); + static String PSK = ""; + static String TEA_SALT = "Harbinger 01/05/2025 @ 03:59:17 AM"; + + /// Version of the packets system. Bumped when there is a major change to protocol + static const VERSION = 2; /// Packet Data Format: /// - /// 8 Bytes (Long) - Total expected bytes of packet minus the 8 bytes here. + /// 4 bytes (int) - Version + /// 8 Bytes (Long) - Total expected bytes of packet /// 8 bytes (Long) - Sequence ID + /// 1 byte - Encryption Type /// 8 bytes (Long) - Number of bytes to read - /// - NBT Data + /// - NBT Data / Encrypted NBT Data /// /// Response Format: /// - /// 8 bytes (Long) - Total expected bytes in packet minus the 8 bytes here. + /// 4 Bytes (int) - Version + /// 8 bytes (Long) - Total expected bytes in packet /// 1 byte - Success flag, Zero or 255 currently. + /// 1 byte - Encryption Type /// 8 byes (Long) - Packet Length - /// - NBT Data + /// - NBT Data / Encrypted NBT Data /// static Future start(int port) async { socket = await ServerSocket.bind(InternetAddress.anyIPv4, port); @@ -45,8 +91,10 @@ class PacketServer { layer.writeBytes(data); var oldPos = layer.currentPosition; layer.resetPosition(); + int version = layer.readInt(); + int pktTotalExpected = layer.readLong(); - if (pktTotalExpected + 8 <= layer.length) { + if (pktTotalExpected <= layer.length) { // Allow Processing } else { layer.restorePosition(oldPos); @@ -54,14 +102,30 @@ class PacketServer { } layer.resetPosition(); + layer.readInt(); layer.readLong(); // This is unused outside of the above sanity check. try { + int encryptType = layer.readByte(); + EncryptionType ENCType = EncryptionType.valueOf(encryptType); + int sequenceID = layer.readLong(); print("Sequence ID in request: $sequenceID"); int numBytes = layer.readLong(); List remainingBytes = layer.readBytes(numBytes); + if (ENCType == EncryptionType.AES) { + AES aes = await AES.useKey(AESKey); + remainingBytes = aes.decrypt(Uint8List.fromList(remainingBytes)); + } else if (ENCType == EncryptionType.RSA) { + RSA rsa = await RSA.fromKeyPair(rsaPrivateKey, rsaPublicKey); + remainingBytes = rsa.decrypt(Uint8List.fromList(remainingBytes)); + } else if (ENCType == EncryptionType.XXTEA) { + XXTEA xtea = await XXTEA.fromPassword(PSK, salt: TEA_SALT); + + remainingBytes = + xtea.decryptBytes(Uint8List.fromList(remainingBytes)); + } CompoundTag tag = await NbtIo.readFromStream(Uint8List.fromList(remainingBytes)); @@ -85,13 +149,28 @@ class PacketServer { layer.clear(); layer.writeLong(sequenceID); layer.writeByte(0xFF); // Successful receipt + layer.writeByte(ENCType.value); + + // Encryption Subroutine + if (ENCType == EncryptionType.AES) { + AES aes = AES.useKey(AESKey); + nbtData = aes.encrypt(nbtData); + } else if (ENCType == EncryptionType.RSA) { + RSA rsa = RSA.fromKeyPair(rsaPrivateKey, rsaPublicKey); + nbtData = rsa.encrypt(nbtData); + } else if (ENCType == EncryptionType.XXTEA) { + XXTEA tea = await XXTEA.fromPassword(PSK, salt: TEA_SALT); + nbtData = tea.encryptBytes(nbtData); + } + layer.writeLong(nbtData.lengthInBytes); layer.writeBytes(nbtData); nbtData = layer.bytes; // NOTE: Added a length indicator because SocketServer is apparently... really really dumb in its impl, and has no way to know when all data has been received, so no special event. We just have to check for it based on this initial value. layer.clear(); - layer.writeLong(nbtData.lengthInBytes); + layer.writeInt(VERSION); + layer.writeLong(nbtData.lengthInBytes + layer.currentPosition + 8); layer.writeBytes(nbtData); sock.add(layer.bytes); diff --git a/pubspec.yaml b/pubspec.yaml index 37fef95..888eee9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,9 +1,8 @@ name: libac_dart description: "Aria's Creations code library" -version: 1.2.112524+1156 +version: 1.3.010525+0414 homepage: "https://zontreck.com" - environment: sdk: ^3.4.0 @@ -17,10 +16,8 @@ dev_dependencies: lints: ^3.0.0 test: ^1.24.0 - # following page: https://dart.dev/tools/pub/pubspec - # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg @@ -52,4 +49,4 @@ dev_dependencies: # For details regarding fonts in packages, see # https://flutter.dev/custom-fonts/#from-packages -publish_to: https://git.zontreck.com/api/packages/AriasCreations/pub \ No newline at end of file +publish_to: https://git.zontreck.com/api/packages/AriasCreations/pub