1/*
2 This file has been derived from Konversation, the KDE IRC client.
3 You can redistribute it and/or modify it under the terms of the
4 GNU General Public License as published by the Free Software Foundation;
5 either version 2 of the License, or (at your option) any later version.
6*/
7
8/*
9 Copyright (C) 1997 Robey Pointer <robeypointer@gmail.com>
10 Copyright (C) 2005 Ismail Donmez <ismail@kde.org>
11 Copyright (C) 2009 Travis McHenry <tmchenryaz@cox.net>
12 Copyright (C) 2009 Johannes Huber <johu@gmx.de>
13*/
14
15#include "cipher.h"
16#include "logger.h"
17
18Cipher::Cipher()
19{
20 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
21 setType("blowfish");
22}
23
24
25Cipher::Cipher(QByteArray key, QString cipherType)
26{
27 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
28 setKey(key);
29 setType(cipherType);
30}
31
32
33Cipher::~Cipher()
34{}
35
36bool Cipher::setKey(QByteArray key)
37{
38 if (key.isEmpty()) {
39 m_key.clear();
40 return false;
41 }
42
43 if (key.mid(0, 4).toLower() == "ecb:")
44 {
45 m_cbc = false;
46 m_key = key.mid(4);
47 }
48 //strip cbc: if included
49 else if (key.mid(0, 4).toLower() == "cbc:")
50 {
51 m_cbc = true;
52 m_key = key.mid(4);
53 }
54 else
55 {
56// if(Preferences::self()->encryptionType())
57// m_cbc = true;
58// else
59 m_cbc = false;
60 m_key = key;
61 }
62 return true;
63}
64
65
66bool Cipher::setType(const QString &type)
67{
68 //TODO check QCA::isSupported()
69 m_type = type;
70 return true;
71}
72
73
74QByteArray Cipher::decrypt(QByteArray cipherText)
75{
76 QByteArray pfx = "";
77 bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
78
79 //if we get cbc
80 if (cipherText.mid(0, 5) == "+OK *")
81 {
82 //if we have cbc
83 if (m_cbc)
84 cipherText = cipherText.mid(5);
85 //if we don't
86 else
87 {
88 cipherText = cipherText.mid(5);
89 pfx = "ERROR_NONECB: ";
90 error = true;
91 }
92 }
93 //if we get ecb
94 else if (cipherText.mid(0, 4) == "+OK " || cipherText.mid(0, 5) == "mcps ")
95 {
96 //if we had cbc
97 if (m_cbc)
98 {
99 cipherText = (cipherText.mid(0, 4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
100 pfx = "ERROR_NONCBC: ";
101 error = true;
102 }
103 //if we don't
104 else
105 {
106 if (cipherText.mid(0, 4) == "+OK ")
107 cipherText = cipherText.mid(4);
108 else
109 cipherText = cipherText.mid(5);
110 }
111 }
112 //all other cases we fail
113 else
114 return cipherText;
115
116 QByteArray temp;
117 // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
118 if ((m_cbc && !error) || (!m_cbc && error))
119 {
120 cipherText = cipherText;
121 temp = blowfishCBC(cipherText, false);
122
123 if (temp == cipherText)
124 {
125 // kDebug("Decryption from CBC Failed");
126 return cipherText+' '+'\n';
127 }
128 else
129 cipherText = temp;
130 }
131 else
132 {
133 temp = blowfishECB(cipherText, false);
134
135 if (temp == cipherText)
136 {
137 // kDebug("Decryption from ECB Failed");
138 return cipherText+' '+'\n';
139 }
140 else
141 cipherText = temp;
142 }
143 // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
144 // don't hate me for the mircryption reference there.
145 if (cipherText.at(0) == 1)
146 pfx = "\x0";
147 cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
148 return cipherText;
149}
150
151
152QByteArray Cipher::initKeyExchange()
153{
154 QCA::Initializer init;
155 m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
156
157 if (m_tempKey.isNull())
158 return QByteArray();
159
160 QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
161
162 //remove leading 0
163 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
164 publicKey = publicKey.mid(1);
165
166 return publicKey.toBase64().append('A');
167}
168
169
170QByteArray Cipher::parseInitKeyX(QByteArray key)
171{
172 QCA::Initializer init;
173 bool isCBC = false;
174
175 if (key.endsWith(" CBC"))
176 {
177 isCBC = true;
178 key.chop(4);
179 }
180
181 if (key.length() != 181)
182 return QByteArray();
183
184 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
185 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
186 QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
187
188 if (privateKey.isNull())
189 return QByteArray();
190
191 QByteArray publicKey = privateKey.y().toArray().toByteArray();
192
193 //remove leading 0
194 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
195 publicKey = publicKey.mid(1);
196
197 QCA::DHPublicKey remotePub(group, remoteKey);
198
199 if (remotePub.isNull())
200 return QByteArray();
201
202 QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
203 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
204
205 //remove trailing = because mircryption and fish think it's a swell idea.
206 while (sharedKey.endsWith('=')) sharedKey.chop(1);
207
208 if (isCBC)
209 sharedKey.prepend("cbc:");
210
211 bool success = setKey(sharedKey);
212
213 if (!success)
214 return QByteArray();
215
216 return publicKey.toBase64().append('A');
217}
218
219
220bool Cipher::parseFinishKeyX(QByteArray key)
221{
222 QCA::Initializer init;
223
224 if (key.length() != 181)
225 return false;
226
227 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
228 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
229
230 QCA::DHPublicKey remotePub(group, remoteKey);
231
232 if (remotePub.isNull())
233 return false;
234
235 if (m_tempKey.isNull())
236 return false;
237
238 QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
239 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
240
241 //remove trailng = because mircryption and fish think it's a swell idea.
242 while (sharedKey.endsWith('=')) sharedKey.chop(1);
243
244 bool success = setKey(sharedKey);
245
246 return success;
247}
248
249
250QByteArray Cipher::decryptTopic(QByteArray cipherText)
251{
252 if (cipherText.mid(0, 4) == "+OK ") // FiSH style topic
253 cipherText = cipherText.mid(4);
254 else if (cipherText.left(5) == "«m«")
255 cipherText = cipherText.mid(5, cipherText.length()-10);
256 else
257 return cipherText;
258
259 QByteArray temp;
260 //TODO currently no backwards sanity checks for topic, it seems to use different standards
261 //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
262 if (m_cbc)
263 temp = blowfishCBC(cipherText.mid(1), false);
264 else
265 temp = blowfishECB(cipherText, false);
266
267 if (temp == cipherText)
268 {
269 return cipherText;
270 }
271 else
272 cipherText = temp;
273
274 if (cipherText.mid(0, 2) == "@@")
275 cipherText = cipherText.mid(2);
276
277 return cipherText;
278}
279
280
281bool Cipher::encrypt(QByteArray &cipherText)
282{
283 if (cipherText.left(3) == "+p ") //don't encode if...?
284 cipherText = cipherText.mid(3);
285 else
286 {
287 if (m_cbc) //encode in ecb or cbc decide how to determine later
288 {
289 QByteArray temp = blowfishCBC(cipherText, true);
290
291 if (temp == cipherText)
292 {
293 // kDebug("CBC Encoding Failed");
294 return false;
295 }
296
297 cipherText = "+OK *" + temp;
298 }
299 else
300 {
301 QByteArray temp = blowfishECB(cipherText, true);
302
303 if (temp == cipherText)
304 {
305 // kDebug("ECB Encoding Failed");
306 return false;
307 }
308
309 cipherText = "+OK " + temp;
310 }
311 }
312 return true;
313}
314
315
316//THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
317QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
318{
319 QCA::Initializer init;
320 QByteArray temp = cipherText;
321 if (direction)
322 {
323 // make sure cipherText is an interval of 8 bits. We do this before so that we
324 // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
325 while ((temp.length() % 8) != 0) temp.append('\0');
326
327 QCA::InitializationVector iv(8);
328 temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
329 }
330 else
331 {
332 temp = QByteArray::fromBase64(temp);
333 //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
334 //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
335 while ((temp.length() % 8) != 0) temp.append('\0');
336 }
337
338 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
339 QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
340 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
341 temp2 += cipher.final().toByteArray();
342
343 if (!cipher.ok())
344 return cipherText;
345
346 if (direction) //send in base64
347 temp2 = temp2.toBase64();
348 else //cut off the 8bits of IV
349 temp2 = temp2.remove(0, 8);
350
351 return temp2;
352}
353
354
355QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
356{
357 QCA::Initializer init;
358 QByteArray temp = cipherText;
359
360 //do padding ourselves
361 if (direction)
362 {
363 while ((temp.length() % 8) != 0) temp.append('\0');
364 }
365 else
366 {
367 temp = b64ToByte(temp);
368 while ((temp.length() % 8) != 0) temp.append('\0');
369 }
370
371 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
372 QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
373 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
374 temp2 += cipher.final().toByteArray();
375
376 if (!cipher.ok())
377 return cipherText;
378
379 if (direction)
380 temp2 = byteToB64(temp2);
381
382 return temp2;
383}
384
385
386//Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
387QByteArray Cipher::byteToB64(QByteArray text)
388{
389 int left = 0;
390 int right = 0;
391 int k = -1;
392 int v;
393 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
394 QByteArray encoded;
395 while (k < (text.length() - 1)) {
396 k++;
397 v = text.at(k); if (v < 0) v += 256;
398 left = v << 24;
399 k++;
400 v = text.at(k); if (v < 0) v += 256;
401 left += v << 16;
402 k++;
403 v = text.at(k); if (v < 0) v += 256;
404 left += v << 8;
405 k++;
406 v = text.at(k); if (v < 0) v += 256;
407 left += v;
408
409 k++;
410 v = text.at(k); if (v < 0) v += 256;
411 right = v << 24;
412 k++;
413 v = text.at(k); if (v < 0) v += 256;
414 right += v << 16;
415 k++;
416 v = text.at(k); if (v < 0) v += 256;
417 right += v << 8;
418 k++;
419 v = text.at(k); if (v < 0) v += 256;
420 right += v;
421
422 for (int i = 0; i < 6; i++) {
423 encoded.append(base64.at(right & 0x3F).toAscii());
424 right = right >> 6;
425 }
426
427 //TODO make sure the .toascii doesn't break anything
428 for (int i = 0; i < 6; i++) {
429 encoded.append(base64.at(left & 0x3F).toAscii());
430 left = left >> 6;
431 }
432 }
433 return encoded;
434}
435
436
437QByteArray Cipher::b64ToByte(QByteArray text)
438{
439 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
440 QByteArray decoded;
441 int k = -1;
442 while (k < (text.length() - 1)) {
443 int right = 0;
444 int left = 0;
445 int v = 0;
446 int w = 0;
447 int z = 0;
448
449 for (int i = 0; i < 6; i++) {
450 k++;
451 v = base64.indexOf(text.at(k));
452 right |= v << (i * 6);
453 }
454
455 for (int i = 0; i < 6; i++) {
456 k++;
457 v = base64.indexOf(text.at(k));
458 left |= v << (i * 6);
459 }
460
461 for (int i = 0; i < 4; i++) {
462 w = ((left & (0xFF << ((3 - i) * 8))));
463 z = w >> ((3 - i) * 8);
464 if (z < 0) { z = z + 256; }
465 decoded.append(z);
466 }
467
468 for (int i = 0; i < 4; i++) {
469 w = ((right & (0xFF << ((3 - i) * 8))));
470 z = w >> ((3 - i) * 8);
471 if (z < 0) { z = z + 256; }
472 decoded.append(z);
473 }
474 }
475 return decoded;
476}
477
478
479bool Cipher::neededFeaturesAvailable()
480{
481 QCA::Initializer init;
482
483 if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))
484 return true;
485
486 return false;
487}
488