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






