File indexing completed on 2024-12-22 04:57:54

0001 /*
0002     SPDX-FileCopyrightText: 2011 Andre Somers
0003 
0004     SPDX-License-Identifier: BSD-3-Clause
0005 */
0006 
0007 #include "debug.h"
0008 #include "o2/o0simplecrypt.h"
0009 #include <QByteArray>
0010 #include <QCryptographicHash>
0011 #include <QDataStream>
0012 #include <QIODevice>
0013 #include <QRandomGenerator>
0014 
0015 O0SimpleCrypt::O0SimpleCrypt()
0016     : m_key(0)
0017     , m_compressionMode(CompressionAuto)
0018     , m_protectionMode(ProtectionChecksum)
0019     , m_lastError(ErrorNoError)
0020 {
0021 }
0022 
0023 O0SimpleCrypt::O0SimpleCrypt(quint64 key)
0024     : m_key(key)
0025     , m_compressionMode(CompressionAuto)
0026     , m_protectionMode(ProtectionChecksum)
0027     , m_lastError(ErrorNoError)
0028 {
0029     splitKey();
0030 }
0031 
0032 void O0SimpleCrypt::setKey(quint64 key)
0033 {
0034     m_key = key;
0035     splitKey();
0036 }
0037 
0038 void O0SimpleCrypt::splitKey()
0039 {
0040     m_keyParts.clear();
0041     m_keyParts.resize(8);
0042     for (int i = 0; i < 8; i++) {
0043         quint64 part = m_key;
0044         for (int j = i; j > 0; j--) {
0045             part = part >> 8;
0046         }
0047         part = part & 0xff;
0048         m_keyParts[i] = static_cast<char>(part);
0049     }
0050 }
0051 
0052 QByteArray O0SimpleCrypt::encryptToByteArray(const QString &plaintext)
0053 {
0054     QByteArray plaintextArray = plaintext.toUtf8();
0055     return encryptToByteArray(plaintextArray);
0056 }
0057 
0058 QByteArray O0SimpleCrypt::encryptToByteArray(const QByteArray &plaintext)
0059 {
0060     if (m_keyParts.isEmpty()) {
0061         qCWarning(TOMBOYNOTESRESOURCE_LOG) << "No key set.";
0062         m_lastError = ErrorNoKeySet;
0063         return {};
0064     }
0065 
0066     QByteArray ba = plaintext;
0067 
0068     CryptoFlags flags = CryptoFlagNone;
0069     if (m_compressionMode == CompressionAlways) {
0070         ba = qCompress(ba, 9); // maximum compression
0071         flags |= CryptoFlagCompression;
0072     } else if (m_compressionMode == CompressionAuto) {
0073         QByteArray compressed = qCompress(ba, 9);
0074         if (compressed.size() < ba.size()) {
0075             ba = compressed;
0076             flags |= CryptoFlagCompression;
0077         }
0078     }
0079 
0080     QByteArray integrityProtection;
0081     if (m_protectionMode == ProtectionChecksum) {
0082         flags |= CryptoFlagChecksum;
0083         QDataStream s(&integrityProtection, QIODevice::WriteOnly);
0084         s << qChecksum(QByteArrayView(ba.constData(), ba.length()));
0085     } else if (m_protectionMode == ProtectionHash) {
0086         flags |= CryptoFlagHash;
0087         QCryptographicHash hash(QCryptographicHash::Sha1);
0088         hash.addData(ba);
0089 
0090         integrityProtection += hash.result();
0091     }
0092 
0093     // prepend a random char to the string
0094     char randomChar = static_cast<char>(QRandomGenerator::global()->bounded(0xFF));
0095     ba = randomChar + integrityProtection + ba;
0096 
0097     int pos(0);
0098     char lastChar(0);
0099 
0100     int cnt = ba.size();
0101 
0102     while (pos < cnt) {
0103         ba[pos] = ba.at(pos) ^ m_keyParts.at(pos % 8) ^ lastChar;
0104         lastChar = ba.at(pos);
0105         ++pos;
0106     }
0107 
0108     QByteArray resultArray;
0109     resultArray.append(char(0x03)); // version for future updates to algorithm
0110     resultArray.append(char(flags)); // encryption flags
0111     resultArray.append(ba);
0112 
0113     m_lastError = ErrorNoError;
0114     return resultArray;
0115 }
0116 
0117 QString O0SimpleCrypt::encryptToString(const QString &plaintext)
0118 {
0119     QByteArray plaintextArray = plaintext.toUtf8();
0120     QByteArray cypher = encryptToByteArray(plaintextArray);
0121     QString cypherString = QString::fromLatin1(cypher.toBase64());
0122     return cypherString;
0123 }
0124 
0125 QString O0SimpleCrypt::encryptToString(const QByteArray &plaintext)
0126 {
0127     QByteArray cypher = encryptToByteArray(plaintext);
0128     QString cypherString = QString::fromLatin1(cypher.toBase64());
0129     return cypherString;
0130 }
0131 
0132 QString O0SimpleCrypt::decryptToString(const QString &cyphertext)
0133 {
0134     const QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
0135     const QByteArray plaintextArray = decryptToByteArray(cyphertextArray);
0136     const QString plaintext = QString::fromUtf8(plaintextArray.constData(), plaintextArray.size());
0137 
0138     return plaintext;
0139 }
0140 
0141 QString O0SimpleCrypt::decryptToString(const QByteArray &cypher)
0142 {
0143     const QByteArray ba = decryptToByteArray(cypher);
0144     const QString plaintext = QString::fromUtf8(ba.constData(), ba.size());
0145 
0146     return plaintext;
0147 }
0148 
0149 QByteArray O0SimpleCrypt::decryptToByteArray(const QString &cyphertext)
0150 {
0151     const QByteArray cyphertextArray = QByteArray::fromBase64(cyphertext.toLatin1());
0152     const QByteArray ba = decryptToByteArray(cyphertextArray);
0153 
0154     return ba;
0155 }
0156 
0157 QByteArray O0SimpleCrypt::decryptToByteArray(const QByteArray &cypher)
0158 {
0159     if (m_keyParts.isEmpty()) {
0160         qCWarning(TOMBOYNOTESRESOURCE_LOG) << "No key set.";
0161         m_lastError = ErrorNoKeySet;
0162         return {};
0163     }
0164 
0165     if (cypher.isEmpty()) {
0166         m_lastError = ErrorUnknownVersion;
0167         return {};
0168     }
0169 
0170     QByteArray ba = cypher;
0171 
0172     char version = ba.at(0);
0173 
0174     if (version != 3) { // we only work with version 3
0175         m_lastError = ErrorUnknownVersion;
0176         qCWarning(TOMBOYNOTESRESOURCE_LOG) << "Invalid version or not a cyphertext.";
0177         return {};
0178     }
0179 
0180     auto flags = CryptoFlags(ba.at(1));
0181 
0182     ba.remove(0, 2);
0183     int pos(0);
0184     int cnt(ba.size());
0185     char lastChar = 0;
0186 
0187     while (pos < cnt) {
0188         char currentChar = ba[pos];
0189         ba[pos] = ba.at(pos) ^ lastChar ^ m_keyParts.at(pos % 8);
0190         lastChar = currentChar;
0191         ++pos;
0192     }
0193 
0194     ba.remove(0, 1); // chop off the random number at the start
0195 
0196     bool integrityOk(true);
0197     if (flags.testFlag(CryptoFlagChecksum)) {
0198         if (ba.length() < 2) {
0199             m_lastError = ErrorIntegrityFailed;
0200             return {};
0201         }
0202         quint16 storedChecksum;
0203         {
0204             QDataStream s(&ba, QIODevice::ReadOnly);
0205             s >> storedChecksum;
0206         }
0207         ba.remove(0, 2);
0208         quint16 checksum = qChecksum(QByteArrayView(ba.constData(), ba.size()));
0209         integrityOk = (checksum == storedChecksum);
0210     } else if (flags.testFlag(CryptoFlagHash)) {
0211         if (ba.length() < 20) {
0212             m_lastError = ErrorIntegrityFailed;
0213             return {};
0214         }
0215         QByteArray storedHash = ba.left(20);
0216         ba.remove(0, 20);
0217         QCryptographicHash hash(QCryptographicHash::Sha1);
0218         hash.addData(ba);
0219         integrityOk = (hash.result() == storedHash);
0220     }
0221 
0222     if (!integrityOk) {
0223         m_lastError = ErrorIntegrityFailed;
0224         return {};
0225     }
0226 
0227     if (flags.testFlag(CryptoFlagCompression)) {
0228         ba = qUncompress(ba);
0229     }
0230 
0231     m_lastError = ErrorNoError;
0232     return ba;
0233 }