C++ implements full random encryption, parsing libraries, and complete code analysis

Keywords: C

A friend recently asked me how to implement the random encryption library I wrote before. After discussing it, I promised him to write a completely random encryption and decryption library.Over the past few days, I have used my working hours to make changes in my original ideas, wrote a completely random encryption header, and calculated the encryption value through the encryption header, then used the encryption value and data operations to get the encryption effect.Next, the writing ideas for each step are analyzed in detail.


First attach the VS2010 project: Project Download!
The DataOperation_class is the DataOperation_.h and DataOperation_.cpp files, which can also be copied to your project for use.


Chapter Preview:

1. Implementing idea of completely random encryption head
2. Encrypt data completely random
3. Resolve Encryption Header
4. Parse encrypted data
5. Encrypt Files
6. Parse the file
7. Summary


Chapter contents:


1. Implementing idea of completely random encryption head

First of all, let's talk about how to implement it:

1. The first three bytes of the encryption header,'LKY', represent the library identity through which decryption identifies the encrypted file that belongs to us.
2. Generate 64-128 random numbers that represent the numeric values that we will use to encrypt and decrypt later.
3. Calculate the check value by adding "Y" to the length of the random number.The length of the check value is equal to the length of the random number. It is mainly used to take out the random number when decrypting.
4. Randomly generate an encrypted byte segment of 1-4 bytes, such as 1 encrypted byte, which means 1 byte encrypted, 4 encrypted bytes, which means 4 bytes encrypted together.
5. Calculate the check value by encrypting bytes and adding "K".The length of the check value is equal to the length of the encrypted byte. It is mainly used to take out the length of the encrypted byte when decrypting.
6. Specify the length of the encrypted data at a time, which ranges from 4 to 4096. Generally, divide by 4, such as 1024,2048.
7. Generate the encrypted data length identifier L, which represents the end identifier of the encryption header.


Next, refer to the source code, or work with me through the source code in the project to analyze the implementation of the encryption library:

Encrypted header source:

//Generate Encryption Header
std::string DataOperation_::EncryptHead(int EncryLen)
{
	int sranLen = 0;
	std::string sran = EncryptRand(sranLen);
	char sranhead[2048] = {0};

	//Number of operations that fill 0 to sranLen-1 bytes
	memcpy(sranhead, "LKY", strlen("LKY"));
	sranhead[sranLen] = ('Y' + sranLen);
	char srandata = 0;
	for (int i = sranLen - 1;i > 2;i--)
	{
		srandata = ((sranhead[i + 1] ^ 128) + i);
		if (0 == srandata)
			sranhead[i] = i;
		else
			sranhead[i] = srandata;
	}

	//Fill Random Encryption Number Length
	sranhead[strlen(sranhead)] = (char)sranLen;

	//Fill Random Encryption Number
	memcpy(sranhead + strlen(sranhead), sran.data(), sran.length());

	
	//Random Encrypted Bytes
	srand((unsigned)time(NULL));
	int encryLen = (1 + (rand() % 4));
	sranhead[strlen(sranhead) + encryLen] = ('K' + encryLen);
	int sranheadLen = strlen(sranhead);
	//Operators Filling Random Number Sections
	for (int i = (sranheadLen + encryLen - 1);i >= sranheadLen;i--)
	{
		srandata = (sranhead[i + 1] ^ 128) + 5;
		if (0 == srandata)
			sranhead[i] = i;
		else
			sranhead[i] = srandata;
	}

	//Save data length once
	m_EncryptionDataLen = EncryLen;

	//Fill Encrypted Byte Length
	sranhead[strlen(sranhead)] = (char)encryLen;

	//Specify one-time encryption length
	char headLen[16] = {0};
	itoa(EncryLen, headLen, 10);
	memcpy(sranhead + strlen(sranhead), headLen, strlen(headLen));
	sranhead[strlen(sranhead)] = 'L';

	return std::string(sranhead);
}

First, we analyze it from the EncryptRand function:

EncryptRand function implementation code:

