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.
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>
16 #include "logmessage.h"
20 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
25 Cipher::Cipher(QByteArray key, QString cipherType)
27 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
33 bool Cipher::setKey(QByteArray key)
40 if (key.mid(0, 4).toLower() == "ecb:")
45 //strip cbc: if included
46 else if (key.mid(0, 4).toLower() == "cbc:")
53 // if(Preferences::self()->encryptionType())
64 bool Cipher::setType(const QString &type)
66 //TODO check QCA::isSupported()
72 QByteArray Cipher::decrypt(QByteArray cipherText)
75 bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
78 if (cipherText.mid(0, 5) == "+OK *")
82 cipherText = cipherText.mid(5);
86 cipherText = cipherText.mid(5);
87 pfx = "ERROR_NONECB: ";
92 else if (cipherText.mid(0, 4) == "+OK " || cipherText.mid(0, 5) == "mcps ")
97 cipherText = (cipherText.mid(0, 4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
98 pfx = "ERROR_NONCBC: ";
104 if (cipherText.mid(0, 4) == "+OK ")
105 cipherText = cipherText.mid(4);
107 cipherText = cipherText.mid(5);
110 //all other cases we fail
115 // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
116 if ((m_cbc && !error) || (!m_cbc && error))
118 temp = blowfishCBC(cipherText, false);
120 if (temp == cipherText)
122 // kDebug("Decryption from CBC Failed");
123 return cipherText+' '+'\n';
130 temp = blowfishECB(cipherText, false);
132 if (temp == cipherText)
134 // kDebug("Decryption from ECB Failed");
135 return cipherText+' '+'\n';
140 // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
141 // don't hate me for the mircryption reference there.
142 if (cipherText.at(0) == 1)
144 cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
149 QByteArray Cipher::initKeyExchange()
151 QCA::Initializer init;
152 m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
154 if (m_tempKey.isNull())
157 QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
160 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
161 publicKey = publicKey.mid(1);
163 return publicKey.toBase64().append('A');
167 QByteArray Cipher::parseInitKeyX(QByteArray key)
169 QCA::Initializer init;
172 if (key.endsWith(" CBC"))
178 if (key.length() != 181)
181 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
182 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
183 QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
185 if (privateKey.isNull())
188 QByteArray publicKey = privateKey.y().toArray().toByteArray();
191 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
192 publicKey = publicKey.mid(1);
194 QCA::DHPublicKey remotePub(group, remoteKey);
196 if (remotePub.isNull())
199 QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
200 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
202 //remove trailing = because mircryption and fish think it's a swell idea.
203 while (sharedKey.endsWith('=')) sharedKey.chop(1);
206 sharedKey.prepend("cbc:");
208 bool success = setKey(sharedKey);
213 return publicKey.toBase64().append('A');
217 bool Cipher::parseFinishKeyX(QByteArray key)
219 QCA::Initializer init;
221 if (key.length() != 181)
224 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
225 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
227 QCA::DHPublicKey remotePub(group, remoteKey);
229 if (remotePub.isNull())
232 if (m_tempKey.isNull())
235 QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
236 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
238 //remove trailng = because mircryption and fish think it's a swell idea.
239 while (sharedKey.endsWith('=')) sharedKey.chop(1);
241 bool success = setKey(sharedKey);
247 QByteArray Cipher::decryptTopic(QByteArray cipherText)
249 if (cipherText.mid(0, 4) == "+OK ") // FiSH style topic
250 cipherText = cipherText.mid(4);
251 else if (cipherText.left(5) == "«m«")
252 cipherText = cipherText.mid(5, cipherText.length()-10);
257 //TODO currently no backwards sanity checks for topic, it seems to use different standards
258 //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
260 temp = blowfishCBC(cipherText.mid(1), false);
262 temp = blowfishECB(cipherText, false);
264 if (temp == cipherText)
271 if (cipherText.mid(0, 2) == "@@")
272 cipherText = cipherText.mid(2);
278 bool Cipher::encrypt(QByteArray &cipherText)
280 if (cipherText.left(3) == "+p ") //don't encode if...?
281 cipherText = cipherText.mid(3);
284 if (m_cbc) //encode in ecb or cbc decide how to determine later
286 QByteArray temp = blowfishCBC(cipherText, true);
288 if (temp == cipherText)
290 // kDebug("CBC Encoding Failed");
294 cipherText = "+OK *" + temp;
298 QByteArray temp = blowfishECB(cipherText, true);
300 if (temp == cipherText)
302 // kDebug("ECB Encoding Failed");
306 cipherText = "+OK " + temp;
313 //THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
314 QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
316 QCA::Initializer init;
317 QByteArray temp = cipherText;
320 // make sure cipherText is an interval of 8 bits. We do this before so that we
321 // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
322 while ((temp.length() % 8) != 0) temp.append('\0');
324 QCA::InitializationVector iv(8);
325 temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
329 temp = QByteArray::fromBase64(temp);
330 //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
331 //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
332 while ((temp.length() % 8) != 0) temp.append('\0');
335 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
336 QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
337 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
338 temp2 += cipher.final().toByteArray();
343 if (direction) //send in base64
344 temp2 = temp2.toBase64();
345 else //cut off the 8bits of IV
346 temp2 = temp2.remove(0, 8);
352 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
354 QCA::Initializer init;
355 QByteArray temp = cipherText;
357 //do padding ourselves
360 while ((temp.length() % 8) != 0) temp.append('\0');
364 // ECB Blowfish encodes in blocks of 12 chars, so anything else is malformed input
365 if ((temp.length() % 12) != 0)
368 temp = b64ToByte(temp);
369 while ((temp.length() % 8) != 0) temp.append('\0');
372 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
373 QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
374 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
375 temp2 += cipher.final().toByteArray();
382 if ((temp2.length() % 8) != 0)
385 temp2 = byteToB64(temp2);
392 //Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
393 QByteArray Cipher::byteToB64(QByteArray text)
399 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
401 while (k < (text.length() - 1)) {
403 v = text.at(k); if (v < 0) v += 256;
406 v = text.at(k); if (v < 0) v += 256;
409 v = text.at(k); if (v < 0) v += 256;
412 v = text.at(k); if (v < 0) v += 256;
416 v = text.at(k); if (v < 0) v += 256;
419 v = text.at(k); if (v < 0) v += 256;
422 v = text.at(k); if (v < 0) v += 256;
425 v = text.at(k); if (v < 0) v += 256;
428 for (int i = 0; i < 6; i++) {
429 encoded.append(base64.at(right & 0x3F).toLatin1());
433 //TODO make sure the .toLatin1 doesn't break anything
434 for (int i = 0; i < 6; i++) {
435 encoded.append(base64.at(left & 0x3F).toLatin1());
443 QByteArray Cipher::b64ToByte(QByteArray text)
445 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
448 while (k < (text.length() - 1)) {
455 for (int i = 0; i < 6; i++) {
457 v = base64.indexOf(text.at(k));
458 right |= v << (i * 6);
461 for (int i = 0; i < 6; i++) {
463 v = base64.indexOf(text.at(k));
464 left |= v << (i * 6);
467 for (int i = 0; i < 4; i++) {
468 w = ((left & (0xFF << ((3 - i) * 8))));
469 z = w >> ((3 - i) * 8);
470 if (z < 0) { z = z + 256; }
474 for (int i = 0; i < 4; i++) {
475 w = ((right & (0xFF << ((3 - i) * 8))));
476 z = w >> ((3 - i) * 8);
477 if (z < 0) { z = z + 256; }
485 bool Cipher::neededFeaturesAvailable()
487 QCA::Initializer init;
489 if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))