LibAC-dart/lib/encryption/xtea.dart

155 lines
4.2 KiB
Dart

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);
}
}