// ccs_obfuscator/include/xtea.h // // XTEA block cipher for std::vector obfuscation. // Header-only, no dependencies beyond /// // /. // // Reference: David Wheeler & Roger Needham, Cambridge Computer Lab, // "TEA extensions" (1997). Public-domain algorithm; this is a // from-scratch C++ implementation following the published spec. // // XTEA encrypts a 64-bit block (two std::uint32_t words) under a // 128-bit key (four std::uint32_t words). One std::uint64_t == one // XTEA block == one cipher invocation; no padding logic, no // chaining, no IV — the cipher loop is a flat per-element pass // (ECB-mode equivalent in cryptographic terms). // // API: // ccs_obf::xtea::Key key = ccs_obf::xtea::make_key("any string"); // ccs_obf::xtea::encipher_vector(v, key); // in-place // ccs_obf::xtea::decipher_vector(v, key); // in-place inverse // // Reversibility: decipher_vector(encipher_vector(v)) == v // element-for-element, for any v and key. The block primitives // encipher_block / decipher_block satisfy the same property at the // single-block level. This is the round-trip property the // ccs_obfuscator round-trip oracle (algorithm 1+) tests against. // // Threat model: this is OBFUSCATION, not cryptographic security. // XTEA is theoretically broken against differential cryptanalysis // with sufficient chosen plaintext. Use ChaCha20 or AES if you need // real confidentiality. For making compiled grammar binaries harder // to casually inspect or hand-edit, XTEA is more than sufficient. // // Endianness: encipher_block / decipher_block treat the 64-bit // block as a contiguous 8-byte buffer and split it into two // little-endian uint32_t halves via memcpy (avoiding strict- // aliasing pitfalls). Because both encipher and decipher use the // same packing/unpacking, the round-trip property holds regardless // of host byte order. However, the *ciphertext* bytes will differ // between big-endian and little-endian hosts running the same code // — round-trip is portable, but ciphertext exchanged across // differently-endian hosts is not. (The .bfgr format itself is // already native-byte-order per cppcc/CLAUDE.md.) #ifndef CCS_OBFUSCATOR_XTEA_H_ #define CCS_OBFUSCATOR_XTEA_H_ #include #include #include #include #include namespace ccs_obf { namespace xtea { // 128-bit key as four std::uint32_t words. using Key = std::array; // Standard XTEA cycle count. 32 cycles = 64 Feistel half-rounds, // the Wheeler/Needham recommended value. Each loop iteration in // encipher_block does TWO half-rounds (one updating v0, one // updating v1), so 32 iterations == 64 Feistel rounds total. constexpr unsigned int kDefaultRounds = 32; // Golden-ratio constant used by both TEA and XTEA as the // per-cycle key-schedule increment. constexpr std::uint32_t kDelta = 0x9E3779B9u; // Encipher one 64-bit block in place under the given key. inline void encipher_block(std::uint64_t& block, const Key& key, unsigned int rounds = kDefaultRounds) { std::uint32_t v0 = 0; std::uint32_t v1 = 0; std::memcpy(&v0, reinterpret_cast(&block) + 0, 4); std::memcpy(&v1, reinterpret_cast(&block) + 4, 4); std::uint32_t sum = 0; for (unsigned int i = 0; i < rounds; ++i) { v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3u]); sum += kDelta; v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3u]); } std::memcpy(reinterpret_cast(&block) + 0, &v0, 4); std::memcpy(reinterpret_cast(&block) + 4, &v1, 4); } // Decipher one 64-bit block in place. Exact inverse of encipher_block // for the same (block, key, rounds) triple. inline void decipher_block(std::uint64_t& block, const Key& key, unsigned int rounds = kDefaultRounds) { std::uint32_t v0 = 0; std::uint32_t v1 = 0; std::memcpy(&v0, reinterpret_cast(&block) + 0, 4); std::memcpy(&v1, reinterpret_cast(&block) + 4, 4); std::uint32_t sum = kDelta * rounds; for (unsigned int i = 0; i < rounds; ++i) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3u]); sum -= kDelta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3u]); } std::memcpy(reinterpret_cast(&block) + 0, &v0, 4); std::memcpy(reinterpret_cast(&block) + 4, &v1, 4); } // In-place encipher of a std::uint64_t vector. Each element is one // XTEA block, treated independently (ECB-mode equivalent — adequate // for obfuscation; would leak repeated-block structure in a // cryptographic setting). inline void encipher_vector(std::vector& v, const Key& key, unsigned int rounds = kDefaultRounds) { for (auto& block : v) encipher_block(block, key, rounds); } // In-place decipher of a std::uint64_t vector. Exact inverse of // encipher_vector under the same (key, rounds). inline void decipher_vector(std::vector& v, const Key& key, unsigned int rounds = kDefaultRounds) { for (auto& block : v) decipher_block(block, key, rounds); } // Derive a 128-bit Key from an arbitrary-length string seed. Each // byte of the seed XORs into one of 16 key bytes (round-robin), // then the 16 bytes are packed into four std::uint32_t. Deterministic // and side-effect-free. NOT a cryptographic key-derivation function // — fine for obfuscation, do not use to derive cryptographic keys. inline Key make_key(const std::string& seed) { std::uint8_t bytes[16] = {0}; for (std::size_t i = 0; i < seed.size(); ++i) { bytes[i % 16u] ^= static_cast(seed[i]); } Key k{}; std::memcpy(k.data(), bytes, 16); return k; } } // namespace xtea } // namespace ccs_obf #endif // CCS_OBFUSCATOR_XTEA_H_