Finish implementing some encryption in the network protocols with AES

This commit is contained in:
zontreck 2025-01-06 02:28:54 -07:00
parent 38eb7c6acd
commit 84cef345eb
10 changed files with 263 additions and 391 deletions

5
.gitignore vendored
View file

@ -31,4 +31,7 @@ doc/api/
.idea
*.iml
out
out
test/aesKey.bin
test/HelloWorld.*

View file

@ -1,3 +1,3 @@
class Constants {
static const VERSION = "1.3.010525+0414";
static const VERSION = "1.3.010625+0228";
}

View file

@ -1,80 +1,55 @@
import 'dart:typed_data';
import 'dart:math';
class AES {
import 'package:encrypt/encrypt.dart';
class AESData {
final List<int> iv;
final List<int> data;
AESData({required this.iv, required this.data});
}
class AESCipher {
final Uint8List _aesKey;
AES._(this._aesKey);
AESCipher._(this._aesKey);
static Future<AES> 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 Future<AESCipher> generate({int aesKeySize = 256}) async {
return AESCipher._(Key.fromLength(aesKeySize ~/ 8).bytes);
}
static AES useKey(Uint8List key) {
AES aes = AES._(key);
return aes;
static AESCipher useKey(Uint8List key) {
return AESCipher._(key);
}
Uint8List getKey() {
return Uint8List.fromList(_aesKey);
}
Uint8List encrypt(Uint8List data) {
return _aesEncrypt(data, _aesKey);
Future<AESData> encrypt(Uint8List data) async {
final iv = IV.fromLength(16); // Generate a random 16-byte IV
final encryptedData = await _aesEncrypt(data, _aesKey, iv);
return AESData(iv: iv.bytes.toList(), data: encryptedData.toList());
}
Uint8List decrypt(Uint8List encryptedData) {
return _aesDecrypt(encryptedData, _aesKey);
Future<Uint8List> decrypt(AESData data) async {
final iv = IV(Uint8List.fromList(data.iv));
return _aesDecrypt(Uint8List.fromList(data.data), _aesKey, iv);
}
static Uint8List _aesEncrypt(Uint8List data, Uint8List key) {
final blockSize = 16;
final paddedData = _pad(data, blockSize);
final encrypted = Uint8List(paddedData.length);
static Future<Uint8List> _aesEncrypt(
Uint8List data, Uint8List key, IV iv) async {
var aes = Encrypter(AES(Key(key), mode: AESMode.cbc));
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;
final encrypted = await aes.encryptBytes(data.toList(), iv: iv);
return encrypted.bytes;
}
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);
static Future<Uint8List> _aesDecrypt(
Uint8List data, Uint8List key, IV iv) async {
final aes = Encrypter(AES(Key(key), mode: AESMode.cbc));
final decrypted = await aes.decryptBytes(Encrypted(data), iv: iv);
return Uint8List.fromList(decrypted);
}
}

View file

@ -1,151 +0,0 @@
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);
}
}

155
lib/encryption/xtea.dart Normal file
View file

@ -0,0 +1,155 @@
import 'dart:convert';
import 'package:libac_dart/utils/Hashing.dart';
class XTEA {
static const int XTEA_DELTA = 0x9E3779B9; // (sqrt(5) - 1) * 2^31
static const int xteaNumRounds = 6;
List<int> _xteaKey = [0, 0, 0, 0];
/// Used as part of testsuite to compare provided keys.
///
/// This function will sequentially check each entry.
///
/// Returns: -1 on success, or which entry failed verification.
///
/// On failure it will print to the console what failed
int testKey(List<int> key) {
for (int i = 0; i < key.length; i++) {
int v0 = _xteaKey[i];
int v1 = key[i];
if (v0 != v1) {
print(
"FATAL: TestKey XTEA failed at position ${i}.\nExpected: ${v1}\nActual: ${v0}");
return i;
}
}
return -1;
}
XTEA(String password) {
_xteaKey = _xteaKeyFromString(password);
}
List<int> _xteaKeyFromString(String str) {
// Convert string to MD5 and split into 4 integers
str = md5(str); // Assume md5 function exists
return [
int.parse(str.substring(0, 8), radix: 16),
int.parse(str.substring(8, 16), radix: 16),
int.parse(str.substring(16, 24), radix: 16),
int.parse(str.substring(24, 32), radix: 16)
];
}
List<int> encipher(List<int> data) {
int v0 = data[0];
int v1 = data[1];
int sum = 0;
for (int i = 0; i < xteaNumRounds; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _xteaKey[sum & 3]);
sum += XTEA_DELTA;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _xteaKey[(sum >> 11) & 3]);
}
return [v0 & 0xFFFFFFFF, v1 & 0xFFFFFFFF]; // Ensure 32-bit integers
}
List<int> decipher(List<int> data) {
int v0 = data[0];
int v1 = data[1];
int sum = XTEA_DELTA * xteaNumRounds;
for (int i = 0; i < xteaNumRounds; i++) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + _xteaKey[(sum >> 11) & 3]);
sum -= XTEA_DELTA;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + _xteaKey[sum & 3]);
}
return [v0 & 0xFFFFFFFF, v1 & 0xFFFFFFFF]; // Ensure 32-bit integers
}
String encryptString(String plaintext) {
List<int> data = utf8.encode(plaintext).toList();
while (data.length % 8 != 0) {
data = [...data, 0]; // Zero padding
}
List<int> encrypted = [];
for (int i = 0; i < data.length; i += 8) {
List<int> block = encipher([
(data[i] << 24) |
(data[i + 1] << 16) |
(data[i + 2] << 8) |
data[i + 3],
(data[i + 4] << 24) |
(data[i + 5] << 16) |
(data[i + 6] << 8) |
data[i + 7]
]);
encrypted.addAll([
(block[0] >> 24) & 0xFF,
(block[0] >> 16) & 0xFF,
(block[0] >> 8) & 0xFF,
block[0] & 0xFF,
(block[1] >> 24) & 0xFF,
(block[1] >> 16) & 0xFF,
(block[1] >> 8) & 0xFF,
block[1] & 0xFF
]);
}
return base64.encode(encrypted);
}
String decryptString(String ciphertext) {
List<int> data = base64.decode(ciphertext).toList();
List<int> decrypted = [];
for (int i = 0; i < data.length; i += 8) {
List<int> block = decipher([
(data[i] << 24) |
(data[i + 1] << 16) |
(data[i + 2] << 8) |
data[i + 3],
(data[i + 4] << 24) |
(data[i + 5] << 16) |
(data[i + 6] << 8) |
data[i + 7]
]);
decrypted.addAll([
(block[0] >> 24) & 0xFF,
(block[0] >> 16) & 0xFF,
(block[0] >> 8) & 0xFF,
block[0] & 0xFF,
(block[1] >> 24) & 0xFF,
(block[1] >> 16) & 0xFF,
(block[1] >> 8) & 0xFF,
block[1] & 0xFF
]);
}
// Find the index of the last non-zero byte
int paddingStart = decrypted.lastIndexWhere((byte) => byte != 0);
// If padding was added, remove only trailing zero bytes
if (paddingStart != -1 && paddingStart + 1 < decrypted.length) {
decrypted = decrypted.sublist(0, paddingStart + 1);
}
// Attempt to decode as UTF-8
try {
return utf8.decode(decrypted);
} catch (e) {
throw FormatException('Decrypted data is not valid UTF-8: $e');
}
}
String md5(String input) {
return Hashing.llMD5String(input, 0);
}
}

