[EU, Client] Cryptography

12/04/2015 17:19 Cryless~#1
Code:
----------------------
.h
-----------------------

#pragma once
#ifndef __CRYPTO_H
#define __CRYPTO_H
#include <string>
#include <vector>

class Crypto
{
public:
	std::vector<unsigned char> encryptGamePacket(const std::string& buf, int session, bool is_session_packet = 0) const;
	std::vector<unsigned char> encryptLoginPacket(const std::string& buf) const;
	std::string decryptLoginPacket(const std::vector<unsigned char>& buf, std::size_t len) const;
	std::vector<std::string> decryptGamePacket(const std::vector<unsigned char>& buf, std::size_t len) const;
	std::string encryptPasswordString(const std::string& password) const;
	std::string createLoginHash(const std::string& user) const;
	std::string createLoginVersion(void) const;
	int randomNumber(int min, int max) const;
private:
	void completeGamePacketEncrypt(std::vector<unsigned char>& buf, int session, bool is_session_packet = 0) const;
};

namespace CryptoVar
{
	extern std::string dxhash;
	extern std::string glhash;
	extern std::string version;
}

#endif

----------------------
.cpp
-----------------------

#include "Crypto.h"

#include <algorithm>
#include <sstream>
#include <random>

#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include <md5.h>
#include <hex.h>
#pragma comment(lib, "cryptlib.lib")


namespace CryptoVar
{
	std::string dxhash;
	std::string glhash;
	std::string version;
}

std::string Crypto::decryptLoginPacket(const std::vector<unsigned char>& buf, std::size_t len) const
{
	std::string output;
	std::transform(buf.begin(), buf.begin() + len, std::back_inserter(output), 
		[](unsigned char c)
		{
			return c - 0xF;
		}
	);
	return output;
}

std::vector<unsigned char> Crypto::encryptLoginPacket(const std::string& buf) const
{
	std::vector<unsigned char> output;
	std::transform(buf.begin(), buf.end(), std::back_inserter(output), 
		[](char c)
		{
			return (c ^ 0xC3) + 0xF;
		}
	);
	return output;
}

std::string Crypto::encryptPasswordString(const std::string& password) const
{
	static unsigned char table[] = {0x2E, 0x2A, 0x17, 0x4F, 0x20, 0x24, 0x47, 0x11, 0x5B, 0x37,
		0x53, 0x43, 0x15, 0x34, 0x45, 0x25, 0x4B, 0x1D, 0x2F, 0x58, 0x2B, 0x32, 0x63};

	std::stringstream output;
	std::size_t random_start_pos = randomNumber(0, 22);
	std::string password_hex_string;
	CryptoPP::StringSource(password, 
		true, new CryptoPP::HexEncoder(new CryptoPP::StringSink(password_hex_string)));

	output << std::uppercase << std::hex << int(table[random_start_pos]);
	
	unsigned char first = 0, second = 0;
	for(std::size_t i = 0; i < password_hex_string.length(); ++i, ++i, ++random_start_pos)
	{
		second = table[random_start_pos] & 0xF;
		first = (table[random_start_pos] & 0xF0) >> 4;

		output << std::uppercase << std::hex << int(first);
		output << std::uppercase << password_hex_string.at(i);
		output << std::uppercase << std::hex << int(second);
		output << std::uppercase << password_hex_string.at(i+1);
		if(random_start_pos == 22)
			random_start_pos = -1;
	}

	return output.str();
}

std::string Crypto::createLoginHash(const std::string& user) const
{
	CryptoPP::Weak1::MD5 hash;
	std::string output;
	std::string login_string_to_hash = CryptoVar::dxhash;
	login_string_to_hash += CryptoVar::glhash;
	login_string_to_hash += user;

	CryptoPP::StringSource(login_string_to_hash,
		true, new CryptoPP::HashFilter(hash, new CryptoPP::HexEncoder(new CryptoPP::StringSink(output))));
	return output;
}

std::string Crypto::createLoginVersion(void) const
{
	std::string test2 = CryptoVar::dxhash;
	std::string test = CryptoVar::version;
	std::stringstream output;
	output << "00" << std::uppercase << std::hex << int(randomNumber(0, 126))
		<< std::uppercase << std::hex << int(randomNumber(0, 126))
		<< std::uppercase << std::hex << int(randomNumber(0, 126))
		<< '\v' << CryptoVar::version;
	return output.str();
}

