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>
20
m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
24
Cipher::Cipher(QByteArray key, QString cipherType)
26
m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
34
bool Cipher::setKey(QByteArray key)
39
if(key.mid(0,4).toLower() == "ecb:")
44
//strip cbc: if included
45
else if(key.mid(0,4).toLower() == "cbc:")
52
// if(Preferences::self()->encryptionType())
61
bool Cipher::setType(const QString &type)
63
//TODO check QCA::isSupported()
68
QByteArray Cipher::decrypt(QByteArray cipherText)
71
bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
74
if(cipherText.mid(0,5) == "+OK *")
78
cipherText = cipherText.mid(5);
82
cipherText = cipherText.mid(5);
83
pfx = "ERROR_NONECB: ";
88
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: ";
100
if(cipherText.mid(0,4) == "+OK ")
101
cipherText = cipherText.mid(4);
103
cipherText = cipherText.mid(5);
106
//all other cases we fail
111
// (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
112
if((m_cbc && !error) || (!m_cbc && error))
114
cipherText = cipherText;
115
temp = blowfishCBC(cipherText, false);
117
if(temp == cipherText)
119
// kDebug("Decryption from CBC Failed");
120
return cipherText+' '+'\n';
127
temp = blowfishECB(cipherText, false);
129
if(temp == cipherText)
131
// kDebug("Decryption from ECB Failed");
132
return cipherText+' '+'\n';
137
// TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
138
// don't hate me for the mircryption reference there.
139
if (cipherText.at(0) == 1)
141
cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
145
QByteArray Cipher::initKeyExchange()
147
QCA::Initializer init;
148
m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
150
if(m_tempKey.isNull())
153
QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
156
if(publicKey.length() > 135 && publicKey.at(0) == '\0')
157
publicKey = publicKey.mid(1);
159
return publicKey.toBase64().append('A');
162
QByteArray Cipher::parseInitKeyX(QByteArray key)
164
QCA::Initializer init;
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('=')) sharedKey.chop(1);
193
bool success = setKey(sharedKey);
198
return publicKey.toBase64().append('A');
201
bool Cipher::parseFinishKeyX(QByteArray key)
203
QCA::Initializer init;
205
if(key.length() != 181)
208
QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
209
QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
211
QCA::DHPublicKey remotePub(group, remoteKey);
213
if(remotePub.isNull())
216
if(m_tempKey.isNull())
219
QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
220
sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
222
//remove trailng = because mircryption and fish think it's a swell idea.
223
while(sharedKey.endsWith('=')) sharedKey.chop(1);
225
bool success = setKey(sharedKey);
230
QByteArray Cipher::decryptTopic(QByteArray cipherText)
232
if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
233
cipherText = cipherText.mid(4);
234
else if(cipherText.left(5) == "«m«")
235
cipherText = cipherText.mid(5,cipherText.length()-10);
240
//TODO currently no backwards sanity checks for topic, it seems to use different standards
241
//if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
243
temp = blowfishCBC(cipherText.mid(1), false);
245
temp = blowfishECB(cipherText, false);
247
if(temp == cipherText)
254
if(cipherText.mid(0,2) == "@@")
255
cipherText = cipherText.mid(2);
260
bool Cipher::encrypt(QByteArray& cipherText)
262
if (cipherText.left(3) == "+p ") //don't encode if...?
263
cipherText = cipherText.mid(3);
266
if(m_cbc) //encode in ecb or cbc decide how to determine later
268
QByteArray temp = blowfishCBC(cipherText, true);
270
if(temp == cipherText)
272
// kDebug("CBC Encoding Failed");
276
cipherText = "+OK *" + temp;
280
QByteArray temp = blowfishECB(cipherText, true);
282
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;
301
// make sure cipherText is an interval of 8 bits. We do this before so that we
302
// know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
303
while((temp.length() % 8) != 0) temp.append('\0');
305
QCA::InitializationVector iv(8);
306
temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
310
temp = QByteArray::fromBase64(temp);
311
//supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
312
//en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
313
while((temp.length() % 8) != 0) temp.append('\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
340
while((temp.length() % 8) != 0) temp.append('\0');
344
temp = b64ToByte(temp);
345
while((temp.length() % 8) != 0) temp.append('\0');
348
QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
349
QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
350
QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
351
temp2 += cipher.final().toByteArray();
357
temp2 = byteToB64(temp2);
362
//Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
363
QByteArray Cipher::byteToB64(QByteArray text)
369
QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
371
while (k < (text.length() - 1)) {
373
v=text.at(k); if (v<0) v+=256;
376
v=text.at(k); if (v<0) v+=256;
379
v=text.at(k); if (v<0) v+=256;
382
v=text.at(k); if (v<0) v+=256;
386
v=text.at(k); if (v<0) v+=256;
389
v=text.at(k); if (v<0) v+=256;
392
v=text.at(k); if (v<0) v+=256;
395
v=text.at(k); if (v<0) v+=256;
398
for (int i = 0; i < 6; i++) {
399
encoded.append(base64.at(right & 0x3F).toAscii());
403
//TODO make sure the .toascii doesn't break anything
404
for (int i = 0; i < 6; i++) {
405
encoded.append(base64.at(left & 0x3F).toAscii());
412
QByteArray Cipher::b64ToByte(QByteArray text)
414
QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
417
while (k < (text.length() - 1)) {
424
for (int i = 0; i < 6; i++) {
426
v = base64.indexOf(text.at(k));
427
right |= v << (i * 6);
430
for (int i = 0; i < 6; i++) {
432
v = base64.indexOf(text.at(k));
433
left |= v << (i * 6);
436
for (int i = 0; i < 4; i++) {
437
w = ((left & (0xFF << ((3 - i) * 8))));
438
z = w >> ((3 - i) * 8);
439
if(z < 0) {z = z + 256;}
443
for (int i = 0; i < 4; i++) {
444
w = ((right & (0xFF << ((3 - i) * 8))));
445
z = w >> ((3 - i) * 8);
446
if(z < 0) {z = z + 256;}