Spirited Posted December 12, 2022 Posted December 12, 2022 BackgroundThis implementation was once published to an underground forum for Conquer Online by Lateralus. Though it works for Conquer Online v4217 (Alpha), it also works for the alpha client of Era of Faith. The code below is publicly published with permission (granted way back in 2012).Code public class Legacy { private byte[] _cryptKey1, _cryptKey2, _cryptKey3, _cryptKey4; private CryptCounter _decryptCounter; private CryptCounter _encryptCounter; public bool Exchanged { get; private set; } public Legacy() { Exchanged = false; _decryptCounter = new CryptCounter(); _encryptCounter = new CryptCounter(); CreateKeys(); } public void Decrypt(byte[] Data) { for (int i = 0; i < Data.Length; i++) { Data[i] ^= Exchanged ? (byte)(_cryptKey4[_decryptCounter.Key2] ^ _cryptKey3[_decryptCounter.Key1]) : Data[i] ^= (byte)(_cryptKey2[_decryptCounter.Key2] ^ _cryptKey1[_decryptCounter.Key1]); _decryptCounter.Increment(); } } public unsafe void Decrypt(byte* lpData, int Length) { for (int i = 0; i < Length; i++) { lpData[i] ^= Exchanged ? (byte)(_cryptKey4[_decryptCounter.Key2] ^ _cryptKey3[_decryptCounter.Key1]) : lpData[i] ^= (byte)(_cryptKey2[_decryptCounter.Key2] ^ _cryptKey1[_decryptCounter.Key1]); _decryptCounter.Increment(); } } public void Encrypt(byte[] Data) { for (int i = 0; i < Data.Length; i++) { Data[i] ^= (byte)(_cryptKey1[_encryptCounter.Key1] ^ _cryptKey2[_encryptCounter.Key2]); _encryptCounter.Increment(); } } public unsafe void Encrypt(byte* lpData, int Length) { for (int i = 0; i < Length; i++) { lpData[i] ^= (byte)(_cryptKey1[_encryptCounter.Key1] ^ _cryptKey2[_encryptCounter.Key2]); _encryptCounter.Increment(); } } private void CreateKeys() { _cryptKey1 = new byte[0x100]; _cryptKey2 = new byte[0x100]; byte iKey1 = 0x1F; byte iKey2 = 0x3F; for (int i = 0; i < 0x100; i++) { _cryptKey1[i] = iKey1; _cryptKey2[i] = iKey2; iKey1 = (byte)(((byte)(iKey1 << 5) + 0xFD) * iKey1 + 7); iKey2 = (byte)((iKey2 * 0x7A - 0x31) * iKey2 - 0x1B); } } public void GenerateKeys(uint Token, uint AccountID) { _cryptKey3 = new byte[0x100]; _cryptKey4 = new byte[0x100]; int tmpkey1 = (int)((Token + AccountID) ^ 0x4321 ^ Token); int tmpkey2 = tmpkey1 * tmpkey1; byte[] tmp1 = BitConverter.GetBytes(tmpkey1); byte[] tmp2 = BitConverter.GetBytes(tmpkey2); for (int i = 0; i < 256; i++) { _cryptKey3[i] = (byte)(_cryptKey1[i] ^ tmp1[i % 4]); _cryptKey4[i] = (byte)(_cryptKey2[i] ^ tmp2[i % 4]); } Exchanged = true; } private class CryptCounter { private int m_Counter = 0; public byte Key2 { get { return (byte)(m_Counter >> 8); } } public byte Key1 { get { return (byte)(m_Counter & 0xFF); } } public void Increment() { Interlocked.Increment(ref m_Counter); } } } Quote
abdoumatrix Posted January 20 Posted January 20 namespace Comet.Network.Security { using System; using System.Threading; /// <summary> /// TQ Digital Entertainment's in-house asymmetric counter-based XOR-cipher. Counters /// are separated by encryption direction to create cipher streams. This implementation /// implements both directions for encrypting and decrypting data on the server side. /// </summary> /// <remarks> /// This cipher algorithm does not provide effective security, and does not make use /// of any NP-hard calculations for encryption or key generation. Key derivations are /// susceptible to brute-force or static key attacks. Only implemented for /// interoperability with the pre-existing game client. Do not use, otherwise. /// </remarks> public sealed class TQCipher : ICipher { private const int KEY_SIZE = 0x100; // Local fields and properties private byte[] mCryptKey1; private byte[] mCryptKey2; private byte[] mCryptKey3; private byte[] mCryptKey4; private int mDecryptCounter; private int mEncryptCounter; private bool mExchanged; /// <summary> /// Instantiates a new instance of <see cref="TQCipher"/> with initial IV keys. /// </summary> public TQCipher() { this.mExchanged = false; this.mDecryptCounter = 0; this.mEncryptCounter = 0; CreateKeys(); } /// <summary> /// Generate the initialization vector keys using static seed values. /// </summary> private void CreateKeys() { this.mCryptKey1 = new byte[KEY_SIZE]; this.mCryptKey2 = new byte[KEY_SIZE]; byte iKey1 = 0x1F; byte iKey2 = 0x3F; for (int i = 0; i < KEY_SIZE; i++) { this.mCryptKey1[i] = iKey1; this.mCryptKey2[i] = iKey2; iKey1 = (byte)(((byte)(iKey1 << 5) + 0xFD) * iKey1 + 7); iKey2 = (byte)((iKey2 * 0x7A - 0x31) * iKey2 - 0x1B); } } /// <summary> /// Generates keys for the game server using the player's token and account ID /// as key derivation variables. Invoked after the first packet is received on /// the game server. /// </summary> /// <param name="seeds">Array of seeds for generating keys (expects Token and AccountID as uint)</param> public void GenerateKeys(object[] seeds) { uint token = Convert.ToUInt32(seeds[0]); uint accountId = Convert.ToUInt32(seeds[1]); this.mCryptKey3 = new byte[KEY_SIZE]; this.mCryptKey4 = new byte[KEY_SIZE]; int tmpkey1 = (int)((token + accountId) ^ 0x4321 ^ token); int tmpkey2 = tmpkey1 * tmpkey1; byte[] tmp1 = BitConverter.GetBytes(tmpkey1); byte[] tmp2 = BitConverter.GetBytes(tmpkey2); for (int i = 0; i < KEY_SIZE; i++) { this.mCryptKey3[i] = (byte)(this.mCryptKey1[i] ^ tmp1[i % 4]); this.mCryptKey4[i] = (byte)(this.mCryptKey2[i] ^ tmp2[i % 4]); } this.mExchanged = true; } /// <summary> /// Decrypts the specified span by XORing the source span with the cipher's /// keystream. The source and destination may be the same slice, but otherwise /// should not overlap. /// </summary> /// <param name="src">Source span that requires decrypting</param> /// <param name="dst">Destination span to contain the decrypted result</param> public void Decrypt(Span<byte> src, Span<byte> dst) { for (int i = 0; i < src.Length; i++) { byte key1Index = (byte)(this.mDecryptCounter & 0xFF); byte key2Index = (byte)(this.mDecryptCounter >> 8); if (this.mExchanged) { dst[i] = (byte)(src[i] ^ (this.mCryptKey4[key2Index] ^ this.mCryptKey3[key1Index])); } else { dst[i] = (byte)(src[i] ^ (this.mCryptKey2[key2Index] ^ this.mCryptKey1[key1Index])); } Interlocked.Increment(ref this.mDecryptCounter); } } /// <summary> /// Encrypt the specified span by XORing the source span with the cipher's /// keystream. The source and destination may be the same slice, but otherwise /// should not overlap. /// </summary> /// <param name="src">Source span that requires encrypting</param> /// <param name="dst">Destination span to contain the encrypted result</param> public void Encrypt(Span<byte> src, Span<byte> dst) { for (int i = 0; i < src.Length; i++) { byte key1Index = (byte)(this.mEncryptCounter & 0xFF); byte key2Index = (byte)(this.mEncryptCounter >> 8); dst[i] = (byte)(src[i] ^ (this.mCryptKey1[key1Index] ^ this.mCryptKey2[key2Index])); Interlocked.Increment(ref this.mEncryptCounter); } } /// <summary> /// Reset both encryption and decryption counters. /// </summary> public void ResetCounters() { this.mEncryptCounter = 0; this.mDecryptCounter = 0; } } } namespace Comet.Network.Security { using System; using Comet.Core.Mathematics; /// <summary> /// Rivest Cipher 5 is implemented for interoperability with the Conquer Online game /// client's login procedure. Passwords are encrypted in RC5 by the client, and decrypted /// on the server to be hashed and compared to the database saved password hash. In /// newer clients, this was replaced with SRP-6A (a hash based exchange protocol). /// </summary> /// <remarks> /// RC5 has a 32, 64 or 128-bit block size and a variable key length from /// 1 bit up to 2040 bits. It is a Feistel-like cipher using between 1 to /// 255 rounds. The suggested values are respectively 128, 64 and 12, although /// 12-round RC5 (with 64-bit blocks) is susceptible to a differential attack /// using 2^44 chosen plaintexts. To be still an effective cipher, 18-20 rounds /// are now suggested. /// </remarks> public sealed class RC5 : ICipher { // Constants and static properties private const int KEY_SIZE = 16; // The key size in bytes (0-255, 16 suggested) private const int BLOCK_SIZE = 8; // The block size in bytes (4, 8, or 16, 8 suggested) private const int ROUNDS = 12; // The number of rounds (1-255, 12 suggested) // Magic values private const uint RC5_PW32 = 0xB7E15163; private const uint RC5_QW32 = 0x61C88647; // Internal key sizes for uint32 arrays private const int RC5_SUB = (ROUNDS * 2) + 2; private const int RC5_KEY = KEY_SIZE / 4; // Local fields and properties private readonly uint[] mKey; private readonly uint[] mSub; /// <summary> /// Initializes a new instance of <see cref="RC5"/> to be interoperable with /// the Conquer Online game client. Automatically generates keys using the /// default seed. In later versions of the client, a random buffer is used /// to seed the cipher. /// </summary> public RC5() { this.mKey = new uint[RC5_KEY]; this.mSub = new uint[RC5_SUB]; // Zero-fill arrays for security purposes Array.Clear(this.mKey, 0, this.mKey.Length); Array.Clear(this.mSub, 0, this.mSub.Length); // Generate keys with default seed GenerateKeys(new object[] { new byte[] { 0x3C, 0xDC, 0xFE, 0xE8, 0xC4, 0x54, 0xD6, 0x7E, 0x16, 0xA6, 0xF8, 0x1A, 0xE8, 0xD0, 0x38, 0xBE } }); } /// <summary> /// Generates keys and the subkey words for RC5 using a shared seed, whether /// that seed is shared statically or shared using a method of transport. Though /// only one seed is expected to generate keys, multiple may be used. Seed must be /// divisible by the selected cipher word size (16 bytes in this implementation). /// </summary> /// <param name="seeds">An array of seeds used to generate keys</param> public void GenerateKeys(object[] seeds) { var aSeed = seeds[0] as byte[]; // Copy seed into key array for (int i = 0; i < RC5_KEY; i++) { this.mKey[i] = BitConverter.ToUInt32(aSeed, i * 4); } // Initialize sub-key array this.mSub[0] = RC5_PW32; for (int i = 1; i < RC5_SUB; i++) { this.mSub[i] = this.mSub[i - 1] - RC5_QW32; } // Generate key schedule uint x = 0, y = 0; int iIdx = 0, jIdx = 0; int iterations = 3 * Math.Max(RC5_KEY, RC5_SUB); for (int k = 0; k < iterations; k++) { this.mSub[iIdx] = (this.mSub[iIdx] + x + y).RotateLeft(3); x = this.mSub[iIdx]; iIdx = (iIdx + 1) % RC5_SUB; this.mKey[jIdx] = (this.mKey[jIdx] + x + y).RotateLeft((int)(x + y)); y = this.mKey[jIdx]; jIdx = (jIdx + 1) % RC5_KEY; } } /// <summary> /// Decrypts bytes from the client. The buffer length must be a multiple of /// the BLOCK_SIZE constant (8 bytes). The source and destination may be the /// same slice. /// </summary> /// <param name="src">Source span that requires decrypting</param> /// <param name="dst">Destination span to contain the decrypted result</param> public void Decrypt(Span<byte> src, Span<byte> dst) { int blockCount = src.Length / BLOCK_SIZE; for (int i = 0; i < blockCount; i++) { int offset = i * BLOCK_SIZE; uint ld = BitConverter.ToUInt32(src.Slice(offset, 4)); uint rd = BitConverter.ToUInt32(src.Slice(offset + 4, 4)); // Decrypt rounds for (int j = ROUNDS; j >= 1; j--) { rd = ((rd - this.mSub[2 * j + 1]).RotateRight((int)ld)) ^ ld; ld = ((ld - this.mSub[2 * j]).RotateRight((int)rd)) ^ rd; } uint a = rd - this.mSub[1]; uint b = ld - this.mSub[0]; BitConverter.GetBytes(b).CopyTo(dst.Slice(offset, 4)); BitConverter.GetBytes(a).CopyTo(dst.Slice(offset + 4, 4)); } } /// <summary> /// Encrypts bytes from the server. The buffer length must be a multiple of the /// BLOCK_SIZE constant (8 bytes). The source and destination may be the same slice. /// </summary> /// <param name="src">Source span that requires encrypting</param> /// <param name="dst">Destination span to contain the encrypted result</param> public void Encrypt(Span<byte> src, Span<byte> dst) { int blockCount = src.Length / BLOCK_SIZE; for (int i = 0; i < blockCount; i++) { int offset = i * BLOCK_SIZE; uint a = BitConverter.ToUInt32(src.Slice(offset, 4)); uint b = BitConverter.ToUInt32(src.Slice(offset + 4, 4)); uint le = a + this.mSub[0]; uint re = b + this.mSub[1]; // Encrypt rounds for (int j = 1; j < ROUNDS; j++) { le = ((le ^ re).RotateLeft((int)re)) + this.mSub[2 * j]; re = ((re ^ le).RotateLeft((int)le)) + this.mSub[2 * j + 1]; } BitConverter.GetBytes(le).CopyTo(dst.Slice(offset, 4)); BitConverter.GetBytes(re).CopyTo(dst.Slice(offset + 4, 4)); } } } } work with comet base Quote
Spirited Posted January 21 Author Posted January 21 Ohhh, someone working with EoF? Interesting! Good luck! Quote
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.