File indexing completed on 2025-01-19 03:55:40

0001 /*
0002 Copyright (c) 2011, Andre Somers
0003 All rights reserved.
0004 
0005 Redistribution and use in source and binary forms, with or without
0006 modification, are permitted provided that the following conditions are met:
0007     * Redistributions of source code must retain the above copyright
0008       notice, this list of conditions and the following disclaimer.
0009     * Redistributions in binary form must reproduce the above copyright
0010       notice, this list of conditions and the following disclaimer in the
0011       documentation and/or other materials provided with the distribution.
0012     * Neither the name of the Rathenau Instituut, Andre Somers nor the
0013       names of its contributors may be used to endorse or promote products
0014       derived from this software without specific prior written permission.
0015 
0016 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
0017 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
0018 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0019 DISCLAIMED. IN NO EVENT SHALL ANDRE SOMERS BE LIABLE FOR ANY
0020 DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
0021 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
0022 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0023 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0024 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
0025 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0026 */
0027 #include "o0simplecrypt.h"
0028 #include <QByteArray>
0029 #include <QtDebug>
0030 #include <QtGlobal>
0031 #include <QDateTime>
0032 #include <QCryptographicHash>
0033 #include <QDataStream>
0034 #include <QIODevice>
0035 
0036 O0SimpleCrypt::O0SimpleCrypt():
0037     m_key(0),
0038     m_compressionMode(CompressionAuto),
0039     m_protectionMode(ProtectionChecksum),
0040     m_lastError(ErrorNoError)
0041 {
0042 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0043     qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
0044 #else
0045     m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
0046 #endif
0047 }
0048 
0049 O0SimpleCrypt::O0SimpleCrypt(quint64 key):
0050     m_key(key),
0051     m_compressionMode(CompressionAuto),
0052     m_protectionMode(ProtectionChecksum),
0053     m_lastError(ErrorNoError)
0054 {
0055 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0056     qsrand(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
0057 #else
0058     m_rand.seed(uint(QDateTime::currentMSecsSinceEpoch() & 0xFFFF));
0059 #endif
0060     splitKey();
0061 }
0062 
0063 void O0SimpleCrypt::setKey(quint64 key)
0064 {
0065     m_key = key;
0066     splitKey();
0067 }
0068 
0069 void O0SimpleCrypt::splitKey()
0070 {
0071     m_keyParts.clear();
0072     m_keyParts.resize(8);
0073     for (int i=0;i<8;i++) {
0074         quint64 part = m_key;
0075         for (int j=i; j>0; j--)
0076             part = part >> 8;
0077         part = part & 0xff;
0078         m_keyParts[i] = static_cast<char>(part);
0079     }
0080 }
0081 
0082 QByteArray O0SimpleCrypt::encryptToByteArray(const QString& plaintext)
0083 {
0084     QByteArray plaintextArray = plaintext.toUtf8();
0085     return encryptToByteArray(plaintextArray);
0086 }
0087 
0088 QByteArray O0SimpleCrypt::encryptToByteArray(QByteArray plaintext)
0089 {
0090     if (m_keyParts.isEmpty()) {
0091         qWarning() << "No key set.";
0092         m_lastError = ErrorNoKeySet;
0093         return QByteArray();
0094     }
0095 
0096 
0097     QByteArray ba = plaintext;
0098 
0099     CryptoFlags flags = CryptoFlagNone;
0100     if (m_compressionMode == CompressionAlways) {
0101         ba = qCompress(ba, 9); //maximum compression
0102         flags |= CryptoFlagCompression;
0103     } else if (m_compressionMode == CompressionAuto) {
0104         QByteArray compressed = qCompress(ba, 9);
0105         if (compressed.count() < ba.count()) {
0106             ba = compressed;
0107             flags |= CryptoFlagCompression;
0108         }
0109     }
0110 
0111     QByteArray integrityProtection;
0112     if (m_protectionMode == ProtectionChecksum) {
0113         flags |= CryptoFlagChecksum;
0114         QDataStream s(&integrityProtection, QIODevice::WriteOnly);
0115 #if QT_VERSION >= 0x060000
0116         s << qChecksum(QByteArrayView(ba));
0117 #else
0118         s << qChecksum(ba.constData(), ba.length());
0119 #endif
0120     } else if (m_protectionMode == ProtectionHash) {
0121         flags |= CryptoFlagHash;
0122         QCryptographicHash hash(QCryptographicHash::Sha1);
0123         hash.addData(ba);
0124 
0125         integrityProtection += hash.result();
0126     }
0127 
0128     //prepend a random char to the string
0129 #if (QT_VERSION < QT_VERSION_CHECK(5, 15, 0))
0130     char randomChar = char(qrand() & 0xFF);
0131 #else
0132     char randomChar = char(m_rand.generate() & 0xFF);
0133 #endif
0134     ba = randomChar + integrityProtection + ba;
0135 
0136     int pos(0);
0137     char lastChar(0);
0138 
0139     int cnt = ba.count();
0140 
0141     while (pos < cnt) {
0142         ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
0143         lastChar = ba.at(pos);
0144         ++pos;
0145     }
0146 
0147     QByteArray resultArray;
0148     resultArray.append(char(0x03));  //version for future updates to algorithm
0149     resultArray.append(char(flags)); //encryption flags
0150     resultArray.append(ba);
0151 
0152     m_lastError = ErrorNoError;
0153     return resultArray;
0154 }
0155 
0156 QString O0SimpleCrypt::encryptToString(const QString& plaintext)
0157 {
0158     QByteArray plaintextArray = plaintext.toUtf8();
0159     QByteArray cypher = encryptToByteArray(plaintextArray);
0160     QString cypherString = QString::fromLatin1(cypher.toBase64());
0161     return cypherString;
0162 }
0163 
0164 QString O0SimpleCrypt::encryptToString(QByteArray plaintext)
0165 {
0166     QByteArray cypher = encryptToByteArray(plaintext);
0167     QString cypherString = QString::fromLatin1(cypher.toBase64());
0168     return cypherString;
0169 }
0170 
0171 QString O0SimpleCrypt::decryptToString(const QString &cyphertext)
0172 {
0173     QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
0174     QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
0175     QString plaintext = QString::fromUtf8(plaintextArray, plaintextArray.size());
0176 
0177     return plaintext;
0178 }
0179 
0180 QString O0SimpleCrypt::decryptToString(QByteArray cypher)
0181 {
0182     QByteArray ba = decryptToByteArray(cypher);
0183     QString plaintext = QString::fromUtf8(ba, ba.size());
0184 
0185     return plaintext;
0186 }
0187 
0188 QByteArray O0SimpleCrypt::decryptToByteArray(const QString& cyphertext)
0189 {
0190     QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
0191     QByteArray ba = decryptToByteArray(cyphertextArray);
0192 
0193     return ba;
0194 }
0195 
0196 QByteArray O0SimpleCrypt::decryptToByteArray(QByteArray cypher)
0197 {
0198     if (m_keyParts.isEmpty()) {
0199         qWarning() << "No key set.";
0200         m_lastError = ErrorNoKeySet;
0201         return QByteArray();
0202     }
0203 
0204     if (!cypher.length()) {
0205         m_lastError = ErrorUnknownVersion;
0206         return QByteArray();
0207     }
0208 
0209     QByteArray ba = cypher;
0210 
0211     char version = ba.at(0);
0212 
0213     if (version !=3) {  //we only work with version 3
0214         m_lastError = ErrorUnknownVersion;
0215         qWarning() << "Invalid version or not a cyphertext.";
0216         return QByteArray();
0217     }
0218 
0219     CryptoFlags flags = CryptoFlags(ba.at(1));
0220 
0221     ba = ba.mid(2);
0222     int pos(0);
0223     int cnt(ba.count());
0224     char lastChar = 0;
0225 
0226     while (pos < cnt) {
0227         char currentChar = ba[pos];
0228         ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
0229         lastChar = currentChar;
0230         ++pos;
0231     }
0232 
0233     ba = ba.mid(1); //chop off the random number at the start
0234 
0235     bool integrityOk(true);
0236     if (flags.testFlag(CryptoFlagChecksum)) {
0237         if (ba.length() < 2) {
0238             m_lastError = ErrorIntegrityFailed;
0239             return QByteArray();
0240         }
0241         quint16 storedChecksum;
0242         {
0243             QDataStream s(&ba, QIODevice::ReadOnly);
0244             s >> storedChecksum;
0245         }
0246         ba = ba.mid(2);
0247 #if QT_VERSION >= 0x060000
0248         quint16 checksum = qChecksum(QByteArrayView(ba));
0249 #else
0250         quint16 checksum = qChecksum(ba.constData(), ba.length());
0251 #endif
0252         integrityOk = (checksum == storedChecksum);
0253     } else if (flags.testFlag(CryptoFlagHash)) {
0254         if (ba.length() < 20) {
0255             m_lastError = ErrorIntegrityFailed;
0256             return QByteArray();
0257         }
0258         QByteArray storedHash = ba.left(20);
0259         ba = ba.mid(20);
0260         QCryptographicHash hash(QCryptographicHash::Sha1);
0261         hash.addData(ba);
0262         integrityOk = (hash.result() == storedHash);
0263     }
0264 
0265     if (!integrityOk) {
0266         m_lastError = ErrorIntegrityFailed;
0267         return QByteArray();
0268     }
0269 
0270     if (flags.testFlag(CryptoFlagCompression))
0271         ba = qUncompress(ba);
0272 
0273     m_lastError = ErrorNoError;
0274     return ba;
0275 }