Quassel IRC  Pre-Release
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros
cipher.cpp
Go to the documentation of this file.
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 
19 {
20  m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
21  setType("blowfish");
22 }
23 
24 
25 Cipher::Cipher(QByteArray key, QString cipherType)
26 {
27  m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
28  setKey(key);
29  setType(cipherType);
30 }
31 
32 
34 {}
35 
36 bool 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 
66 bool Cipher::setType(const QString &type)
67 {
68  //TODO check QCA::isSupported()
69  m_type = type;
70  return true;
71 }
72 
73 
74 QByteArray 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 
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 
170 QByteArray 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 
220 bool 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 
250 QByteArray 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 
281 bool 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
317 QByteArray 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 
355 QByteArray 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  // ECB Blowfish encodes in blocks of 12 chars, so anything else is malformed input
368  if ((temp.length() % 12) != 0)
369  return cipherText;
370 
371  temp = b64ToByte(temp);
372  while ((temp.length() % 8) != 0) temp.append('\0');
373  }
374 
375  QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
376  QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
377  QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
378  temp2 += cipher.final().toByteArray();
379 
380  if (!cipher.ok())
381  return cipherText;
382 
383  if (direction) {
384  // Sanity check
385  if ((temp2.length() % 8) != 0)
386  return cipherText;
387 
388  temp2 = byteToB64(temp2);
389  }
390 
391  return temp2;
392 }
393 
394 
395 //Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
396 QByteArray Cipher::byteToB64(QByteArray text)
397 {
398  int left = 0;
399  int right = 0;
400  int k = -1;
401  int v;
402  QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
403  QByteArray encoded;
404  while (k < (text.length() - 1)) {
405  k++;
406  v = text.at(k); if (v < 0) v += 256;
407  left = v << 24;
408  k++;
409  v = text.at(k); if (v < 0) v += 256;
410  left += v << 16;
411  k++;
412  v = text.at(k); if (v < 0) v += 256;
413  left += v << 8;
414  k++;
415  v = text.at(k); if (v < 0) v += 256;
416  left += v;
417 
418  k++;
419  v = text.at(k); if (v < 0) v += 256;
420  right = v << 24;
421  k++;
422  v = text.at(k); if (v < 0) v += 256;
423  right += v << 16;
424  k++;
425  v = text.at(k); if (v < 0) v += 256;
426  right += v << 8;
427  k++;
428  v = text.at(k); if (v < 0) v += 256;
429  right += v;
430 
431  for (int i = 0; i < 6; i++) {
432  encoded.append(base64.at(right & 0x3F).toLatin1());
433  right = right >> 6;
434  }
435 
436  //TODO make sure the .toLatin1 doesn't break anything
437  for (int i = 0; i < 6; i++) {
438  encoded.append(base64.at(left & 0x3F).toLatin1());
439  left = left >> 6;
440  }
441  }
442  return encoded;
443 }
444 
445 
446 QByteArray Cipher::b64ToByte(QByteArray text)
447 {
448  QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
449  QByteArray decoded;
450  int k = -1;
451  while (k < (text.length() - 1)) {
452  int right = 0;
453  int left = 0;
454  int v = 0;
455  int w = 0;
456  int z = 0;
457 
458  for (int i = 0; i < 6; i++) {
459  k++;
460  v = base64.indexOf(text.at(k));
461  right |= v << (i * 6);
462  }
463 
464  for (int i = 0; i < 6; i++) {
465  k++;
466  v = base64.indexOf(text.at(k));
467  left |= v << (i * 6);
468  }
469 
470  for (int i = 0; i < 4; i++) {
471  w = ((left & (0xFF << ((3 - i) * 8))));
472  z = w >> ((3 - i) * 8);
473  if (z < 0) { z = z + 256; }
474  decoded.append(z);
475  }
476 
477  for (int i = 0; i < 4; i++) {
478  w = ((right & (0xFF << ((3 - i) * 8))));
479  z = w >> ((3 - i) * 8);
480  if (z < 0) { z = z + 256; }
481  decoded.append(z);
482  }
483  }
484  return decoded;
485 }
486 
487 
489 {
490  QCA::Initializer init;
491 
492  if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))
493  return true;
494 
495  return false;
496 }