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>
17 #include "logmessage.h"
21 m_primeNum = QCA::BigInteger(
22 "1274521622976118676957500994394419861914916474683157971994114042507645662182483432285325880488323284287731172324978281860867705095"
23 "6745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862"
24 "335062591404043092163187352352197487303798807791605274487594646923");
28 Cipher::Cipher(QByteArray key, QString cipherType)
30 m_primeNum = QCA::BigInteger(
31 "1274521622976118676957500994394419861914916474683157971994114042507645662182483432285325880488323284287731172324978281860867705095"
32 "6745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862"
33 "335062591404043092163187352352197487303798807791605274487594646923");
38 bool Cipher::setKey(QByteArray key)
45 if (key.mid(0, 4).toLower() == "ecb:") {
49 // strip cbc: if included
50 else if (key.mid(0, 4).toLower() == "cbc:") {
55 // if(Preferences::self()->encryptionType())
65 bool Cipher::setType(const QString& type)
67 // 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 *") {
81 cipherText = cipherText.mid(5);
84 cipherText = cipherText.mid(5);
85 pfx = "ERROR_NONECB: ";
90 else if (cipherText.mid(0, 4) == "+OK " || cipherText.mid(0, 5) == "mcps ") {
93 cipherText = (cipherText.mid(0, 4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
94 pfx = "ERROR_NONCBC: ";
99 if (cipherText.mid(0, 4) == "+OK ")
100 cipherText = cipherText.mid(4);
102 cipherText = cipherText.mid(5);
105 // all other cases we fail
110 // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
111 if ((m_cbc && !error) || (!m_cbc && error)) {
112 temp = blowfishCBC(cipherText, false);
114 if (temp == cipherText) {
115 // kDebug("Decryption from CBC Failed");
116 return cipherText + ' ' + '\n';
122 temp = blowfishECB(cipherText, false);
124 if (temp == cipherText) {
125 // kDebug("Decryption from ECB Failed");
126 return cipherText + ' ' + '\n';
131 // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
132 // don't hate me for the mircryption reference there.
133 if (cipherText.at(0) == 1)
135 cipherText = pfx + cipherText + ' ' + '\n'; // FIXME(??) why is there an added space here?
139 QByteArray Cipher::initKeyExchange()
141 QCA::Initializer init;
142 m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
144 if (m_tempKey.isNull())
147 QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
150 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
151 publicKey = publicKey.mid(1);
153 return publicKey.toBase64().append('A');
156 QByteArray Cipher::parseInitKeyX(QByteArray key)
158 QCA::Initializer init;
161 if (key.endsWith(" CBC")) {
166 if (key.length() != 181)
169 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
170 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
171 QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
173 if (privateKey.isNull())
176 QByteArray publicKey = privateKey.y().toArray().toByteArray();
179 if (publicKey.length() > 135 && publicKey.at(0) == '\0')
180 publicKey = publicKey.mid(1);
182 QCA::DHPublicKey remotePub(group, remoteKey);
184 if (remotePub.isNull())
187 QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
188 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
190 // remove trailing = because mircryption and fish think it's a swell idea.
191 while (sharedKey.endsWith('='))
195 sharedKey.prepend("cbc:");
197 bool success = setKey(sharedKey);
202 return publicKey.toBase64().append('A');
205 bool Cipher::parseFinishKeyX(QByteArray key)
207 QCA::Initializer init;
209 if (key.length() != 181)
212 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
213 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
215 QCA::DHPublicKey remotePub(group, remoteKey);
217 if (remotePub.isNull())
220 if (m_tempKey.isNull())
223 QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
224 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
226 // remove trailng = because mircryption and fish think it's a swell idea.
227 while (sharedKey.endsWith('='))
230 bool success = setKey(sharedKey);
235 QByteArray Cipher::decryptTopic(QByteArray cipherText)
237 if (cipherText.mid(0, 4) == "+OK ") // FiSH style topic
238 cipherText = cipherText.mid(4);
239 else if (cipherText.left(5) == "«m«")
240 cipherText = cipherText.mid(5, cipherText.length() - 10);
245 // TODO currently no backwards sanity checks for topic, it seems to use different standards
246 // if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
248 temp = blowfishCBC(cipherText.mid(1), false);
250 temp = blowfishECB(cipherText, false);
252 if (temp == cipherText) {
258 if (cipherText.mid(0, 2) == "@@")
259 cipherText = cipherText.mid(2);
264 bool Cipher::encrypt(QByteArray& cipherText)
266 if (cipherText.left(3) == "+p ") // don't encode if...?
267 cipherText = cipherText.mid(3);
269 if (m_cbc) // encode in ecb or cbc decide how to determine later
271 QByteArray temp = blowfishCBC(cipherText, true);
273 if (temp == cipherText) {
274 // kDebug("CBC Encoding Failed");
278 cipherText = "+OK *" + temp;
281 QByteArray temp = blowfishECB(cipherText, true);
283 if (temp == cipherText) {
284 // kDebug("ECB Encoding Failed");
288 cipherText = "+OK " + temp;
294 // THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
295 QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
297 QCA::Initializer init;
298 QByteArray temp = cipherText;
300 // make sure cipherText is an interval of 8 bits. We do this before so that we
301 // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
302 while ((temp.length() % 8) != 0)
305 QCA::InitializationVector iv(8);
306 temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
309 temp = QByteArray::fromBase64(temp);
310 // supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
311 // en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
312 while ((temp.length() % 8) != 0)
316 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
317 QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
318 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
319 temp2 += cipher.final().toByteArray();
324 if (direction) // send in base64
325 temp2 = temp2.toBase64();
326 else // cut off the 8bits of IV
327 temp2 = temp2.remove(0, 8);
332 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
334 QCA::Initializer init;
335 QByteArray temp = cipherText;
337 // do padding ourselves
339 while ((temp.length() % 8) != 0)
343 // ECB Blowfish encodes in blocks of 12 chars, so anything else is malformed input
344 if ((temp.length() % 12) != 0)
347 temp = b64ToByte(temp);
348 while ((temp.length() % 8) != 0)
352 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
353 QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
354 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
355 temp2 += cipher.final().toByteArray();
362 if ((temp2.length() % 8) != 0)
365 temp2 = byteToB64(temp2);
371 // Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
372 QByteArray Cipher::byteToB64(QByteArray text)
378 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
380 while (k < (text.length() - 1)) {
423 for (int i = 0; i < 6; i++) {
424 encoded.append(base64.at(right & 0x3F).toLatin1());
428 // TODO make sure the .toLatin1 doesn't break anything
429 for (int i = 0; i < 6; i++) {
430 encoded.append(base64.at(left & 0x3F).toLatin1());
437 QByteArray Cipher::b64ToByte(QByteArray text)
439 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
442 while (k < (text.length() - 1)) {
449 for (int i = 0; i < 6; i++) {
451 v = base64.indexOf(text.at(k));
452 right |= v << (i * 6);
455 for (int i = 0; i < 6; i++) {
457 v = base64.indexOf(text.at(k));
458 left |= v << (i * 6);
461 for (int i = 0; i < 4; i++) {
462 w = ((left & (0xFF << ((3 - i) * 8))));
463 z = w >> ((3 - i) * 8);
470 for (int i = 0; i < 4; i++) {
471 w = ((right & (0xFF << ((3 - i) * 8))));
472 z = w >> ((3 - i) * 8);
482 bool Cipher::neededFeaturesAvailable()
484 QCA::Initializer init;
486 if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))