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 _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 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 _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 encipher(List 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 decipher(List 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 data = utf8.encode(plaintext).toList(); while (data.length % 8 != 0) { data = [...data, 0]; // Zero padding } List encrypted = []; for (int i = 0; i < data.length; i += 8) { List 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 data = base64.decode(ciphertext).toList(); List decrypted = []; for (int i = 0; i < data.length; i += 8) { List 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); } }