1 /***************************************************************************
2 * Copyright (C) 2005-09 by the Quassel Project *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) version 3. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************/
21 Copyright (C) 1997 Robey Pointer <robeypointer@gmail.com>
22 Copyright (C) 2005 Ismail Donmez <ismail@kde.org>
23 Copyright (C) 2009 Travis McHenry <tmchenryaz@cox.net>
24 Copyright (C) 2009 Johannes Huber <johu@gmx.de>
32 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
36 Cipher::Cipher(QByteArray key, QString cipherType)
38 m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
46 bool Cipher::setKey(QByteArray key)
51 if(key.mid(0,4).toLower() == "ecb:")
56 //strip cbc: if included
57 else if(key.mid(0,4).toLower() == "cbc:")
64 // if(Preferences::self()->encryptionType())
73 bool Cipher::setType(const QString &type)
75 //TODO check QCA::isSupported()
80 QByteArray Cipher::decrypt(QByteArray cipherText)
83 bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
86 if(cipherText.mid(0,5) == "+OK *")
90 cipherText = cipherText.mid(5);
94 cipherText = cipherText.mid(5);
95 pfx = "ERROR_NONECB: ";
100 else if(cipherText.mid(0,4) == "+OK " || cipherText.mid(0,5) == "mcps ")
105 cipherText = (cipherText.mid(0,4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
106 pfx = "ERROR_NONCBC: ";
112 if(cipherText.mid(0,4) == "+OK ")
113 cipherText = cipherText.mid(4);
115 cipherText = cipherText.mid(5);
118 //all other cases we fail
123 // (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
124 if((m_cbc && !error) || (!m_cbc && error))
126 cipherText = cipherText;
127 temp = blowfishCBC(cipherText, false);
129 if(temp == cipherText)
131 // kDebug("Decryption from CBC Failed");
132 return cipherText+' '+'\n';
139 temp = blowfishECB(cipherText, false);
141 if(temp == cipherText)
143 // kDebug("Decryption from ECB Failed");
144 return cipherText+' '+'\n';
149 // TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
150 // don't hate me for the mircryption reference there.
151 if (cipherText.at(0) == 1)
153 cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
157 QByteArray Cipher::initKeyExchange()
159 QCA::Initializer init;
160 m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
162 if(m_tempKey.isNull())
165 QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
168 if(publicKey.length() > 135 && publicKey.at(0) == '\0')
169 publicKey = publicKey.mid(1);
171 return publicKey.toBase64().append('A');
174 QByteArray Cipher::parseInitKeyX(QByteArray key)
176 QCA::Initializer init;
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);
205 bool success = setKey(sharedKey);
210 return publicKey.toBase64().append('A');
213 bool Cipher::parseFinishKeyX(QByteArray key)
215 QCA::Initializer init;
217 if(key.length() != 181)
220 QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
221 QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
223 QCA::DHPublicKey remotePub(group, remoteKey);
225 if(remotePub.isNull())
228 if(m_tempKey.isNull())
231 QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
232 sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
234 //remove trailng = because mircryption and fish think it's a swell idea.
235 while(sharedKey.endsWith('=')) sharedKey.chop(1);
237 bool success = setKey(sharedKey);
242 QByteArray Cipher::decryptTopic(QByteArray cipherText)
244 if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
245 cipherText = cipherText.mid(4);
246 else if(cipherText.left(5) == "«m«")
247 cipherText = cipherText.mid(5,cipherText.length()-10);
252 //TODO currently no backwards sanity checks for topic, it seems to use different standards
253 //if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
255 temp = blowfishCBC(cipherText.mid(1), false);
257 temp = blowfishECB(cipherText, false);
259 if(temp == cipherText)
266 if(cipherText.mid(0,2) == "@@")
267 cipherText = cipherText.mid(2);
272 bool Cipher::encrypt(QByteArray& cipherText)
274 if (cipherText.left(3) == "+p ") //don't encode if...?
275 cipherText = cipherText.mid(3);
278 if(m_cbc) //encode in ecb or cbc decide how to determine later
280 QByteArray temp = blowfishCBC(cipherText, true);
282 if(temp == cipherText)
284 // kDebug("CBC Encoding Failed");
288 cipherText = "+OK *" + temp;
292 QByteArray temp = blowfishECB(cipherText, true);
294 if(temp == cipherText)
296 // kDebug("ECB Encoding Failed");
300 cipherText = "+OK " + temp;
306 //THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
307 QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
309 QCA::Initializer init;
310 QByteArray temp = cipherText;
313 // make sure cipherText is an interval of 8 bits. We do this before so that we
314 // know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
315 while((temp.length() % 8) != 0) temp.append('\0');
317 QCA::InitializationVector iv(8);
318 temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
322 temp = QByteArray::fromBase64(temp);
323 //supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
324 //en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
325 while((temp.length() % 8) != 0) temp.append('\0');
328 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
329 QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
330 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
331 temp2 += cipher.final().toByteArray();
336 if(direction) //send in base64
337 temp2 = temp2.toBase64();
338 else //cut off the 8bits of IV
339 temp2 = temp2.remove(0,8);
344 QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
346 QCA::Initializer init;
347 QByteArray temp = cipherText;
349 //do padding ourselves
352 while((temp.length() % 8) != 0) temp.append('\0');
356 temp = b64ToByte(temp);
357 while((temp.length() % 8) != 0) temp.append('\0');
360 QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
361 QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
362 QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
363 temp2 += cipher.final().toByteArray();
369 temp2 = byteToB64(temp2);
374 //Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
375 QByteArray Cipher::byteToB64(QByteArray text)
381 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
383 while (k < (text.length() - 1)) {
385 v=text.at(k); if (v<0) v+=256;
388 v=text.at(k); if (v<0) v+=256;
391 v=text.at(k); if (v<0) v+=256;
394 v=text.at(k); if (v<0) v+=256;
398 v=text.at(k); if (v<0) v+=256;
401 v=text.at(k); if (v<0) v+=256;
404 v=text.at(k); if (v<0) v+=256;
407 v=text.at(k); if (v<0) v+=256;
410 for (int i = 0; i < 6; i++) {
411 encoded.append(base64.at(right & 0x3F).toAscii());
415 //TODO make sure the .toascii doesn't break anything
416 for (int i = 0; i < 6; i++) {
417 encoded.append(base64.at(left & 0x3F).toAscii());
424 QByteArray Cipher::b64ToByte(QByteArray text)
426 QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
429 while (k < (text.length() - 1)) {
436 for (int i = 0; i < 6; i++) {
438 v = base64.indexOf(text.at(k));
439 right |= v << (i * 6);
442 for (int i = 0; i < 6; i++) {
444 v = base64.indexOf(text.at(k));
445 left |= v << (i * 6);
448 for (int i = 0; i < 4; i++) {
449 w = ((left & (0xFF << ((3 - i) * 8))));
450 z = w >> ((3 - i) * 8);
451 if(z < 0) {z = z + 256;}
455 for (int i = 0; i < 4; i++) {
456 w = ((right & (0xFF << ((3 - i) * 8))));
457 z = w >> ((3 - i) * 8);
458 if(z < 0) {z = z + 256;}