After around patch 5174, Conquer Online implemented a pseudo-randomized seed for RC5 using MsgEncryptCode, and some type of substitution cipher for passwords. It bothered me that I knew anything about this "password cipher", besides how horribly programmed the logic was in every public source. I looked into it, and found that the substitutions were from keyboard scan codes to ASCII, and to make it that much more difficult, the keyboard scan codes aren't from a US keyboard.
So, what I did is I made my own scan codes based on input permitted by the client. Definitely not promising that this works for all input, as I've only tested the input I have access to on a US keyboard, but it does allow for the numpad and special characters (unlike current implementations). Use it if you want, and at your own risk.
Code
Code:
/// ScanCodeCipher is a simple substitution cipher that maps client key /// scan codes to ASCII characters based on the user's account name, which /// acts as a randomizer for substitution. The key map was created by me /// just for the Conquer Online client since it doesn't use a traditional /// US keyboard's scan code map. Don't use it for anything else. public class ScanCodeCipher : ICipher { private readonly byte[] _key = new byte[0x200]; private static readonly byte[] _codes = { 0x00, 0x00, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x2D, 0x00, 0x00, 0x00, 0x71, 0x77, 0x65, 0x72, 0x74, 0x79, 0x75, 0x69, 0x6F, 0x70, 0x00, 0x00, 0x00, 0x00, 0x61, 0x73, 0x64, 0x66, 0x67, 0x68, 0x6A, 0x6B, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7A, 0x78, 0x63, 0x76, 0x62, 0x6E, 0x6D, 0x00, 0x2E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x37, 0x38, 0x39, 0x00, 0x34, 0x35, 0x36, 0x00, 0x31, 0x32, 0x33, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29, 0x5F, 0x00, 0x00, 0x00, 0x51, 0x57, 0x45, 0x52, 0x54, 0x59, 0x55, 0x49, 0x4F, 0x50, 0x00, 0x00, 0x00, 0x00, 0x41, 0x53, 0x44, 0x46, 0x47, 0x48, 0x4A, 0x4B, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0x58, 0x43, 0x56, 0x42, 0x4E, 0x4D, 0x00, 0x3E }; /// Constructor using the account checksum as seed. public ScanCodeCipher(string account) { int checksum = 0; foreach (byte c in account) checksum += c; // Pseudo-Randomize using C's rand function. byte[] buf = new byte[16]; for (int i = 0; i < buf.Length; i++) { checksum = checksum * 0x343fd + 0x269ec3; buf[i] = (byte)((checksum >> 0x10) & 0x7fff); } GenerateKeys(buf); } /// Generates key mappings based on the player's account name. public void GenerateKeys(params object[] keys) { var buf = keys[0] as byte[]; for (int i = 1; i < 0x100; i++) { _key[i * 2] = (byte)i; _key[(i * 2) + 1] = (byte)(i ^ buf[i & 15]); } for (int k = 1; k < 0x100; k++) { for (int m = k + 1; m < 0x100; m++) { int a = m * 2, b = k * 2; if (_key[b + 1] < _key[a + 1]) { _key[b] = (byte)(_key[b] ^ _key[a]); _key[a] = (byte)(_key[a] ^ _key[b]); _key[b] = (byte)(_key[b] ^ _key[a]); _key[b + 1] = (byte)(_key[b + 1] ^ _key[a + 1]); _key[a + 1] = (byte)(_key[a + 1] ^ _key[b + 1]); _key[b + 1] = (byte)(_key[b + 1] ^ _key[a + 1]); } } } } /// Decrypt maps the randomized scan codes to ASCII characters. /// Returns the plaintext password, assuming that the RC5 decrypt /// method has been called before this. public void Decrypt(byte[] src, int s, byte[] dst, int d, int l) { for (int i = 0; i < l; i++) { if (src[i + s] == 0) { for (int j = i; j < l; j++) dst[j] = 0; return; } dst[i + d] = _codes[(byte)(_key[src[i + s] * 2])]; } } /// Encrypt is implemented by the client. public void Encrypt(byte[] src, int s, byte[] dst, int d, int l) { throw new System.NotImplementedException(); } }