int Crypto::randomNumber(int min, int max) const
{
	std::random_device seeder;
	std::mt19937 engine(seeder());
	std::uniform_int_distribution<int> generator(min, max);
	return generator(engine);
}

std::vector<std::string> Crypto::decryptGamePacket(const std::vector<unsigned char>& buf, std::size_t len) const
{
	std::vector<std::string> output;
	std::string current_packet;
	static const char keys[] = {' ','-','.','0','1','2','3','4','5','6','7','8','9','n'};
	std::size_t index = 0;
	unsigned char currentByte = 0, length = 0, first = 0, second = 0;

	while(index < len)
	{
		currentByte = buf[index];
		++index;
		if(currentByte == 0xFF)
		{
			output.push_back(current_packet);
			current_packet.clear();
			continue;
		}

		length = currentByte & 0x7F;
		if(currentByte & 0x80)
		{
			while(length)
			{
				if(index <= len)
				{
					currentByte = buf[index];
					++index;

					first = keys[((currentByte & 0xF0u) >> 4) -1];
					if(first != 0x6E)
						current_packet += first;
					
					if(length <= 1)
						break;

					second = keys[(currentByte & 0xF) -1];
					if(second != 0x6E)
						current_packet += second;
					
					length -= 2;
				} else
				{
					--length;
				}
			}
		} else
		{
			while(length)
			{
				if(index <= len)
				{
					current_packet += buf[index] ^ 0xFF;
					++index;
				} 

				--length;
			}
		}
	}
	
	return output;
}

std::vector<unsigned char> Crypto::encryptGamePacket(const std::string& buf, int session, bool is_session_packet) const
{
	std::size_t packet_length = buf.size();
	std::string packet_mask;
	std::transform(buf.begin(), buf.end(), std::back_inserter(packet_mask),
		[](char c)
	{
		if(c == '#')
			return '0';

		if(!(c -= 0x20) || (c += 0xF1) < 0 || (c -= 0xB) < 0 || !(c -= 0xC5)) 
			return '1';
		return '0';
	}
	);

	std::vector<unsigned char> output;
	std::size_t sequences = 0, sequence_counter = 0;
	std::size_t last_position = 0, current_position = 0, length = 0;
	unsigned char current_byte = 0;
	while(current_position <= packet_length)
	{
		last_position = current_position;
		while(current_position < packet_length && packet_mask[current_position] == '0')
			++current_position;

		if(current_position)
		{
			length = (current_position - last_position);
			sequences = (length / 0x7E);
			for(std::size_t i = 0; i < length; ++i, ++last_position)
			{
				if(i == (sequence_counter * 0x7E))
				{
					if(!sequences)
					{
						output.push_back(length - i);
					} else
					{
						output.push_back(0x7E);
						--sequences;
						++sequence_counter;
					}
				}

				output.push_back(buf[last_position] ^ 0xFF);
			}
		}

		if(current_position >= packet_length)
			break;

		last_position = current_position;
		while(current_position < packet_length && packet_mask[current_position] == '1')
			++current_position;

		if(current_position)
		{
			length = (current_position - last_position);
			sequences = (length / 0x7E);
			for(std::size_t i = 0; i < length; ++i, ++last_position)
			{
				if(i == (sequence_counter * 0x7E))
				{
					if(!sequences)
					{
						output.push_back((length - i) | 0x80);
					} else
					{
						output.push_back(0x7E | 0x80);
						--sequences;
						++sequence_counter;
					}
				}

				current_byte = buf[last_position]; 
				switch(current_byte)
				{
				case 0x20:
					current_byte = 1;
					break;
				case 0x2D:
					current_byte = 2;
					break;
				case 0x2E:
					current_byte = 3;
					break;
				case 0xFF:
					current_byte = 0xE;
					break;
				default:
					current_byte -= 0x2C;
					break;
				}

				if(current_byte != 0x00)
				{
					if(i % 2 == 0)
					{
						output.push_back(current_byte << 4);
					} else 
					{
						output.back() |= current_byte; 
					}
				}
			}
		}
	}

	output.push_back(0xFF);
	completeGamePacketEncrypt(output, session, is_session_packet);
	return output;
}