//Generating Random Numbers
std::string DataOperation_::EncryptRand(int & RandLen)
{
	//Generate encrypted bytes between 64-128
	srand((unsigned)time(NULL));
	RandLen = (64 + (rand() % 65));

	//Generate Random Numbers
	std::string sran;
	char sdata;
	for (int i = 0;i < RandLen;i++)
	{
		sdata = (128 + (rand() % 128));
		sran.push_back(sdata);
	}

	m_RandData = sran;
	return sran;
}

This function is easy to understand, first generates a random number length between 64-128, and then generates the corresponding random number from the random number length.
Random numbers are numbers between 128 and 255, and the original value can be correctly calculated by the'^'operation (the design is described later).


Back to the EncryptHead function, continue with the analysis:

Code 49-59 lines, encrypt the first three bytes as "LKY", and then write down the identification in the byte of random number length, such as 65 random number length, for the 65th byte here.In this byte, the length of the random number is recorded plus "Y", and the byte value is used to calculate the operands from the fourth byte to the length of the random number by "((sranhead[i + 1] ^ 128) + i)", i.e. the current value is derived from the next value "^" 128 plus the current position of the number. This code is an inverse inference operation.

The code 62 lines, which adds 1 to the random number length position, assigns a random number length, which is used to calculate the random number length when decrypting.

Code 65 lines, filled with random numbers, whose length is random number length.

Lines 69-71 generate random encrypted bytes in a range of 1-4 bytes, such as the current value of 2, which will be filled with bytes plus "Y" in the byte position of the previous position plus 2.

Lines 72-81 are also backward inference operations, filling operation bytes forward from the filling byte position, such as from byte 2 to byte 0, through'(sranhead[i + 1] ^ 128) + 5', where the current value is derived from the next number'^'125 plus 5.

Note that to prevent the string from ending with 0, there is no zero in the random header, and when 0, the number 5 is used instead.This implementation avoids misreading, such as when our bytes are between 1 and 4, and when we read 5, we won't misjudge.

Code 84-87 lines, filled with random encrypted byte length.

Code 90-93, fill in the encrypted length once, such as 512, 1024, or 2048, and store them as strings with "L" at the end to indicate the end of the encryption header.


This code may not be understood at first glance, but with subsequent encryption and decryption operations, it will be easier to parse.


2. Encrypt data completely random

First of all, let's talk about how to implement it:

1. Read 1-4 bytes of data and convert to the long long type, which is 8 bytes.This design idea can be avoided because bitfullness results in an error data (data overflow) that cannot be pushed back to the original value.
2. Fill in unencrypted data.Because we use four-byte alignment encryption, if the encrypted byte is 3, there will be 1-2 bytes unencrypted, such as 1024 bytes encrypted, and the last byte unencrypted, but we also want to store it in the data.

Encrypted data source:

//Encrypted data calculation function
char *DataOperation_::EncryptionData(char * SrcData,int & nDataLen)
{
	if (0 == SrcData || 0 >= nDataLen)
		return 0;

	char pStrData[4096 + 1] = {0};

	int i = 0;
	for (; i < nDataLen;i += m_ByteLen)
	{
		long long data = LongLongFromData(SrcData + i);
		data ^= m_RandData[m_nCurrent++ % m_RandLen];
		
		memcpy(pStrData + i, (char *)&data, m_ByteLen);
	}
	//Write in the remaining numbers
	if (0 != nDataLen - i)
	{
		int res = (m_ByteLen - (i - nDataLen));
		memcpy(pStrData + (nDataLen - res - 1), SrcData + (nDataLen - res - 1), res);
	}

	return pStrData;
}

First, we analyze from the LongLongFromData function:

LongLongFromData function implementation code:

long long DataOperation_::LongLongFromData(char *SrcData)
{
	long long ndata = 0;
	for (int i = 0;i < m_ByteLen;i++)
	{
		ndata |= (long long)(*(SrcData + i) << (i * 8));
		if (i == 0)
			ndata &= 0x00000000000000FF;
		else if (i == 1)
			ndata &= 0x000000000000FFFF;
		else if (i == 2)
			ndata &= 0x0000000000FFFFFF;
		else if (i == 3)
			ndata &= 0x00000000FFFFFFFF;
	}

	return ndata;
}

