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>
21 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
25 Cipher::Cipher(QByteArray key, QString cipherType)
27 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
35 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())
62 bool Cipher::setType(const QString &type)
64 //TODO check QCA::isSupported()
69 QByteArray Cipher::decrypt(QByteArray cipherText)
72 bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
75 if(cipherText.mid(0,5) == "+OK *")
79 cipherText = cipherText.mid(5);
83 cipherText = cipherText.mid(5);
84 pfx = "ERROR_NONECB: ";
89 else if(cipherText.mid(0,4) == "+OK " || cipherText.mid(0,5) == "mcps ")
94 cipherText = (cipherText.mid(0,4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
95 pfx = "ERROR_NONCBC: ";
101 if(cipherText.mid(0,4) == "+OK ")
102 cipherText = cipherText.mid(4);
104 cipherText = cipherText.mid(5);
107 //all other cases we fail
112 // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
113 if((m_cbc && !error) || (!m_cbc && error))
115 cipherText = cipherText;
116 temp = blowfishCBC(cipherText, false);
118 if(temp == cipherText)
120 // kDebug("Decryption from CBC Failed");
121 return cipherText+' '+'\n';
128 temp = blowfishECB(cipherText, false);
130 if(temp == cipherText)
132 // kDebug("Decryption from ECB Failed");
133 return cipherText+' '+'\n';
138 // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
139 // don't hate me for the mircryption reference there.
140 if (cipherText.at(0) == 1)
142 cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
146 QByteArray Cipher::initKeyExchange()
148 QCA::Initializer init;
149 m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
151 if(m_tempKey.isNull())
154 QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
157 if(publicKey.length() > 135 && publicKey.at(0) == '\0')
158 publicKey = publicKey.mid(1);
160 return publicKey.toBase64().append('A');
163 QByteArray Cipher::parseInitKeyX(QByteArray key)
165 QCA::Initializer init;
167 if(key.length() != 181)
170 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
171 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
172 QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
174 if(privateKey.isNull())
177 QByteArray publicKey = privateKey.y().toArray().toByteArray();
180 if(publicKey.length() > 135 && publicKey.at(0) == '\0')
181 publicKey = publicKey.mid(1);
183 QCA::DHPublicKey remotePub(group, remoteKey);
185 if(remotePub.isNull())
188 QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
189 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
191 //remove trailing = because mircryption and fish think it's a swell idea.
192 while(sharedKey.endsWith('=')) sharedKey.chop(1);
194 bool success = setKey(sharedKey);
199 return publicKey.toBase64().append('A');
202 bool Cipher::parseFinishKeyX(QByteArray key)
204 QCA::Initializer init;
206 if(key.length() != 181)
209 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
210 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
212 QCA::DHPublicKey remotePub(group, remoteKey);
214 if(remotePub.isNull())
217 if(m_tempKey.isNull())
220 QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
221 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
223 //remove trailng = because mircryption and fish think it's a swell idea.
224 while(sharedKey.endsWith('=')) sharedKey.chop(1);
226 bool success = setKey(sharedKey);
231 QByteArray Cipher::decryptTopic(QByteArray cipherText)
233 if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
234 cipherText = cipherText.mid(4);
235 else if(cipherText.left(5) == "«m«")
236 cipherText = cipherText.mid(5,cipherText.length()-10);
241 //TODO currently no backwards sanity checks for topic, it seems to use different standards
242 //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
244 temp = blowfishCBC(cipherText.mid(1), false);
246 temp = blowfishECB(cipherText, false);
248 if(temp == cipherText)
255 if(cipherText.mid(0,2) == "@@")
256 cipherText = cipherText.mid(2);
261 bool Cipher::encrypt(QByteArray& cipherText)
263 if (cipherText.left(3) == "+p ") //don't encode if...?
264 cipherText = cipherText.mid(3);
267 if(m_cbc) //encode in ecb or cbc decide how to determine later
269 QByteArray temp = blowfishCBC(cipherText, true);
271 if(temp == cipherText)
273 // kDebug("CBC Encoding Failed");
277 cipherText = "+OK *" + temp;
281 QByteArray temp = blowfishECB(cipherText, true);
283 if(temp == cipherText)
285 // kDebug("ECB Encoding Failed");
289 cipherText = "+OK " + temp;
295 //THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
296 QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
298 QCA::Initializer init;
299 QByteArray temp = cipherText;
302 // make sure cipherText is an interval of 8 bits. We do this before so that we
303 // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
304 while((temp.length() % 8) != 0) temp.append('\0');
306 QCA::InitializationVector iv(8);
307 temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
311 temp = QByteArray::fromBase64(temp);
312 //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
313 //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
314 while((temp.length() % 8) != 0) temp.append('\0');
317 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
318 QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
319 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
320 temp2 += cipher.final().toByteArray();
325 if(direction) //send in base64
326 temp2 = temp2.toBase64();
327 else //cut off the 8bits of IV
328 temp2 = temp2.remove(0,8);
333 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
335 QCA::Initializer init;
336 QByteArray temp = cipherText;
338 //do padding ourselves
341 while((temp.length() % 8) != 0) temp.append('\0');
345 temp = b64ToByte(temp);
346 while((temp.length() % 8) != 0) temp.append('\0');
349 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
350 QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
351 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
352 temp2 += cipher.final().toByteArray();
358 temp2 = byteToB64(temp2);
363 //Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
364 QByteArray Cipher::byteToB64(QByteArray text)
370 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
372 while (k < (text.length() - 1)) {
374 v=text.at(k); if (v<0) v+=256;
377 v=text.at(k); if (v<0) v+=256;
380 v=text.at(k); if (v<0) v+=256;
383 v=text.at(k); if (v<0) v+=256;
387 v=text.at(k); if (v<0) v+=256;
390 v=text.at(k); if (v<0) v+=256;
393 v=text.at(k); if (v<0) v+=256;
396 v=text.at(k); if (v<0) v+=256;
399 for (int i = 0; i < 6; i++) {
400 encoded.append(base64.at(right & 0x3F).toAscii());
404 //TODO make sure the .toascii doesn't break anything
405 for (int i = 0; i < 6; i++) {
406 encoded.append(base64.at(left & 0x3F).toAscii());
413 QByteArray Cipher::b64ToByte(QByteArray text)
415 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
418 while (k < (text.length() - 1)) {
425 for (int i = 0; i < 6; i++) {
427 v = base64.indexOf(text.at(k));
428 right |= v << (i * 6);
431 for (int i = 0; i < 6; i++) {
433 v = base64.indexOf(text.at(k));
434 left |= v << (i * 6);
437 for (int i = 0; i < 4; i++) {
438 w = ((left & (0xFF << ((3 - i) * 8))));
439 z = w >> ((3 - i) * 8);
440 if(z < 0) {z = z + 256;}
444 for (int i = 0; i < 4; i++) {
445 w = ((right & (0xFF << ((3 - i) * 8))));
446 z = w >> ((3 - i) * 8);
447 if(z < 0) {z = z + 256;}
454 bool Cipher::neededFeaturesAvailable()
456 QCA::Initializer init;
458 if (QCA::isSupported("blowfish-ecb") && QCA::isSupported("blowfish-cbc") && QCA::isSupported("dh"))
461 qWarning() << "QCA provider plugin not found. It is usually provided by the qca-ossl plugin.";