void Crypto::completeGamePacketEncrypt(std::vector<unsigned char>& buf, int session, bool is_session_packet) const
{
	unsigned char session_number = (((session >> 6) & 0xFF) & 0x80000003);
	
	if(session_number < 0)
		session_number = (((session_number - 1) | 0xFFFFFFFC) + 1);

	unsigned char session_key = (session & 0xFF);

	if(is_session_packet)
		session_number = -1;

	switch (session_number)
	{
	case 0:
		for(std::size_t i = 0; i < buf.size(); ++i)
			buf[i] = (buf[i] + (session_key + 0x40));
		break;

	case 1:
		for(std::size_t i = 0; i < buf.size(); ++i)
			buf[i] = (buf[i] - (session_key + 0x40));
		break;

	case 2:
		for(std::size_t i = 0; i < buf.size(); ++i)
			buf[i] = ((buf[i] ^ 0xC3) + (session_key + 0x40));
		break;

	case 3:
		for(std::size_t i = 0; i < buf.size(); ++i)
			buf[i] = ((buf[i] ^ 0xC3) - (session_key + 0x40));
		break;

	default:
		for(std::size_t i = 0; i < buf.size(); ++i)
			buf[i] = buf[i] + 0x0F;
		break;
	}
}
12/05/2015 13:51 0Lucifer0#2
Good job!
12/05/2015 21:26 abssy#3
Thanks a lot, I was just searching for this a week ago and you just post it. Amazing job.
12/22/2015 21:06 PainToTheWorld#4
I've tested all encryptions and it works well . but when I send my session and id / pw to the game server , I get no response . Is there a solution to this problem ?
12/22/2015 21:20 BladeTiger12#5
Then is your packet build wrong. Can you send code snippet where you send that packet?
12/22/2015 23:21 Trollcrap-#6
Quote:
Originally Posted by PainToTheWorld View Post
I've tested all encryptions and it works well . but when I send my session and id / pw to the game server , I get no response . Is there a solution to this problem ?
Well, it also happens when you send too fast the packets to world server.
12/23/2015 18:17 PainToTheWorld#7
did it with different delays .. unfortunately it does not work :/



btw is the packet identifier random?
12/23/2015 19:21 BladeTiger12#8
Yes it's random. (Just generate it 1 time and add for new packet +1)
You have to send the SessionID...(Wait 1sec~)...IP|PW same packet.
Like:

Code:
// You need it like this:

std::vector<byte> bytes;

bytes.push_back(EncryptGame("IDENTIFIER ID")); //EncryptGame("25555 Test")
bytes.push_back(0xFF);
bytes.push_back(EncryptGame("IDENTIFIER PW")); //EncryptGame("25556 Rofl")

send(bytes);
12/24/2015 23:14 Trollcrap-#9
Quote:
Originally Posted by BladeTiger12 View Post
Yes it's random. (Just generate it 1 time and add for new packet +1)
You have to send the SessionID...(Wait 1sec~)...IP|PW same packet.
Like:

Code:
// You need it like this:

std::vector<byte> bytes;

bytes.push_back(EncryptGame("IDENTIFIER ID")); //EncryptGame("25555 Test")
bytes.push_back(0xFF);
bytes.push_back(EncryptGame("IDENTIFIER PW")); //EncryptGame("25556 Rofl")

send(bytes);
There is not a rule about ID|PW, server is async and splits by delimiter.. 1s is right pause ~
08/09/2016 20:24 lika85#10
While compiling it returns error

"32 2 H:\DevC++\Dev-Cpp\MinGW64\lib\gcc\x86_64-w64-mingw32\4.9.2\include\c++\bits\c++0x_warning.h [Error] #error This file requires compiler and library support for the ISO C++ 2011 standard. This support is currently experimental, and must be enabled with the -std=c++11 or -std=gnu++11 compiler options.

md5.h No such file or directory"

for hex.h too. Missing md5.h and hex.h, where can i find it?
03/03/2017 15:01 ArtTorn#11
This is still correct way?
03/03/2017 15:04 FI0w#12
Quote:
Originally Posted by ArtTorn View Post
This is still correct way?
Just PW is wrong you dont need to Encrypt/Decrypt it anymore you can just use SHA512 Uppercase :P
03/08/2017 14:33 Cryless~#13
Quote:
Originally Posted by xSensitivex View Post
Just PW is wrong you dont need to Encrypt/Decrypt it anymore you can just use SHA512 Uppercase :P
openssl