This code is designed according to the bitwise operation principle. In order to ensure that the data is properly stored in the long long type, invalid high bits should be cleared after each assignment, which can avoid getting wrong data due to negative long type.


We actually only use 32 bits, but to avoid backstepping errors caused by symbol bits (data overflow), we can only use more bits of data to do these operations.

Back to the EncryptionData function, continue with the analysis:

Code 107-113 lines, we get the encrypted number from the long long type number'^'random number, then encrypt the byte store.This method only needs to save the original byte length, if not implemented in this way, one more byte will be saved after each encryption to avoid the impact of data overflow.During the process of writing this library, I did a lot of experiments, and if all the bits were occupied, I could not get back to the real data.The key reason is that an error data is obtained due to data overflow.

Code 115-119 lines to store unencrypted data.


3. Resolve Encryption Header

First of all, let's talk about how to implement it:

1. Read part of the data first, such as 512 bytes.The length of the random encryption header is limited by the random number, which can not exceed three times the random number, i.e. within 384 bytes.
2. After obtaining the random encryption header length, return the extra data to the original data, and then discard the encryption header for decryption.

Parse Encryption Header Source:

//Resolve Encryption Header
int DataOperation_::DecryptHeadLen(std::string EncryData, int & HeadLen)
{
	if (EncryData.empty())
		return -1;

	m_HeadLen = HeadLen = DecryptHead(EncryData, m_RandData, m_RandLen, m_ByteLen, m_EncryptionDataLen);

	return m_EncryptionDataLen;
}

The DecryptHead function is used to obtain valid data stored in the encryption header. m_RandData represents random numbers, m_RandLen represents the length of random numbers, m_ByteLen represents the length of random bytes, m_EncryptionDataLen represents the length of an encryption header, and m_HeadLen represents the length of the encryption header.

Next, the DecryptHead function is analyzed:

DecryptHead function implementation code:

//Resolve Encryption Header
int DataOperation_::DecryptHead(std::string HeadData, std::string & EncryRand, int & EncryRandLen, int & ByteLen, int & EncryLen)
{
	if (3 >= HeadData.length() || (HeadData[0] != 'L' || HeadData[1] != 'K' || HeadData[2] != 'Y'))
		return -1;

	int HeadLen = 0;;
	int Num = 0;
	for (int i = 3;i < HeadData.length() - 1;i++)
	{
		if ((char)(HeadData[i + 1] - i) ^ 128 == (char)HeadData[i] || i == (char)HeadData[i])
			++Num;
		else if ((char)HeadData[i + 1] == (char)(HeadData[i] - 'Y'))
		{
			if (3 < Num)
			{
				//Get Random Number Length
				EncryRandLen = HeadData[i + 1];
				//Record header length
				HeadLen = (i + 2);
				//Intercept data before random numbers
				HeadData = HeadData.substr(i + 2, HeadData.length() - i - 2);
				break;
			}
			else
				Num = 0;
		}
		else if (i > 128)
			return -1;
	}

	//Get Random Numbers
	EncryRand = HeadData.substr(0, EncryRandLen);

	//Data after intercepting random numbers
	HeadData = HeadData.substr(EncryRandLen, HeadData.length() - EncryRandLen);
	HeadLen += EncryRandLen;

	//Get Continuous Encrypted Byte Length
	for (int i = 0;i < HeadData.length() - 1;i++)
	{
		if (((char)HeadData[i + 1] == (char)HeadData[i] - 'K') && (((char)HeadData[i] == (char)((HeadData[i - 1] - 5) ^ 128)) || i == (char)HeadData[i]))
		{
			ByteLen = HeadData[i + 1];
			//Record header length
			HeadLen += (i + 2);
			//Intercept data before random numbers
			HeadData = HeadData.substr(i + 2, HeadData.length() - i - 2);
			break;
		}
	}
	
	//Get the encryption length once
	for (int i = 0;i <  HeadData.length();i++)
	{
		if ('L' == (char)HeadData[i])
		{
			EncryLen = atoi(HeadData.substr(0 , i).data());
			++HeadLen; 
			break;
		}

		++HeadLen;
	}

	return HeadLen;
}

