/**
* This file is part of aion-emu <aion-emu.com>.
*
* aion-emu is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* aion-emu is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with aion-emu. If not, see <http://www.gnu.org/licenses/>.
*/
package com.aionemu.loginserver.network.ncrypt;
//~--- non-JDK imports --------------------------------------------------------
import com.aionemu.commons.utils.Rnd;
/**
* Crypto engine for ecnrypting/decrypting packets, error handling
* and verifying checksum
*
* @author EvilSpirit
*/
public class CryptEngine
{
/**
* A key
*/
private byte[] key =
{
(byte) 0x6b, (byte) 0x60, (byte) 0xcb, (byte) 0x5b, (byte) 0x82, (byte) 0xce, (byte) 0x90, (byte) 0xb1,
(byte) 0xcc, (byte) 0x2b, (byte) 0x6c, (byte) 0x55, (byte) 0x6c, (byte) 0x6c, (byte) 0x6c, (byte) 0x6c
};
/**
* Tells you whether the key is updated or not
*/
private boolean updatedKey = false;
/**
* A secret blowfish cipher
*/
private BlowfishCipher cipher;
/**
* Default constructor. Initialize the Blowfish Cipher with an initial static key
* to encrypt the first packet sent to the client
*/
public CryptEngine()
{
cipher = new BlowfishCipher(key);
}
/**
* Update the key for packet encryption/decryption with the Blowfish Cipher
* @param newKey new Blowfish Key
*/
public void updateKey(byte[] newKey)
{
this.key = newKey;
}
/**
* Decrypt given data
* @param data byte array to be decrypted
* @param offset byte array offset
* @param length byte array length
* @return true, if decrypted packet has valid checksum, false overwise
*/
public boolean decrypt(byte[] data, int offset, int length)
{
cipher.decipher(data, offset, length);
return verifyChecksum(data, offset, length);
}
/**
* Encrypt given data
* @param data byte array to be encrypted
* @param offset byte array offset
* @param length byte array length
* @return length of encrypted byte array
*/
public int encrypt(byte[] data, int offset, int length)
{
length += 4;
// the key is not updated, so the first packet should be encrypted with initial key
if (!updatedKey)
{
length += 4;
length += 8 - length % 8;
encXORPass(data, offset, length, Rnd.nextInt());
cipher.cipher(data, offset, length);
cipher.updateKey(key);
updatedKey = true;
}
else
{
length += 8 - length % 8;
appendChecksum(data, offset, length);
cipher.cipher(data, offset, length);
}
return length;
}
/**
* Verify checksum in a packet
* @param data byte array - encrypted packet
* @param offset byte array offset
* @param length byte array size
* @return true, if checksum is ok, false overwise
*/
private boolean verifyChecksum(byte[] data, int offset, int length)
{
if ((length & 3) != 0 || (length <= 4))
{
return false;
}
long chksum = 0;
int count = length - 4;
long check;
int i;
for (i = offset; i < count; i += 4)
{
check = data[i] & 0xff;
check |= data[i + 1] << 8 & 0xff00;
check |= data[i + 2] << 0x10 & 0xff0000;
check |= data[i + 3] << 0x18 & 0xff000000;
chksum ^= check;
}
check = data[i] & 0xff;
check |= data[i + 1] << 8 & 0xff00;
check |= data[i + 2] << 0x10 & 0xff0000;
check |= data[i + 3] << 0x18 & 0xff000000;
check = data[i] & 0xff;
check |= data[i + 1] << 8 & 0xff00;
check |= data[i + 2] << 0x10 & 0xff0000;
check |= data[i + 3] << 0x18 & 0xff000000;
return 0 == chksum;
}
/**
* add checksum to the end of the packet
* @param raw byte array - encrypted packet
* @param offset byte array offset
* @param length byte array size
*/
private void appendChecksum(byte[] raw, int offset, int length)
{
long chksum = 0;
int count = length - 4;
long ecx;
int i;
for (i = offset; i < count; i += 4)
{
ecx = raw[i] & 0xff;
ecx |= raw[i + 1] << 8 & 0xff00;
ecx |= raw[i + 2] << 0x10 & 0xff0000;
ecx |= raw[i + 3] << 0x18 & 0xff000000;
chksum ^= ecx;
}
ecx = raw[i] & 0xff;
ecx |= raw[i + 1] << 8 & 0xff00;
ecx |= raw[i + 2] << 0x10 & 0xff0000;
ecx |= raw[i + 3] << 0x18 & 0xff000000;
raw[i] = (byte) (chksum & 0xff);
raw[i + 1] = (byte) (chksum >> 0x08 & 0xff);
raw[i + 2] = (byte) (chksum >> 0x10 & 0xff);
raw[i + 3] = (byte) (chksum >> 0x18 & 0xff);
}
/**
* First packet encryption with XOR key (integer - 4 bytes)
* @param data byte array to be encrypted
* @param offset byte array offset
* @param length byte array length
* @param key integer value as key
*/
private void encXORPass(byte[] data, int offset, int length, int key)
{
int stop = length - 8;
int pos = 4 + offset;
int edx;
int ecx = key;
while (pos < stop)
{
edx = (data[pos] & 0xFF);
edx |= (data[pos + 1] & 0xFF) << 8;
edx |= (data[pos + 2] & 0xFF) << 16;
edx |= (data[pos + 3] & 0xFF) << 24;
ecx += edx;
edx ^= ecx;
data[pos++] = (byte) (edx & 0xFF);
data[pos++] = (byte) (edx >> 8 & 0xFF);
data[pos++] = (byte) (edx >> 16 & 0xFF);
data[pos++] = (byte) (edx >> 24 & 0xFF);
}
data[pos++] = (byte) (ecx & 0xFF);
data[pos++] = (byte) (ecx >> 8 & 0xFF);
data[pos++] = (byte) (ecx >> 16 & 0xFF);
data[pos] = (byte) (ecx >> 24 & 0xFF);
}
}