Code:
std::string NTCryptoHelper::ToHex(unsigned char *digest, std::size_t length)
{
	static char table[] = {
		'0', '1', '2', '3', '4', '5', '6', '7',
		'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
	};

	std::string result;
	for (std::size_t i = 0; i < length; i++) {
		result += table[digest[i] / 16];
		result += table[digest[i] % 16];
	}

	return result;
}
Code:
std::string NTCryptoHelper::Sha512(const std::string& plainText)
{
	unsigned char digest[SHA512_DIGEST_LENGTH];
	SHA512((unsigned char*)plainText.c_str(), plainText.length(), digest);
	return ToHex(digest, sizeof(digest));
}
08/24/2017 12:51 lika85#14
Code:
	public static ArrayList<String> DecryptGamePacketTest(ArrayList<Integer> buf) {
		int len = buf.size();
		ArrayList<String> output = new ArrayList<String>();
		ArrayList<Integer> current_packet = new ArrayList<Integer>();
		char keys[] = { ' ', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'n' };
		int index = 0;
		int currentByte = 0, length = 0, first = 0, second = 0;

		while (index < len) {
			currentByte = buf.get(index);
			++index;
			if (currentByte == 0xFF) {
				output.add(ArrayListToString(current_packet));
				//System.out.println("RECEIVED: "+ArrayListToString(current_packet));
				current_packet = new ArrayList<Integer>();
				continue;
			}

			length = (currentByte & 0x7F);
			if (((currentByte & 0x80) & 0xFF) != 0 && currentByte!=0) {
				while (length != 0) {
					if (index < len) {
						currentByte = buf.get(index);
						++index;
						try{
						first = keys[(((currentByte & 0xF0) ) >> 4) - 1];
						if (first != 0x6E)
							current_packet.add(first);

						if (length <= 1)
							break;
                        
						second = keys[(currentByte & 0xF) - 1];
						if (second != 0x6E)
							current_packet.add(second);

						length -= 2;
						}
						catch(Exception e){System.out.println("Exception packet: "+ArrayListToString(current_packet));}
					} else {
						--length;
					}
				}
			} else {
				while (length != 0) {
					if (index < len) {
						//current_packet.add(buf.get(index) ^ 0xFF);¨
						current_packet.add(buf.get(index) ^ 0xFF);
						++index;
					}

					--length;
				}
			}
		}

		return output;
	}
This is DecryptGamePacket function i currently use in Java and i have problems with chars with diacritics (ěščřžýáíé). Probably becouse java uses 2 byte chars and in this function it is only 1 byte. But in client diacritics and other shit chars are working. Is it working for you? How this can be fixed? :handsdown:
10/22/2017 20:21 -Nokis#15
Quote:
Originally Posted by lika85 View Post
Code:
	public static ArrayList<String> DecryptGamePacketTest(ArrayList<Integer> buf) {
		int len = buf.size();
		ArrayList<String> output = new ArrayList<String>();
		ArrayList<Integer> current_packet = new ArrayList<Integer>();
		char keys[] = { ' ', '-', '.', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'n' };
		int index = 0;
		int currentByte = 0, length = 0, first = 0, second = 0;

		while (index < len) {
			currentByte = buf.get(index);
			++index;
			if (currentByte == 0xFF) {
				output.add(ArrayListToString(current_packet));
				//System.out.println("RECEIVED: "+ArrayListToString(current_packet));
				current_packet = new ArrayList<Integer>();
				continue;
			}

			length = (currentByte & 0x7F);
			if (((currentByte & 0x80) & 0xFF) != 0 && currentByte!=0) {
				while (length != 0) {
					if (index < len) {
						currentByte = buf.get(index);
						++index;
						try{
						first = keys[(((currentByte & 0xF0) ) >> 4) - 1];
						if (first != 0x6E)
							current_packet.add(first);

						if (length <= 1)
							break;
                        
						second = keys[(currentByte & 0xF) - 1];
						if (second != 0x6E)
							current_packet.add(second);

						length -= 2;
						}
						catch(Exception e){System.out.println("Exception packet: "+ArrayListToString(current_packet));}
					} else {
						--length;
					}
				}
			} else {
				while (length != 0) {
					if (index < len) {
						//current_packet.add(buf.get(index) ^ 0xFF);¨
						current_packet.add(buf.get(index) ^ 0xFF);
						++index;
					}

					--length;
				}
			}
		}

		return output;
	}
This is DecryptGamePacket function i currently use in Java and i have problems with chars with diacritics (ěščřžýáíé). Probably becouse java uses 2 byte chars and in this function it is only 1 byte. But in client diacritics and other shit chars are working. Is it working for you? How this can be fixed? :handsdown:
The NosTale Client uses the Windows-1252 encoding. You have to encode ‘current_packet’ in win1252.