2
This program is free software; you can redistribute it and/or modify
3
it under the terms of the GNU General Public License as published by
4
the Free Software Foundation; either version 2 of the License, or
5
(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>
15
#include "preferences.h"
20
namespace Konversation
24
m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
28
Cipher::Cipher(QByteArray key, QString cipherType)
30
m_primeNum = QCA::BigInteger("12745216229761186769575009943944198619149164746831579719941140425076456621824834322853258804883232842877311723249782818608677050956745409379781245497526069657222703636504651898833151008222772087491045206203033063108075098874712912417029101508315117935752962862335062591404043092163187352352197487303798807791605274487594646923");
39
bool Cipher::setKey(QByteArray key)
44
if(key.mid(0,4).toLower() == "ecb:")
49
//strip cbc: if included
50
else if(key.mid(0,4).toLower() == "cbc:")
57
if(Preferences::self()->encryptionType())
66
bool Cipher::setType(const QString &type)
68
//TODO check QCA::isSupported()
73
QByteArray Cipher::decrypt(QByteArray cipherText)
75
QByteArray pfx = "(e) ";
76
bool error = false; // used to flag non cbc, seems like good practice not to parse w/o regard for set encryption type
79
if(cipherText.mid(0,5) == "+OK *")
83
cipherText = cipherText.mid(5);
87
cipherText = cipherText.mid(5);
88
pfx = "ERROR_NONECB: ";
93
else if(cipherText.mid(0,4) == "+OK " || cipherText.mid(0,5) == "mcps ")
98
cipherText = (cipherText.mid(0,4) == "+OK ") ? cipherText.mid(4) : cipherText.mid(5);
99
pfx = "ERROR_NONCBC: ";
105
if(cipherText.mid(0,4) == "+OK ")
106
cipherText = cipherText.mid(4);
108
cipherText = cipherText.mid(5);
111
//all other cases we fail
116
// (if cbc and no error we parse cbc) || (if ecb and error we parse cbc)
117
if((m_cbc && !error) || (!m_cbc && error))
119
cipherText = cipherText;
120
temp = blowfishCBC(cipherText, false);
122
if(temp == cipherText)
124
kDebug() << "Decryption from CBC Failed";
125
return "ERROR: "+cipherText+' '+'\n';
132
temp = blowfishECB(cipherText, false);
134
if(temp == cipherText)
136
kDebug() << "Decryption from ECB Failed";
137
return "ERROR: "+cipherText+' '+'\n';
142
// If it's a CTCP we don't want to have the (e) interfering with the processing
143
// TODO FIXME the proper fix for this is to show encryption differently e.g. [nick] instead of <nick>
144
// don't hate me for the mircryption reference there.
145
if (cipherText.at(0) == 1)
147
cipherText = pfx+cipherText+' '+'\n'; // FIXME(??) why is there an added space here?
151
QByteArray Cipher::initKeyExchange()
153
QCA::Initializer init;
154
m_tempKey = QCA::KeyGenerator().createDH(QCA::DLGroup(m_primeNum, QCA::BigInteger(2))).toDH();
156
if(m_tempKey.isNull())
159
QByteArray publicKey = m_tempKey.toPublicKey().toDH().y().toArray().toByteArray();
162
if(publicKey.length() > 135 && publicKey.at(0) == '\0')
163
publicKey = publicKey.mid(1);
165
return publicKey.toBase64().append('A');
168
QByteArray Cipher::parseInitKeyX(QByteArray key)
170
QCA::Initializer init;
172
if(key.length() != 181)
175
QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
176
QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
177
QCA::DHPrivateKey privateKey = QCA::KeyGenerator().createDH(group).toDH();
179
if(privateKey.isNull())
182
QByteArray publicKey = privateKey.y().toArray().toByteArray();
185
if(publicKey.length() > 135 && publicKey.at(0) == '\0')
186
publicKey = publicKey.mid(1);
188
QCA::DHPublicKey remotePub(group, remoteKey);
190
if(remotePub.isNull())
193
QByteArray sharedKey = privateKey.deriveKey(remotePub).toByteArray();
194
sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
196
//remove trailing = because mircryption and fish think it's a swell idea.
197
while(sharedKey.endsWith('=')) sharedKey.chop(1);
199
bool success = setKey(sharedKey);
204
return publicKey.toBase64().append('A');
207
bool Cipher::parseFinishKeyX(QByteArray key)
209
QCA::Initializer init;
211
if(key.length() != 181)
214
QCA::SecureArray remoteKey = QByteArray::fromBase64(key.left(180));
215
QCA::DLGroup group(m_primeNum, QCA::BigInteger(2));
217
QCA::DHPublicKey remotePub(group, remoteKey);
219
if(remotePub.isNull())
222
if(m_tempKey.isNull())
225
QByteArray sharedKey = m_tempKey.deriveKey(remotePub).toByteArray();
226
sharedKey = QCA::Hash("sha256").hash(sharedKey).toByteArray().toBase64();
228
//remove trailng = because mircryption and fish think it's a swell idea.
229
while(sharedKey.endsWith('=')) sharedKey.chop(1);
231
bool success = setKey(sharedKey);
236
QByteArray Cipher::decryptTopic(QByteArray cipherText)
238
if(cipherText.mid(0,4) == "+OK ")// FiSH style topic
239
cipherText = cipherText.mid(4);
240
else if(cipherText.left(5) == "«m«")
241
cipherText = cipherText.mid(5,cipherText.length()-10);
243
return "ERROR: "+cipherText;
246
//TODO currently no backwards sanity checks for topic, it seems to use different standards
247
//if somebody figures them out they can enable it and add "ERROR_NONECB/CBC" warnings
249
temp = blowfishCBC(cipherText.mid(1), false);
251
temp = blowfishECB(cipherText, false);
253
if(temp == cipherText)
255
return "ERROR: "+cipherText;
260
if(cipherText.mid(0,2) == "@@")
261
cipherText = cipherText.mid(2);
263
cipherText = "(e) "+cipherText;
267
bool Cipher::encrypt(QByteArray& cipherText)
269
if (cipherText.left(3) == "+p ") //don't encode if...?
270
cipherText = cipherText.mid(3);
273
if(m_cbc) //encode in ecb or cbc decide how to determine later
275
QByteArray temp = blowfishCBC(cipherText, true);
277
if(temp == cipherText)
279
kDebug() << "CBC Encoding Failed";
283
cipherText = "+OK *" + temp;
287
QByteArray temp = blowfishECB(cipherText, true);
289
if(temp == cipherText)
291
kDebug() << "ECB Encoding Failed";
295
cipherText = "+OK " + temp;
300
//THE BELOW WORKS AKA DO NOT TOUCH UNLESS YOU KNOW WHAT YOU'RE DOING
301
QByteArray Cipher::blowfishCBC(QByteArray cipherText, bool direction)
303
QCA::Initializer init;
304
QByteArray temp = cipherText;
307
// make sure cipherText is an interval of 8 bits. We do this before so that we
308
// know there's at least 8 bytes to en/decryption this ensures QCA doesn't fail
309
while((temp.length() % 8) != 0) temp.append('\0');
311
QCA::InitializationVector iv(8);
312
temp.prepend(iv.toByteArray()); // prefix with 8bits of IV for mircryptions *CUSTOM* cbc implementation
316
temp = QByteArray::fromBase64(temp);
317
//supposedly nescessary if we get a truncated message also allows for decryption of 'crazy'
318
//en/decoding clients that use STANDARDIZED PADDING TECHNIQUES
319
while((temp.length() % 8) != 0) temp.append('\0');
322
QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
323
QCA::Cipher cipher(m_type, QCA::Cipher::CBC, QCA::Cipher::NoPadding, dir, m_key, QCA::InitializationVector(QByteArray("0")));
324
QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
325
temp2 += cipher.final().toByteArray();
330
if(direction) //send in base64
331
temp2 = temp2.toBase64();
332
else //cut off the 8bits of IV
333
temp2 = temp2.remove(0,8);
338
QByteArray Cipher::blowfishECB(QByteArray cipherText, bool direction)
340
QCA::Initializer init;
341
QByteArray temp = cipherText;
343
//do padding ourselves
346
while((temp.length() % 8) != 0) temp.append('\0');
350
temp = b64ToByte(temp);
351
while((temp.length() % 8) != 0) temp.append('\0');
354
QCA::Direction dir = (direction) ? QCA::Encode : QCA::Decode;
355
QCA::Cipher cipher(m_type, QCA::Cipher::ECB, QCA::Cipher::NoPadding, dir, m_key);
356
QByteArray temp2 = cipher.update(QCA::MemoryRegion(temp)).toByteArray();
357
temp2 += cipher.final().toByteArray();
363
temp2 = byteToB64(temp2);
368
//Custom non RFC 2045 compliant Base64 enc/dec code for mircryption / FiSH compatibility
369
QByteArray Cipher::byteToB64(QByteArray text)
375
QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
377
while (k < (text.length() - 1)) {
379
v=text.at(k); if (v<0) v+=256;
382
v=text.at(k); if (v<0) v+=256;
385
v=text.at(k); if (v<0) v+=256;
388
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
v=text.at(k); if (v<0) v+=256;
401
v=text.at(k); if (v<0) v+=256;
404
for (int i = 0; i < 6; i++) {
405
encoded.append(base64.at(right & 0x3F).toAscii());
408
//TODO make sure the .toascii doesn't break anything
410
for (int i = 0; i < 6; i++) {
411
encoded.append(base64.at(left & 0x3F).toAscii());
418
QByteArray Cipher::b64ToByte(QByteArray text)
420
QString base64 = "./0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
423
while (k < (text.length() - 1)) {
430
for (int i = 0; i < 6; i++) {
432
v = base64.indexOf(text.at(k));
433
right |= v << (i * 6);
436
for (int i = 0; i < 6; i++) {
438
v = base64.indexOf(text.at(k));
439
left |= v << (i * 6);
442
for (int i = 0; i < 4; i++) {
443
w = ((left & (0xFF << ((3 - i) * 8))));
444
z = w >> ((3 - i) * 8);
445
if(z < 0) {z = z + 256;}
449
for (int i = 0; i < 4; i++) {
450
w = ((right & (0xFF << ((3 - i) * 8))));
451
z = w >> ((3 - i) * 8);
452
if(z < 0) {z = z + 256;}