1 /*
2    Copyright (c) 2011, Andre Somers
3    All rights reserved.
4 
5    Redistribution and use in source and binary forms, with or without
6    modification, are permitted provided that the following conditions are met:
7  * Redistributions of source code must retain the above copyright
8       notice, this list of conditions and the following disclaimer.
9  * Redistributions in binary form must reproduce the above copyright
10       notice, this list of conditions and the following disclaimer in the
11       documentation and/or other materials provided with the distribution.
12  * Neither the name of the Rathenau Instituut, Andre Somers nor the
13       names of its contributors may be used to endorse or promote products
14       derived from this software without specific prior written permission.
15 
16    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17    ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19    DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
20    DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22    LOSS OF USE, DATA, OR #######; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23    ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "3rd-party/sc/simplecrypt.h"
29 
30 #include <QByteArray>
31 #include <QCryptographicHash>
32 #include <QDataStream>
33 #include <QDateTime>
34 #include <QIODevice>
35 #include <QRandomGenerator>
36 #include <QtDebug>
37 #include <QtGlobal>
38 
39 SimpleCrypt::SimpleCrypt() :
40   m_key(0),
41   m_compressionMode(CompressionAlways),
42   m_protectionMode(ProtectionHash),
43   m_lastError(ErrorNoError) {}
44 
45 SimpleCrypt::SimpleCrypt(quint64 key) :
46   m_key(key),
47   m_compressionMode(CompressionAlways),
48   m_protectionMode(ProtectionHash),
49   m_lastError(ErrorNoError) {
50   splitKey();
51 }
52 
53 void SimpleCrypt::setKey(quint64 key) {
54   m_key = key;
55   splitKey();
56 }
57 
58 void SimpleCrypt::splitKey() {
59   m_keyParts.clear();
60   m_keyParts.resize(8);
61 
62   for (int i = 0; i < 8; i++) {
63     quint64 part = m_key;
64 
65     for (int j = i; j > 0; j--) {
66       part = part >> 8;
67     }
68 
69     part = part & 0xff;
70     m_keyParts[i] = static_cast<char>(part);
71   }
72 }
73 
74 QByteArray SimpleCrypt::encryptToByteArray(const QString& plaintext) {
75   return encryptToByteArray(plaintext.toUtf8());
76 }
77 
78 QByteArray SimpleCrypt::encryptToByteArray(QByteArray plaintext) {
79   if (m_keyParts.isEmpty()) {
80     qWarning() << "No key set.";
81     m_lastError = ErrorNoKeySet;
82     return QByteArray();
83   }
84 
85   QByteArray ba = plaintext;
86   CryptoFlags flags = CryptoFlagNone;
87 
88   if (m_compressionMode == CompressionAlways) {
89     ba = qCompress(ba, 9); //maximum compression
90     flags |= CryptoFlagCompression;
91   }
92   else if (m_compressionMode == CompressionAuto) {
93     QByteArray compressed = qCompress(ba, 9);
94 
95     if (compressed.count() < ba.count()) {
96       ba = compressed;
97       flags |= CryptoFlagCompression;
98     }
99   }
100 
101   QByteArray integrityProtection;
102 
103   if (m_protectionMode == ProtectionChecksum) {
104     flags |= CryptoFlagChecksum;
105     QDataStream s(&integrityProtection, QIODevice::WriteOnly);
106 
107     s << qChecksum(ba.constData(), ba.length());
108   }
109   else if (m_protectionMode == ProtectionHash) {
110     flags |= CryptoFlagHash;
111     QCryptographicHash hash(QCryptographicHash::Sha1);
112 
113     hash.addData(ba);
114     integrityProtection += hash.result();
115   }
116 
117   //prepend a random char to the string
118   char randomChar = char(QRandomGenerator::global()->generate() & 0xFF);
119 
120   ba = randomChar + integrityProtection + ba;
121   int pos(0);
122   char lastChar(0);
123   int cnt = ba.count();
124 
125   while (pos < cnt) {
126     ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
127     lastChar = ba.at(pos);
128     ++pos;
129   }
130 
131   QByteArray resultArray;
132 
133   resultArray.append(char(0x03));  //version for future updates to algorithm
134   resultArray.append(char(flags)); //encryption flags
135   resultArray.append(ba);
136   m_lastError = ErrorNoError;
137   return resultArray;
138 }
139 
140 QString SimpleCrypt::encryptToString(const QString& plaintext) {
141   QByteArray plaintextArray = plaintext.toUtf8();
142   QByteArray cypher = encryptToByteArray(plaintextArray);
143   QString cypherString = QString::fromLatin1(cypher.toBase64());
144 
145   return cypherString;
146 }
147 
148 QString SimpleCrypt::encryptToString(QByteArray plaintext) {
149   QByteArray cypher = encryptToByteArray(plaintext);
150   QString cypherString = QString::fromLatin1(cypher.toBase64());
151 
152   return cypherString;
153 }
154 
155 QString SimpleCrypt::decryptToString(const QString& cyphertext) {
156   QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
157   QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
158   QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
159 
160   return plaintext;
161 }
162 
163 QString SimpleCrypt::decryptToString(QByteArray cypher) {
164   QByteArray ba = decryptToByteArray(cypher);
165   QString plaintext = QString::fromUtf8(ba, ba.size());
166 
167   return plaintext;
168 }
169 
170 QByteArray SimpleCrypt::decryptToByteArray(const QString& cyphertext) {
171   QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
172   QByteArray ba = decryptToByteArray(cyphertextArray);
173 
174   return ba;
175 }
176 
177 QByteArray SimpleCrypt::decryptToByteArray(QByteArray cypher) {
178   if (m_keyParts.isEmpty()) {
179     qWarning() << "No key set.";
180     m_lastError = ErrorNoKeySet;
181     return QByteArray();
182   }
183 
184   QByteArray ba = cypher;
185 
186   if (cypher.count() < 3) {
187     return QByteArray();
188   }
189 
190   char version = ba.at(0);
191 
192   if (version != 3) { //we only work with version 3
193     m_lastError = ErrorUnknownVersion;
194     qWarning() << "Invalid version or not a cyphertext.";
195     return QByteArray();
196   }
197 
198   CryptoFlags flags = CryptoFlags(ba.at(1));
199 
200   ba = ba.mid(2);
201   int pos(0);
202   int cnt(ba.count());
203   char lastChar = 0;
204 
205   while (pos < cnt) {
206     char currentChar = ba[pos];
207 
208     ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
209     lastChar = currentChar;
210     ++pos;
211   }
212 
213   ba = ba.mid(1); //chop off the random number at the start
214   bool integrityOk(true);
215 
216   if (flags.testFlag(CryptoFlagChecksum)) {
217     if (ba.length() < 2) {
218       m_lastError = ErrorIntegrityFailed;
219       return QByteArray();
220     }
221 
222     quint16 storedChecksum;
223 
224     {
225       QDataStream s(&ba, QIODevice::ReadOnly);
226 
227       s >> storedChecksum;
228     }
229 
230     ba = ba.mid(2);
231     quint16 checksum = qChecksum(ba.constData(), ba.length());
232 
233     integrityOk = (checksum == storedChecksum);
234   }
235   else if (flags.testFlag(CryptoFlagHash)) {
236     if (ba.length() < 20) {
237       m_lastError = ErrorIntegrityFailed;
238       return QByteArray();
239     }
240 
241     QByteArray storedHash = ba.left(20);
242 
243     ba = ba.mid(20);
244     QCryptographicHash hash(QCryptographicHash::Sha1);
245 
246     hash.addData(ba);
247     integrityOk = (hash.result() == storedHash);
248   }
249 
250   if (!integrityOk) {
251     m_lastError = ErrorIntegrityFailed;
252     return QByteArray();
253   }
254 
255   if (flags.testFlag(CryptoFlagCompression)) {
256     ba = qUncompress(ba);
257   }
258 
259   m_lastError = ErrorNoError;
260   return ba;
261 }
262