View file

@ -1,152 +0,0 @@
import 'dart:convert';
import 'dart:typed_data';
class XXTEA {
final Uint8List _key;
XXTEA._(this._key);
static Future<XXTEA> 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');
// }

View file

@ -4,8 +4,7 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:libac_dart/encryption/aes.dart';
import 'package:libac_dart/encryption/rsa.dart';
import 'package:libac_dart/encryption/xxtea.dart';
import 'package:libac_dart/encryption/xtea.dart';
import '../nbt/NbtIo.dart';
import '../nbt/Stream.dart';
@ -14,9 +13,8 @@ import '../nbt/impl/CompoundTag.dart';
import '../nbt/impl/StringTag.dart';
enum EncryptionType {
RSA(value: 3),
AES(value: 2),
XXTEA(value: 1),
XTEA(value: 1),
NONE(value: 0);
final int value;
@ -26,16 +24,12 @@ enum EncryptionType {
switch (val) {
case 1:
{
return EncryptionType.XXTEA;
return EncryptionType.XTEA;
}
case 2:
{
return EncryptionType.AES;
}
case 3:
{
return EncryptionType.RSA;
}
default:
{
return EncryptionType.NONE;
@ -49,8 +43,6 @@ class PacketServer {
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";
@ -115,16 +107,17 @@ class PacketServer {
List<int> 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);
int ivLen = layer.readInt();
List<int> ivBytes = layer.readBytes(ivLen);
AESData encData = AESData(iv: ivBytes, data: remainingBytes);
AESCipher aes = await AESCipher.useKey(AESKey);
remainingBytes = await aes.decrypt(encData);
} else if (ENCType == EncryptionType.XTEA) {
XTEA xtea = await XTEA(PSK);
remainingBytes =
xtea.decryptBytes(Uint8List.fromList(remainingBytes));
xtea.decipher(Uint8List.fromList(remainingBytes));
}
CompoundTag tag =
@ -153,14 +146,15 @@ class PacketServer {
// 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);
AESCipher aes = AESCipher.useKey(AESKey);
var encData = await aes.encrypt(nbtData);
nbtData = Uint8List.fromList(encData.data);
layer.writeInt(encData.iv.length);
layer.writeBytes(encData.iv);
} else if (ENCType == EncryptionType.XTEA) {
XTEA tea = await XTEA(PSK);
nbtData = Uint8List.fromList(tea.encipher(nbtData));
}
layer.writeLong(nbtData.lengthInBytes);

View file

@ -8,6 +8,10 @@ class Hashing {
return bytes2Hash(md5SumStr(input));
}
static String llMD5String(String src, int nonce) {
return md5Hash("${src}:${nonce}");
}
/// This will generate the Sha1 bytes and hash it using #bytes2Hash
static String sha1Hash(String input) {
return bytes2Hash(sha1SumStr(input));

View file

@ -1,6 +1,6 @@
name: libac_dart
description: "Aria's Creations code library"
version: 1.3.010525+0414
version: 1.3.010625+0228
homepage: "https://zontreck.com"
environment:
@ -10,6 +10,7 @@ environment:
dependencies:
crypto: ^3.0.3
dio: ^5.5.0+1
encrypt: ^5.0.3
# path: ^1.8.0
dev_dependencies:

43
test/encryption_test.dart Normal file
View file

@ -0,0 +1,43 @@
import 'dart:convert';
import 'dart:io';
import 'package:libac_dart/encryption/aes.dart';
import 'package:libac_dart/encryption/xtea.dart';
import 'package:libac_dart/utils/Hashing.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
void main() {
test("Test XTEA Encryption", () async {
String knownEncryptedValue =
"MU1T+AuHyBmALhbMOgZJQa5A"; // "Hello World!" // Test Key
String keyHash = "131515d94e2574cd680ab1a41ecdc34c";
List<int> knownKey = [320148953, 1311077581, 1745531300, 516801356];
XTEA tea = await XTEA("Test Key");
expect(-1, tea.testKey(knownKey));
String newValue = tea.encryptString("Hello World!");
expect(Hashing.llMD5String("Test Key", 0), keyHash);
expect(newValue, knownEncryptedValue);
expect(tea.decryptString(newValue), "Hello World!");
});
test("Test AES Implementation", () async {
File key = File("test/aesKey.bin");
File enc = File("test/HelloWorld.enc");
File hw = File("test/HelloWorld.txt");
String helloWorld = "Hello World!";
hw.writeAsStringSync(helloWorld);
AESCipher aes = await AESCipher.generate();
AESData encryptedNew = await aes.encrypt(utf8.encode(helloWorld));
enc.writeAsBytes(encryptedNew.data);
key.writeAsBytes(aes.getKey());
expect(helloWorld, utf8.decode(await aes.decrypt(encryptedNew)));
});
}