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 }