Lines 158-159 detect whether the first three bytes of the data are "LKY" and continue parsing if they are "LKY".

Code 163-184 lines, parsed backwards through the fourth byte, with the current value of'^'128 or the current value equal to the current position (to avoid including 0 in the encryption header, assign the current position).If four or more consecutive bytes meet our computational requirements and are equal to the length of a random number, the random number position is obtained by subtracting the current value from the next value and adding Y.This design avoids "misalignment" caused by accidental data that is identical to the results of our calculations.

Line 187, take out the random values.

Lines 190-191, intercepting random values and previous data.

Lines 194-205, take out the random byte length by subtracting the current value from the next value and adding "K", the idea is the same as taking the random number.

Lines 208-218, take out the number of encrypted bytes at a time and determine the length of the encrypted header by using the header end flag "L".


4. Parse encrypted data

This part of the implementation idea is the same as that of encrypting data, because we get the long-long type number'^'random number, and we can also deduce the original data in this way.

Parse Encryption Header Source:

//Decrypt Data Calculation Function
char *DataOperation_::DecryptionData(char * SrcData,int & nDataLen)
{
	if (0 == SrcData || 0 >= nDataLen)
		return 0;

	char pStrData[4096 + 1] = {0};
	int i = 0;
	for (; i < nDataLen;i += m_ByteLen)
	{
		long long data = LongLongFromData(SrcData + i);
		data ^= (char)m_RandData[m_nCurrent++ % m_RandLen];
		memcpy(pStrData + i, (char *)&data, m_ByteLen);
	}
	//Write in the remaining numbers
	if (0 != nDataLen - i)
	{
		int res = (m_ByteLen - (i - nDataLen));
		memcpy(pStrData + (nDataLen - res - 1), SrcData + (nDataLen - res - 1), res);
	}

	return pStrData;
}

The code here is the same as the encrypted data function, so it is designed to facilitate the decryption of data, and can be encrypted in an infinite overlay and decrypted in an infinite overlay.For example, if a file is encrypted 10 times, the original data can be obtained by decrypting it 10 times.


5. Encrypt Files

This part of the content is relatively simple, first make the encryption header, then encrypt the original data and save it.

Encrypted file source:

//Encrypt Files
bool DataOperation_::EncryptionFile(CString Filename,bool bCover)
{
	if (Filename.IsEmpty())
		return false;
	CFile file1,file2;
	if (!file1.Open(Filename,CFile::modeRead))
		return false;

	ULONG64 FileLen = file1.GetLength();
	if (0 == FileLen)
		return false;

	CString Filename2(Filename.Mid(0,Filename.ReverseFind(_T('.'))));
	Filename2.AppendFormat(_T("_Temp%s"),Filename.Mid(Filename.ReverseFind(_T('.')),
		Filename.GetLength() - Filename.ReverseFind(_T('.'))));

	if (!file2.Open(Filename2,CFile::modeCreate | CFile::modeWrite))
		return false;
	char StrData[4096] = {0};
	int DataLen = 0;
	int Encryt = 0;

	std::string encry = EncryptHead(1024);
	int DecryptionLen = 0;
	if (DecryptionLen = DecryptHeadLen(encry, Encryt))
	{
		file2.Write(encry.data(), encry.length());
	}
	else
	{
		bCover = false; 
		goto Exit;
	}

	while (0 < FileLen)
	{
		if (DecryptionLen <= FileLen)
		{
			file1.Read(StrData,DecryptionLen);
			DataLen = DecryptionLen;
			char *WriteFile = EncryptionData(StrData,DataLen);
			file2.Write(WriteFile,DataLen);
			FileLen -= DecryptionLen;
		}
		else
		{
			file1.Read(StrData,FileLen);
			DataLen = FileLen;
			char *WriteFile = EncryptionData(StrData,DataLen);
			file2.Write(WriteFile,DataLen);
			FileLen -= FileLen;
			break;
		}
	}

Exit:
	file1.Close();
	file2.Close();
	if (bCover)
	{
		USES_CONVERSION;
		//Overwrite local files
		int nResult = remove(T2A(Filename));
		nResult = rename(T2A(Filename2),T2A(Filename));
	}
	return true;
}

