1 /*
2  * A simple implementation of the XOR Cipher, with addition functions to read and write hex cipher text.
3  * See https://en.wikipedia.org/wiki/XOR_cipher for an explanation.
4  *
5  * Two constructors are available:
6  *	 * A file name and a key length - if the file does not exist, the key is randomly generated and saved to the file.
7  * 	 * A key specified in hex form directly.
8  *
9  * Only plain/cipher text less than or equal to the key length can be encrypted / decrypted.
10  *
11  * Note, protect the key if you don't want someone to be able to decrypt a string.
12  *
13  * Author bluap/pjbroad March 2019.
14 */
15 
16 #include <iostream>
17 #include <vector>
18 #include <sstream>
19 #include <string>
20 #include <cstdlib>
21 #include <ctime>
22 #include <iomanip>
23 #include <fstream>
24 #include <cassert>
25 
26 #include "xor_cipher.hpp"
27 
28 #ifdef ELC
29 #include "elloggingwrapper.h"
30 #endif
31 
32 namespace XOR_Cipher
33 {
34 	//	A wrapper around the EL client LOG_ERROR() function.
show_error(const char * function_name,const std::string & message)35 	static void show_error(const char * function_name, const std::string& message)
36 	{
37 #ifdef ELC
38 		LOG_ERROR("%s: %s\n", function_name, message.c_str());
39 #else
40 		std::cerr << function_name << ": " << message << std::endl;
41 #endif
42 	}
43 
44 	//	Contruct using a pre created key, specified as hex
45 	//
Cipher(const std::string & key_hex_str)46 	Cipher::Cipher(const std::string& key_hex_str) : status_ok(true)
47 	{
48 		key = hex_to_cipher(key_hex_str);
49 	}
50 
51 	//	Construct using a key stored in a file.  If the file does not exist, create a randon key and save to the file.
52 	//
Cipher(const std::string & file_name,size_t the_key_size)53 	Cipher::Cipher(const std::string& file_name, size_t the_key_size) : key_file_name(file_name), status_ok(true)
54 	{
55 		std::ifstream in(key_file_name.c_str());
56 		if (!in)
57 		{
58 			srand (time(NULL));
59 			for (size_t i=0; i<the_key_size; ++i)
60 				key.push_back(rand()%256);
61 			std::ofstream out(key_file_name.c_str(), std::ios_base::out | std::ios_base::trunc);
62 			if (out)
63 			{
64 				out << cipher_to_hex(key) << std::endl;
65 				out.close();
66 			}
67 			else
68 			{
69 				show_error(__PRETTY_FUNCTION__, std::string("Failed to created new ciper key file ") + key_file_name);
70 				status_ok = false;
71 			}
72 			return;
73 		}
74 		std::string key_hex_str;
75 		in >> key_hex_str;
76 		in.close();
77 		key = hex_to_cipher(key_hex_str);
78 		if (key.size() != the_key_size)
79 		{
80 			show_error(__PRETTY_FUNCTION__, std::string("Key from file does not match required size"));
81 			status_ok = false;
82 		}
83 	}
84 
85 	//	Convert a binary cipher text vector to a hex string
86 	//
cipher_to_hex(const std::vector<unsigned char> & cipher_text) const87 	std::string Cipher::cipher_to_hex(const std::vector<unsigned char>& cipher_text) const
88 	{
89 		std::stringstream ss;
90 		for (size_t i=0; i<cipher_text.size(); ++i)
91 			ss << std::setfill('0') << std::setw(2) << std::hex << static_cast<int>(cipher_text[i]);
92 		return ss.str();
93 	}
94 
95 	//	Convert a validated hex string to a binary vector for use in this module
96 	//
hex_to_cipher(const std::string & hex_str)97 	std::vector<unsigned char> Cipher::hex_to_cipher(const std::string& hex_str)
98 	{
99 		std::vector <unsigned char> cipher_text;
100 		if (((hex_str.size() % 2) == 0) && (hex_str.find_first_not_of("0123456789abcdefABCDEF", 0) == std::string::npos))
101 			for (size_t i=0; i<hex_str.size(); i+=2)
102 			{
103 				int tmp;
104 				std::stringstream ss;
105 				ss << std::hex << hex_str.substr(i, 2);
106 				ss >> tmp;
107 				cipher_text.push_back(static_cast<unsigned char>(tmp));
108 			}
109 		else
110 		{
111 			show_error(__PRETTY_FUNCTION__, std::string("Not valid hex string [") + hex_str + std::string("]"));
112 			status_ok = false;
113 		}
114 		return cipher_text;
115 	}
116 
117 	//	Return the encryted cipher text for the specifed string
118 	//
encrypt(const std::string & plain_text)119 	std::vector<unsigned char> Cipher::encrypt(const std::string& plain_text)
120 	{
121 		std::vector<unsigned char> cipher_text;
122 		if (plain_text.size() <= key.size())
123 			for (size_t i=0; i<plain_text.size(); ++i)
124 				cipher_text.push_back(static_cast<unsigned char>(plain_text[i]) ^ key[i]);
125 		else
126 		{
127 			show_error(__PRETTY_FUNCTION__, std::string("Plain text too long [") + plain_text + std::string("]"));
128 			status_ok = false;
129 		}
130 		return cipher_text;
131 	}
132 
133 	//	Return the plan text string of the specified cipher text
134 	//
decrypt(const std::vector<unsigned char> & cipher_text)135 	std::string Cipher::decrypt(const std::vector<unsigned char>& cipher_text)
136 	{
137 		std::string plain_text;
138 		if (cipher_text.size() <= key.size())
139 			for (size_t i=0; i<cipher_text.size(); ++i)
140 				plain_text.push_back(static_cast<char>(cipher_text[i]) ^ key[i]);
141 		else
142 		{
143 			show_error(__PRETTY_FUNCTION__, std::string("Cypher text too long [") + cipher_to_hex(cipher_text) + std::string("]"));
144 			status_ok = false;
145 		}
146 		return plain_text;
147 	}
148 }
149 
150 //	Unit testing
151 //	g++ XOR_cipher.cpp
152 //	./a.out
153 //
154 #ifndef ELC
main(int argc,char * argv[])155 int main(int argc, char *argv[])
156 {
157 	if (argc > 1)
158 	{
159 		std::string help(std::string(argv[0]) + " <-d | -e> <key hex string> <cypher text hex string | message>");
160 		if (argc > 3)
161 		{
162 			XOR_Cipher::Cipher cipher(argv[2]);
163 			if (std::string(argv[1]) == "-e")
164 				std::cout << cipher.cipher_to_hex(cipher.encrypt(argv[3])) << std::endl;
165 			else if (std::string(argv[1]) == "-d")
166 				std::cout << cipher.decrypt(cipher.hex_to_cipher(argv[3])) << std::endl;
167 			else
168 				std::cerr << help << std::endl;
169 		}
170 		else
171 			std::cerr << help << std::endl;
172 	}
173 	else
174 	{
175 		std::cout << "Unit tests....." << std::endl;
176 
177 		std::string key_file_name("XOR_cipher_test_key_for_testing");
178 		static size_t key_size = 32;
179 
180 		XOR_Cipher::Cipher f_cipher(key_file_name, key_size);
181 		assert(f_cipher.get_status_ok());
182 		assert(std::ifstream(key_file_name.c_str()).good());
183 		std::string f_message("My_secret");
184 		std::vector<unsigned char> f_cipher_text = f_cipher.encrypt(f_message);
185 		assert(f_message == f_cipher.decrypt(f_cipher_text));
186 		assert(f_cipher.get_status_ok());
187 
188 		XOR_Cipher::Cipher f_2_cipher(key_file_name, key_size);
189 		assert(f_2_cipher.get_status_ok());
190 		remove(key_file_name.c_str());
191 
192 		XOR_Cipher::Cipher cipher("0123456789abcdef0123456789abcdef");
193 		assert(cipher.get_status_ok());
194 
195 		assert(cipher.encrypt("this is a long long long long long long long long long long long long test, too long").size() == 0);
196 		assert(!cipher.get_status_ok());
197 		cipher.set_status_ok();
198 
199 		std::vector<unsigned char> too_big_cipher;
200 		for (size_t i=0; i<key_size + 1; ++i)
201 			too_big_cipher.push_back(32);
202 		assert(cipher.decrypt(too_big_cipher).empty());
203 		assert(!cipher.get_status_ok());
204 		cipher.set_status_ok();
205 
206 		assert(cipher.encrypt("this is a test").size() != 0);
207 		assert(cipher.get_status_ok());
208 
209 		assert(cipher.hex_to_cipher("123").size() == 0);
210 		assert(!cipher.get_status_ok());
211 		cipher.set_status_ok();
212 		assert(cipher.hex_to_cipher("efgh").size() == 0);
213 		assert(!cipher.get_status_ok());
214 		cipher.set_status_ok();
215 
216 		std::string message("My_secret");
217 		std::vector<unsigned char> cipher_text = cipher.encrypt(message);
218 		assert(cipher.get_status_ok());
219 		std::string cipher_hex = cipher.cipher_to_hex(cipher_text);
220 		assert(cipher.get_status_ok());
221 
222 		assert(message == cipher.decrypt(cipher_text));
223 		assert(cipher.get_status_ok());
224 		assert(cipher_text == cipher.hex_to_cipher(cipher_hex));
225 		assert(cipher.get_status_ok());
226 		assert(message == cipher.decrypt(cipher.hex_to_cipher(cipher_hex)));
227 		assert(cipher.get_status_ok());
228 
229 		std::cout << "All PASS" << std::endl;
230 	}
231 
232 	return 0;
233 }
234 #endif
235