[Release] Password Cipher / Scan Codes

12/24/2017 08:03 Spirited#1
Introduction
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();
    }
}
PS: I didn't implement encrypt, but that would just map virtual key codes to the scan codes. Doesn't help to implement that on the server end, unless I'm overlooking something - so decrypt only it is.
12/25/2017 10:18 snipetime#2
Thanks Fang +1
12/27/2017 23:15 pintinho12#3
Sorry for asking, but what are the s, d and l things on
Code:
public void Decrypt(byte[] src, int s, byte[] dst, int d, int l)
?
D:
12/27/2017 23:20 atef201080#4
Quote:
Originally Posted by pintinho12 View Post
Sorry for asking, but what are the s, d and l things on
Code:
public void Decrypt(byte[] src, int s, byte[] dst, int d, int l)
?
D:
s is the src starting offset
d is the destination starting offset
and l is the length
12/28/2017 00:33 Spirited#5
Quote:
Originally Posted by atef201080 View Post
s is the src starting offset
d is the destination starting offset
and l is the length
This is correct. Sorry if that was unclear.
12/28/2017 01:58 pintinho12#6
Quote:
Originally Posted by Spirited View Post
This is correct. Sorry if that was unclear.
I had my bets, but was unsure xD Thank you btw