Code 252-258 lines, detection of files that need to be encrypted cannot be empty.

Lines 260-262 generate a temporary file named after the file name plus _Temp and a suffix, such as test123.txt, the temporary file name is test123_Temp.txt, and when the file encryption is complete, replace it with test123.txt.

270 lines of code to generate encrypted header information.

Code 272 lines, parse the encryption header and save the information in the class.

Code 274 lines, writing encrypted header information in a temporary file.


The next step is to take out the data encryption, write to the temporary file, and replace the original file.


6. Parse the file

This part is similar to encrypted files, first parse the encrypted header, then parse the encrypted data and save it.

Parse Source:

 //Decrypt File
bool DataOperation_::DecryptionFile(CString Filename,bool bCover)
{
	if (Filename.IsEmpty())
		return false;
	CFile file1,file2;
	if (!file1.Open(Filename,CFile::modeRead))
		return false;
	ULONG64 FileLen = file1.GetLength();
	if (512 >= FileLen)
		return false;

	CString Filename2(Filename.Mid(0,Filename.ReverseFind(_T('.'))));
	Filename2.AppendFormat(_T("_Temp%s"),Filename.Mid(Filename.ReverseFind(_T('.')),
		Filename.GetLength() - Filename.ReverseFind(_T('.'))));

	if (!file2.Open(Filename2,CFile::modeCreate | CFile::modeWrite))
		return false;
	char StrData[2048 + 100] = {0};
	int DataLen = 0;
	file1.Read(StrData,512);
	//Decrypt header
	int DecryptionLen = 0;
	int HeadLen = 0;
	if (DecryptionLen = DecryptHeadLen(StrData, HeadLen))
	{
		file1.Seek(HeadLen, CFile::begin);
		FileLen -= HeadLen;
	}
	else
	{
		bCover = false; 
		goto Exit;
	}
	
	while (0 < FileLen)
	{
		if (DecryptionLen <= FileLen)
		{
			DataLen = DecryptionLen;
			file1.Read(StrData,DataLen);
			char *WriteFile = DecryptionData(StrData,DataLen);
			memset(StrData,0,strlen(StrData));
			memcpy(StrData,WriteFile,DataLen);
			file2.Write(StrData,DataLen);
			FileLen -= DecryptionLen;
		}
		else
		{
			DataLen = FileLen;
			file1.Read(StrData,FileLen);
			char *WriteFile = DecryptionData(StrData,DataLen);
			memset(StrData,0,strlen(StrData));
			memcpy(StrData,WriteFile,DataLen);
			file2.Write(StrData,DataLen);
			FileLen -= FileLen;
			break;
		}
	}

Exit:
	file1.Close();
	file2.Close();
	if (bCover)
	{
		USES_CONVERSION;
		//Overwrite local files
		int nResult = remove(T2A(Filename));
		nResult = rename(T2A(Filename2),T2A(Filename));
	}

	return true;
}


7. Summary

Overall, this fully random encryption and parsing library is relatively simple, mainly using bit operations related knowledge.Due to the small amount of private time, I've spent about 10 hours, five of which are dedicated to debugging data misalignment problems caused by inadvertent modifications.

This library can only be considered as a rudimentary one. In the future, there may be time to add compression capabilities to this library, such as compressing the original data four or even ten times after encrypting it.Of course, if you have a better idea in the future, you will also redesign a new form of encryption library.

This library uses knowledge that is described in detail in the series of articles I have written on "Learning C Language Together". Interested friends can refer to this series of articles!

102 original articles, 225 acclaimed, 190,000 visits+
Private letter follow

Posted by John_S on Wed, 04 Mar 2020 16:51:33 -0800