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