Merge branch 'main' of feistymeow.org:feisty_meow
[feisty_meow.git] / crypto / blowfish_crypto.cpp
1 /*****************************************************************************\
2 *                                                                             *
3 *  Name   : blowfish encryption                                               *
4 *  Author : Chris Koeritz                                                     *
5 *                                                                             *
6 *******************************************************************************
7 * Copyright (c) 2005-$now By Author.  This program is free software; you can  *
8 * redistribute it and/or modify it under the terms of the GNU General Public  *
9 * License as published by the Free Software Foundation; either version 2 of   *
10 * the License or (at your option) any later version.  This is online at:      *
11 *     http://www.fsf.org/copyleft/gpl.html                                    *
12 * Please send any updates to: fred@gruntose.com                               *
13 \*****************************************************************************/
14
15 #include "blowfish_crypto.h"
16 #include "ssl_init.h"
17
18 #include <basis/astring.h>
19 #include <basis/functions.h>
20 #include <basis/mutex.h>
21 #include <loggers/critical_events.h>
22 #include <loggers/program_wide_logger.h>
23 #include <mathematics/chaos.h>
24 #include <structures/static_memory_gremlin.h>
25
26 #include <openssl/blowfish.h>
27 #include <openssl/evp.h>
28
29 using namespace basis;
30 using namespace loggers;
31 using namespace mathematics;
32 using namespace structures;
33
34 namespace crypto {
35
36 const int FUDGE = 128;
37   // extra space for the cipher's block size.  blowfish is only 8 bytes for
38   // the cipher block size, but we ensure there will definitely be no
39   // problems.
40
41 //#undef set_key
42   // get rid of a macro we don't want.
43
44 //#define DEBUG_BLOWFISH
45   // uncomment for noisier version.
46
47 #ifdef DEBUG_BLOWFISH
48   #undef LOG
49   #define LOG(t) CLASS_EMERGENCY_LOG(program_wide_logger::get(), t)
50 #else
51   #undef LOG
52   #define LOG(t) 
53 #endif
54
55 #ifdef DEBUG_BLOWFISH
56   // this macro checks on the validity of the key sizes (in bits).
57   #define DISCUSS_KEY_SIZE(key_size) \
58     if (key_size < minimum_key_size()) { \
59       continuable_error(static_class_name(), func, \
60           a_sprintf("key size (%d bits) is less than minimum key size %d.", \
61               key_size, minimum_key_size())); \
62       } \
63   if (key_size > maximum_key_size()) { \
64       continuable_error(static_class_name(), func, \
65           a_sprintf("key size (%d bits) is greater than maximum key size %d.", \
66               key_size, maximum_key_size())); \
67     }
68   
69   // this macro checks that the key in the byte array has enough bytes for
70   // the key size bits.
71   #define DISCUSS_PROVIDED_KEY(key_size, key) \
72     if (key.length() * BITS_PER_BYTE < key_size) { \
73       continuable_error(static_class_name(), func, \
74           a_sprintf("key array length (%d) is less than required by key size " \
75               "(%d bits).", key.length(), key_size)); \
76     }
77 #else
78   #define DISCUSS_PROVIDED_KEY(key_size, key) 
79   #define DISCUSS_KEY_SIZE(key_size) 
80 #endif
81
82 blowfish_crypto::blowfish_crypto(int key_size)
83 : _key_size(key_size),
84   _key(new byte_array)
85 {
86   FUNCDEF("ctor(int)");
87   static_ssl_initializer();
88   LOG("prior to key size discuss");
89   DISCUSS_KEY_SIZE(key_size);
90   if (key_size < minimum_key_size())
91     _key_size = minimum_key_size();
92   if (key_size > maximum_key_size())
93     _key_size = maximum_key_size();
94   LOG("prior to generate key");
95   generate_key(_key_size, *_key);
96   LOG("after generate key");
97 }
98
99 blowfish_crypto::blowfish_crypto(const byte_array &key, int key_size)
100 : _key_size(key_size),
101   _key(new byte_array(key))
102 {
103   FUNCDEF("ctor(byte_array,int)");
104   // any problems with the key provided are horrid.  they will yield a
105   // non-working blowfish object.
106   LOG("prior to key size discuss");
107   DISCUSS_KEY_SIZE(key_size);
108   LOG("prior to provided key discuss");
109   DISCUSS_PROVIDED_KEY(key_size, key);
110   LOG("prior to ssl static init");
111   static_ssl_initializer();
112   LOG("after ssl static init");
113 }
114
115 blowfish_crypto::blowfish_crypto(const blowfish_crypto &to_copy)
116 : root_object(),
117   _key_size(to_copy._key_size),
118   _key(new byte_array(*to_copy._key))
119 {
120   FUNCDEF("copy ctor");
121   static_ssl_initializer(); 
122   LOG("after ssl static init");
123 }
124
125 blowfish_crypto::~blowfish_crypto()
126 {
127   FUNCDEF("destructor");
128   LOG("prior to key whack");
129   WHACK(_key);
130   LOG("after key whack");
131 }
132
133 int blowfish_crypto::key_size() const { return _key_size; }
134
135 const byte_array &blowfish_crypto::get_key() const { return *_key; }
136
137 int blowfish_crypto::minimum_key_size() { return 64; }
138
139 int blowfish_crypto::maximum_key_size() { return 448; }
140
141 blowfish_crypto &blowfish_crypto::operator = (const blowfish_crypto &to_copy)
142 {
143   if (this == &to_copy) return *this;
144   _key_size = to_copy._key_size;
145   *_key = *to_copy._key;
146   return *this;
147 }
148
149 bool blowfish_crypto::set_key(const byte_array &new_key, int key_size)
150 {
151   FUNCDEF("set_key");
152   if (!new_key.length()) return false;
153   DISCUSS_KEY_SIZE(key_size);
154   DISCUSS_PROVIDED_KEY(key_size, new_key);
155   if ( (key_size < minimum_key_size()) || (key_size > maximum_key_size()) )
156     return false;
157   if (new_key.length() * BITS_PER_BYTE < key_size) return false;
158   _key_size = key_size;
159   *_key = new_key;
160   return true;
161 }
162
163 void blowfish_crypto::generate_key(int size, byte_array &new_key)
164 {
165   FUNCDEF("generate_key");
166   DISCUSS_KEY_SIZE(size);
167   if (size < minimum_key_size())
168     size = minimum_key_size();
169   else if (size > maximum_key_size())
170     size = maximum_key_size();
171   int bytes = size / BITS_PER_BYTE;  // calculate the number of bytes needed.
172   if (size % BITS_PER_BYTE) bytes++;  // add one for non-integral portion.
173   new_key.reset(bytes);
174   for (int i = 0; i < bytes; i++)
175     new_key[i] = static_ssl_initializer().randomizer().inclusive(0, 255);
176 }
177
178 SAFE_STATIC(mutex, __vector_init_lock, )
179
180 const byte_array &blowfish_crypto::init_vector()
181 {
182   FUNCDEF("init_vector");
183   auto_synchronizer locking(__vector_init_lock());
184   static byte_array to_return(EVP_MAX_IV_LENGTH);
185   static bool initted = false;
186   LOG("prior to initted check");
187   if (!initted) {
188     LOG("actually doing init");
189     for (int i = 0; i < EVP_MAX_IV_LENGTH; i++)
190       to_return[i] = 214 - i;
191     initted = true;
192   }
193   LOG("leaving init check");
194   return to_return;
195 }
196
197 bool blowfish_crypto::encrypt(const byte_array &source,
198     byte_array &target) const
199 {
200   FUNCDEF("encrypt");
201   target.reset();
202   if (!_key->length() || !source.length()) return false;
203   bool to_return = true;
204
205   // initialize an encoding session.
206   EVP_CIPHER_CTX *session = EVP_CIPHER_CTX_new();
207
208   EVP_CIPHER_CTX_init(session);
209   EVP_EncryptInit_ex(session, EVP_bf_cbc(), NULL_POINTER, _key->observe(),
210       init_vector().observe());
211   EVP_CIPHER_CTX_set_key_length(session, _key_size);
212
213   // allocate temporary space for encrypted data.
214   byte_array encoded(source.length() + FUDGE);
215
216   // encrypt the entire source buffer.
217   int encoded_len = 0;
218   int enc_ret = EVP_EncryptUpdate(session, encoded.access(), &encoded_len,
219       source.observe(), source.length());
220   if (enc_ret != 1) {
221     continuable_error(class_name(), func, a_sprintf("encryption failed, "
222         "result=%d.", enc_ret));
223     to_return = false;
224   } else {
225     // chop any extra space off.
226     LOG(a_sprintf("chopping bytes %d to %d.\n", encoded_len, encoded.last()));
227     encoded.zap(encoded_len, encoded.last());
228     target = encoded;
229   }
230
231   // only add padding if we succeeded with the encryption.
232   if (enc_ret == 1) {
233     // finalize the encryption.
234     encoded.reset(FUDGE);  // reinflate for padding.
235     int pad_len = 0;
236     enc_ret = EVP_EncryptFinal_ex(session, encoded.access(), &pad_len);
237     if (enc_ret != 1) {
238       continuable_error(class_name(), func, a_sprintf("finalizing encryption "
239           "failed, result=%d.", enc_ret));
240       to_return = false;
241     } else {
242       LOG(a_sprintf("padding added %d bytes.\n", pad_len));
243       encoded.zap(pad_len, encoded.last());
244       target += encoded;
245     }
246   }
247
248   EVP_CIPHER_CTX_cleanup(session);
249   EVP_CIPHER_CTX_free(session);
250   return to_return;
251 }
252
253 bool blowfish_crypto::decrypt(const byte_array &source,
254     byte_array &target) const
255 {
256   FUNCDEF("decrypt");
257   target.reset();
258   if (!_key->length() || !source.length()) return false;
259   bool to_return = true;
260   EVP_CIPHER_CTX *session = EVP_CIPHER_CTX_new();
261   EVP_CIPHER_CTX_init(session);
262   LOG(a_sprintf("key size %d bits.\n", BITS_PER_BYTE * _key->length()));
263   EVP_DecryptInit_ex(session, EVP_bf_cbc(), NULL_POINTER, _key->observe(),
264       init_vector().observe());
265   EVP_CIPHER_CTX_set_key_length(session, _key_size);
266
267   // allocate enough space for decoded bytes.
268   byte_array decoded(source.length() + FUDGE);
269
270   int decoded_len = 0;
271   int dec_ret = EVP_DecryptUpdate(session, decoded.access(), &decoded_len,
272       source.observe(), source.length());
273   if (dec_ret != 1) {
274     continuable_error(class_name(), func, "decryption failed.");
275     to_return = false;
276   } else {
277     LOG(a_sprintf("  decrypted size in bytes is %d.\n", decoded_len));
278     decoded.zap(decoded_len, decoded.last());
279     target = decoded;
280   }
281
282   // only process padding if the first part of decryption succeeded.
283   if (dec_ret == 1) {
284     decoded.reset(FUDGE);  // reinflate for padding.
285     int pad_len = 0;
286     dec_ret = EVP_DecryptFinal_ex(session, decoded.access(), &pad_len);
287     LOG(a_sprintf("padding added %d bytes.\n", pad_len));
288     if (dec_ret != 1) {
289       continuable_error(class_name(), func, a_sprintf("finalizing decryption "
290           "failed, result=%d, padlen=%d, target had %d bytes.", dec_ret,
291           pad_len, target.length()));
292       to_return = false;
293     } else {
294       int dec_size = pad_len;
295       decoded.zap(dec_size, decoded.last());
296       target += decoded;
297     }
298   }
299
300   EVP_CIPHER_CTX_cleanup(session);
301   EVP_CIPHER_CTX_free(session);
302   return to_return;
303 }
304
305 } //namespace.
306
307