├── .gitignore ├── LICENSE ├── README.md ├── examples ├── CryptFileDeviceExample │ ├── CryptFileDeviceExample.pro │ └── main.cpp └── WebViewWithCryptDevice │ ├── WebViewWithCryptDevice.pro │ ├── main.cpp │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── mainwindow.ui │ ├── schemehandler.cpp │ └── schemehandler.h └── src ├── cryptfiledevice.cpp └── cryptfiledevice.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.moc 20 | moc_*.cpp 21 | qrc_*.cpp 22 | ui_*.h 23 | Makefile* 24 | *-build-* 25 | 26 | # QtCreator 27 | 28 | *.autosave 29 | 30 | #QtCtreator Qml 31 | *.qmlproject.user 32 | *.qmlproject.user.* 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Alexey Lysenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CryptFileDevice 2 | =============== 3 | 4 | Qt cross-platform class which allows transparently encrypt/decrypt QFileDevice using AES-CTR algorithm with OpenSSL library. 5 | 6 | ## Dependencies 7 | 8 | | Name | Version | Comment | 9 | |--------------|----------------------------------|--------------------------------------------------| 10 | | Qt | >= 5.1.0 (supports Qt6 as well) | adapted for Qt 6.2 | 11 | | C++ compiler | supporting C++11 (i.e. gcc 4.6+) | | 12 | | OpenSSL | >= 1.0.0 | didn't test with older versions, but it may work | 13 | 14 | ## Usage 15 | 16 | Copy 2 files (cryptfiledevice.cpp and cryptfiledevice.h) into your Qt project. See `examples/` for examples on using this class. 17 | 18 | In examples folder there are 2 example projects. 19 | 20 | 1) CryptFileDeviceExample - test general operations with file and compare results with QFile object. 21 | 22 | 2) WebViewWithCryptFileDevice - show how to display the user's encrypted content (image). 23 | 24 | You can find the docker image ready to build & run CryptFileDeviceExample without any additional preparations [here](https://github.com/alexeylysenko/docker_ubuntu_qt) 25 | 26 | ## Contact 27 | 28 | Questions and suggestions can be sent to email: lysenkoalexmail@gmail.com 29 | 30 | ## Contributing 31 | 32 | Please report any suggestions, feature requests, bug reports, or annoyances to 33 | the Github [issue tracker][issue_tracker]. 34 | 35 | ## License 36 | 37 | CrypFileDevice is licensed under [MIT](LICENSE). 38 | 39 | ## Thanks to 40 | 41 | Special thanks to Ruslan Salikhov for testing and sensible suggestions. 42 | 43 | Thanks to habrauser Disasm for suggestion. 44 | 45 | 46 | [issue_tracker]: https://github.com/alexeylysenko/CryptFileDevice/issues 47 | -------------------------------------------------------------------------------- /examples/CryptFileDeviceExample/CryptFileDeviceExample.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2014-09-21T15:21:49 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core 8 | CONFIG += c++11 console 9 | 10 | TARGET = CryptFileDeviceExample 11 | TEMPLATE = app 12 | 13 | SRCPATH = $$PWD/../../src 14 | 15 | INCLUDEPATH += $$SRCPATH 16 | 17 | SOURCES += main.cpp \ 18 | $$SRCPATH/cryptfiledevice.cpp 19 | 20 | HEADERS += \ 21 | $$SRCPATH/cryptfiledevice.h 22 | 23 | #openssl 24 | win32 { 25 | INCLUDEPATH += c:/OpenSSL-Win32/include 26 | LIBS += -Lc:/OpenSSL-Win32/bin -llibeay32 27 | } 28 | 29 | linux|macx { 30 | LIBS += -lcrypto 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/CryptFileDeviceExample/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "cryptfiledevice.h" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #define PRINT_STATE(ok) \ 13 | { qDebug() << ((ok) ? "..Done" : "..Failed"); } 14 | 15 | void initRandomGenerator() { 16 | #if QT_VERSION >= 0x051000 17 | // unnecessary, global QRandomGenerator is already seeded using 18 | // securelySeeded() 19 | #else 20 | uint seed = QDateTime::currentSecsSinceEpoch(); 21 | qDebug() << "Seed:" << seed; 22 | qsrand(seed); 23 | #endif 24 | } 25 | 26 | quint32 generateRandomInt() { 27 | #if QT_VERSION >= 0x051000 28 | return QRandomGenerator::global()->generate(); 29 | #else 30 | return qrand(); 31 | #endif 32 | } 33 | 34 | QByteArray generateRandomData(int size) { 35 | QByteArray data; 36 | while (data.size() < size) { 37 | data += char(generateRandomInt() % 256); 38 | } 39 | return data; 40 | } 41 | 42 | bool compare(const QString &pathToEnc, const QString &pathToPlain) { 43 | // Create files 44 | QFile plainFile(pathToPlain); 45 | 46 | QFile encryptedFile(pathToEnc); 47 | CryptFileDevice cryptFileDevice( 48 | &encryptedFile, "01234567890123456789012345678901", "0123456789012345"); 49 | 50 | if (!plainFile.open(QIODevice::ReadOnly)) { 51 | return false; 52 | } 53 | 54 | if (!cryptFileDevice.open(QIODevice::ReadOnly)) { 55 | plainFile.close(); 56 | return false; 57 | } 58 | 59 | QByteArray plainData = plainFile.readAll(); 60 | QByteArray decryptData = cryptFileDevice.readAll(); 61 | 62 | bool result = (plainData == decryptData); 63 | 64 | plainFile.close(); 65 | cryptFileDevice.close(); 66 | 67 | return result; 68 | } 69 | 70 | QByteArray calculateXor(const QByteArray &data, const QByteArray &key) { 71 | if (key.isEmpty()) 72 | return data; 73 | 74 | QByteArray result; 75 | for (int i = 0, j = 0; i < data.length(); ++i, ++j) { 76 | if (j == key.length()) 77 | j = 0; // repeat the key if key.length() < data.length() 78 | result.append(data.at(i) ^ key.at(j)); 79 | } 80 | return result; 81 | } 82 | 83 | bool openDevicePair(QIODevice *device1, QIODevice *device2, 84 | QIODevice::OpenMode mode) { 85 | if (device1->isOpen()) 86 | device1->close(); 87 | if (device2->isOpen()) 88 | device2->close(); 89 | 90 | if (!device1->open(mode)) { 91 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot create test file"); 92 | return false; 93 | } 94 | 95 | if (!device2->open(mode)) { 96 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot create test file"); 97 | return false; 98 | } 99 | return true; 100 | } 101 | 102 | void testCryptFileDevice() { 103 | bool ok; 104 | 105 | // Create files 106 | QFile plainFile(qApp->applicationDirPath() + "/testfile.plain"); 107 | 108 | QFile encryptedFile(qApp->applicationDirPath() + "/testfile.encrypted"); 109 | CryptFileDevice cryptFileDevice( 110 | &encryptedFile, "01234567890123456789012345678901", "0123456789012345"); 111 | 112 | // Creating (rewriting files) 113 | /// ---------------------------------------------------------------------- 114 | qDebug() << "Creating test files"; 115 | if (!openDevicePair(&plainFile, &cryptFileDevice, 116 | QIODevice::WriteOnly | QIODevice::Truncate)) 117 | return; 118 | PRINT_STATE(true) 119 | 120 | qDebug() << "Writing random content"; 121 | for (int i = 0; i < 200; i++) { 122 | QByteArray data = 123 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 124 | plainFile.write(data); 125 | cryptFileDevice.write(data); 126 | } 127 | plainFile.close(); 128 | cryptFileDevice.close(); 129 | PRINT_STATE(true) 130 | 131 | /// ---------------------------------------------------------------------- 132 | qDebug() << "Comparing content (should be the same)"; 133 | { 134 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 135 | PRINT_STATE(ok) 136 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 137 | } 138 | 139 | /// ---------------------------------------------------------------------- 140 | qDebug() << "Comparing files's size (sould be the same)"; 141 | { 142 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 143 | return; 144 | 145 | ok = (cryptFileDevice.size() == plainFile.size()); 146 | 147 | PRINT_STATE(ok) 148 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Size is different"); 149 | } 150 | 151 | /// ---------------------------------------------------------------------- 152 | qDebug() << "Reading from random position"; 153 | { 154 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 155 | return; 156 | ok = true; 157 | for (int i = 0; i < 200; i++) { 158 | qint64 pos = generateRandomInt() % plainFile.size(); // size is the same 159 | qint64 maxlen = generateRandomInt() % 256; 160 | 161 | cryptFileDevice.seek(pos); 162 | Q_ASSERT(cryptFileDevice.pos() == pos); 163 | plainFile.seek(pos); 164 | Q_ASSERT(plainFile.pos() == pos); 165 | 166 | QByteArray data1 = plainFile.read(maxlen); 167 | QByteArray data2 = cryptFileDevice.read(maxlen); 168 | 169 | if (data1 != data2) { 170 | ok = false; 171 | break; 172 | } 173 | } 174 | PRINT_STATE(ok) 175 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Random read content is different"); 176 | } 177 | 178 | /// ---------------------------------------------------------------------- 179 | qDebug() << "Reading line by line"; 180 | { 181 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 182 | return; 183 | 184 | Q_ASSERT(plainFile.pos() == 0); 185 | Q_ASSERT(cryptFileDevice.pos() == 0); 186 | 187 | ok = true; 188 | QByteArray seed = generateRandomData(300); 189 | QByteArray chk1 = seed, chk2 = seed; 190 | 191 | while (!plainFile.atEnd()) { 192 | QByteArray line = plainFile.readLine(); 193 | if (line.isEmpty()) 194 | break; 195 | chk1 = calculateXor(chk1, line); 196 | } 197 | 198 | while (!cryptFileDevice.atEnd()) { 199 | QByteArray line = cryptFileDevice.readLine(); 200 | if (line.isEmpty()) 201 | break; 202 | chk2 = calculateXor(chk2, line); 203 | } 204 | 205 | ok = (chk1 == chk2); 206 | PRINT_STATE(ok) 207 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Reading lines is failed"); 208 | } 209 | 210 | /// ---------------------------------------------------------------------- 211 | qDebug() << "Appending data"; 212 | { 213 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::Append)) 214 | return; 215 | 216 | for (int i = 0; i < 200; i++) { 217 | QByteArray data = 218 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 219 | qint64 plainBytesWritten = plainFile.write(data); 220 | Q_ASSERT(plainBytesWritten == data.size()); 221 | qint64 cryptBytesWritten = cryptFileDevice.write(data); 222 | Q_ASSERT(cryptBytesWritten == data.size()); 223 | } 224 | plainFile.close(); 225 | cryptFileDevice.close(); 226 | 227 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 228 | PRINT_STATE(ok) 229 | 230 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 231 | } 232 | 233 | /// ---------------------------------------------------------------------- 234 | { 235 | qDebug() << "Rewriting file (truncate)"; 236 | if (!openDevicePair(&plainFile, &cryptFileDevice, 237 | QIODevice::WriteOnly | QIODevice::Truncate)) 238 | return; 239 | for (int i = 0; i < 200; i++) { 240 | QByteArray data = 241 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 242 | plainFile.write(data); 243 | cryptFileDevice.write(data); 244 | } 245 | plainFile.close(); 246 | cryptFileDevice.close(); 247 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 248 | PRINT_STATE(ok) 249 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 250 | } 251 | 252 | /// ---------------------------------------------------------------------- 253 | qDebug() << "Flushing"; 254 | { 255 | qDebug() << "Rewriting file (truncate)"; 256 | if (!openDevicePair(&plainFile, &cryptFileDevice, 257 | QIODevice::WriteOnly | QIODevice::Truncate)) 258 | return; 259 | 260 | for (int i = 0; i < 200; i++) { 261 | QByteArray data = 262 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 263 | plainFile.write(data); 264 | plainFile.flush(); 265 | cryptFileDevice.write(data); 266 | cryptFileDevice.flush(); 267 | } 268 | 269 | plainFile.close(); 270 | cryptFileDevice.close(); 271 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 272 | PRINT_STATE(ok) 273 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 274 | } 275 | 276 | /// ---------------------------------------------------------------------- 277 | qDebug() << "Sizing Flushing"; 278 | { 279 | qDebug() << "Rewriting file (truncate)"; 280 | if (!openDevicePair(&plainFile, &cryptFileDevice, 281 | QIODevice::WriteOnly | QIODevice::Truncate)) 282 | return; 283 | 284 | for (int i = 0; i < 200; i++) { 285 | QByteArray data = 286 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 287 | plainFile.write(data); 288 | qint64 plainSize = plainFile.size(); 289 | cryptFileDevice.write(data); 290 | qint64 cryptSize = cryptFileDevice.size(); 291 | Q_ASSERT(plainSize == cryptSize); 292 | } 293 | 294 | plainFile.close(); 295 | cryptFileDevice.close(); 296 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 297 | PRINT_STATE(ok) 298 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 299 | } 300 | 301 | /// ---------------------------------------------------------------------- 302 | qDebug() << "Rewriting random data in file"; 303 | { 304 | ok = false; 305 | if (!openDevicePair(&plainFile, &cryptFileDevice, 306 | QIODevice::WriteOnly | QIODevice::Truncate)) 307 | return; 308 | for (int i = 0; i < 200; i++) { 309 | QByteArray data = 310 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 311 | plainFile.write(data); 312 | cryptFileDevice.write(data); 313 | } 314 | plainFile.close(); 315 | cryptFileDevice.close(); 316 | if (compare(encryptedFile.fileName(), plainFile.fileName())) { 317 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadWrite)) 318 | return; 319 | 320 | for (int i = 0; i < 200; i++) { 321 | qint64 pos = generateRandomInt() % plainFile.size(); // size is the same 322 | 323 | cryptFileDevice.seek(pos); 324 | Q_ASSERT(cryptFileDevice.pos() == pos); 325 | plainFile.seek(pos); 326 | Q_ASSERT(plainFile.pos() == pos); 327 | 328 | QByteArray data = 329 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 330 | plainFile.write(data); 331 | cryptFileDevice.write(data); 332 | } 333 | } 334 | plainFile.close(); 335 | cryptFileDevice.close(); 336 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 337 | 338 | PRINT_STATE(ok) 339 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 340 | } 341 | 342 | /// ---------------------------------------------------------------------- 343 | qDebug() << "Writing using QDataStream (operator <<)"; 344 | { 345 | ok = false; 346 | if (!openDevicePair(&plainFile, &cryptFileDevice, 347 | QIODevice::WriteOnly | QIODevice::Truncate)) 348 | return; 349 | 350 | QDataStream plainStream(&plainFile); 351 | QDataStream cryptStream(&cryptFileDevice); 352 | for (int i = 0; i < 200; i++) { 353 | QByteArray data = 354 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 355 | 356 | plainStream << data; 357 | cryptStream << data; 358 | } 359 | plainFile.close(); 360 | cryptFileDevice.close(); 361 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 362 | PRINT_STATE(ok) 363 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 364 | } 365 | 366 | /// ---------------------------------------------------------------------- 367 | qDebug() << "Writing using QDataStream (writeRawData)"; 368 | { 369 | ok = false; 370 | if (!openDevicePair(&plainFile, &cryptFileDevice, 371 | QIODevice::WriteOnly | QIODevice::Truncate)) 372 | return; 373 | 374 | QDataStream plainStream(&plainFile); 375 | QDataStream cryptStream(&cryptFileDevice); 376 | for (int i = 0; i < 200; i++) { 377 | QByteArray data = 378 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 379 | 380 | int plainBytesWritten = 381 | plainStream.writeRawData(data.constData(), data.length()); 382 | int cryptBytesWritten = 383 | cryptStream.writeRawData(data.constData(), data.length()); 384 | Q_ASSERT(plainBytesWritten == cryptBytesWritten); 385 | } 386 | plainFile.close(); 387 | cryptFileDevice.close(); 388 | ok = compare(encryptedFile.fileName(), plainFile.fileName()); 389 | PRINT_STATE(ok) 390 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 391 | } 392 | 393 | // ---------------------------------------------------------------------- 394 | qDebug() << "Reading using QDataStream (operator >>)"; 395 | { 396 | ok = false; 397 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 398 | return; 399 | 400 | QDataStream plainStream(&plainFile); 401 | QDataStream cryptStream(&cryptFileDevice); 402 | 403 | QByteArray dataFromPlainFile; 404 | QByteArray dataFromCryptDevice; 405 | plainStream >> dataFromPlainFile; 406 | cryptStream >> dataFromCryptDevice; 407 | plainFile.close(); 408 | cryptFileDevice.close(); 409 | ok = (dataFromPlainFile == dataFromCryptDevice); 410 | PRINT_STATE(ok) 411 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 412 | } 413 | 414 | // ---------------------------------------------------------------------- 415 | qDebug() << "Reading using QDataStream (readRawData)"; 416 | { 417 | ok = true; 418 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 419 | return; 420 | 421 | QDataStream plainStream(&plainFile); 422 | QDataStream cryptStream(&cryptFileDevice); 423 | 424 | for (int i = 0; i < 200; ++i) { 425 | int size = generateRandomInt() % 256; 426 | QByteArray dataFromPlainFile(size, ' '); 427 | QByteArray dataFromCryptDevice(size, ' '); 428 | 429 | int plainBytesRead = 430 | plainStream.readRawData(dataFromPlainFile.data(), size); 431 | int cryptBytesRead = 432 | cryptStream.readRawData(dataFromCryptDevice.data(), size); 433 | Q_ASSERT(plainBytesRead == cryptBytesRead); 434 | 435 | if (dataFromPlainFile != dataFromCryptDevice) { 436 | ok = false; 437 | break; 438 | } 439 | } 440 | plainFile.close(); 441 | cryptFileDevice.close(); 442 | 443 | PRINT_STATE(ok) 444 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 445 | } 446 | 447 | // ---------------------------------------------------------------------- 448 | qDebug() << "Reading from random position using QTextStream"; 449 | { 450 | ok = true; 451 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 452 | return; 453 | 454 | QTextStream plainStream(&plainFile); 455 | QTextStream cryptStream(&cryptFileDevice); 456 | 457 | for (int i = 0; i < 200; ++i) { 458 | int pos = generateRandomInt() % plainFile.size(); 459 | int size = generateRandomInt() % 256; 460 | 461 | plainStream.seek(pos); 462 | Q_ASSERT(plainStream.pos() == pos); 463 | cryptStream.seek(pos); 464 | Q_ASSERT(cryptStream.pos() == pos); 465 | 466 | QString plainData = plainStream.read(size); 467 | QString cryptData = cryptStream.read(size); 468 | 469 | if (plainData != cryptData) { 470 | ok = false; 471 | break; 472 | } 473 | } 474 | plainFile.close(); 475 | cryptFileDevice.close(); 476 | 477 | PRINT_STATE(ok) 478 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Content is different"); 479 | } 480 | 481 | // ---------------------------------------------------------------------- 482 | qDebug() << "Reading line by line using QTextStream"; 483 | { 484 | if (!openDevicePair(&plainFile, &cryptFileDevice, QIODevice::ReadOnly)) 485 | return; 486 | 487 | QTextStream plainStream(&plainFile); 488 | QTextStream cryptStream(&cryptFileDevice); 489 | 490 | ok = true; 491 | QByteArray seed = generateRandomData(300); 492 | QByteArray chk1 = seed, chk2 = seed; 493 | 494 | while (!plainStream.atEnd()) { 495 | QString line = plainStream.readLine(); 496 | if (line.isEmpty()) 497 | break; 498 | chk1 = calculateXor(chk1, line.toUtf8()); 499 | } 500 | 501 | while (!cryptStream.atEnd()) { 502 | QString line = cryptStream.readLine(); 503 | if (line.isEmpty()) 504 | break; 505 | chk2 = calculateXor(chk2, line.toUtf8()); 506 | } 507 | 508 | plainFile.close(); 509 | cryptFileDevice.close(); 510 | 511 | ok = (chk1 == chk2); 512 | PRINT_STATE(ok) 513 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Reading lines is failed"); 514 | } 515 | 516 | // ---------------------------------------------------------------------- 517 | qDebug() << "Open CryptFileDevice with wrong password"; 518 | { 519 | ok = false; 520 | CryptFileDevice cryptFileDevice(&encryptedFile, "1234567890123456789012", 521 | "123456789012"); 522 | 523 | if (!cryptFileDevice.open(QIODevice::ReadOnly)) { 524 | ok = true; 525 | } 526 | 527 | PRINT_STATE(ok) 528 | Q_ASSERT_X(ok, Q_FUNC_INFO, 529 | "Open CryptFileDevice with wrong password is failed"); 530 | } 531 | 532 | /// ---------------------------------------------------------------------- 533 | qDebug() << "Removing"; 534 | { 535 | ok = cryptFileDevice.remove() && !encryptedFile.exists(); 536 | PRINT_STATE(ok) 537 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Cannot remove file"); 538 | } 539 | } 540 | 541 | QMap testQFilePerformance(const QString &pathToTestData) { 542 | QString fileName = qApp->applicationDirPath() + "/test_qfile"; 543 | QMap result; 544 | 545 | if (QFile::exists(fileName)) { 546 | if (!QFile::remove(fileName)) { 547 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot remove file"); 548 | return result; 549 | } 550 | } 551 | 552 | QFile testDataFile(pathToTestData); 553 | if (!testDataFile.open(QIODevice::ReadOnly)) { 554 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot open file"); 555 | return result; 556 | } 557 | 558 | QFile testFile(fileName); 559 | bool ok = true; 560 | 561 | QElapsedTimer timer; 562 | int time; 563 | QByteArray chunk; 564 | chunk.reserve(100000); 565 | 566 | // Writing data 567 | qDebug() << "Writing data"; 568 | timer.start(); 569 | 570 | ok = testFile.open(QIODevice::WriteOnly); 571 | PRINT_STATE(ok) 572 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 573 | if (!ok) 574 | return result; 575 | 576 | testDataFile.seek(0); 577 | while (!testDataFile.atEnd()) { 578 | chunk = testDataFile.read(99991); // max prime number < 100000 579 | testFile.write(chunk); 580 | } 581 | 582 | testFile.close(); 583 | time = timer.elapsed(); 584 | result.insert("writing", time); 585 | qDebug() << "Time:" << time; 586 | 587 | // Reading data 588 | qDebug() << "Reading data"; 589 | 590 | ok = testFile.open(QIODevice::ReadOnly); 591 | PRINT_STATE(ok) 592 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 593 | if (!ok) 594 | return result; 595 | 596 | timer.start(); 597 | testFile.seek(0); 598 | while (!testFile.atEnd()) { 599 | chunk = testFile.read(99991); // max prime number < 100000 600 | } 601 | 602 | testFile.close(); 603 | time = timer.elapsed(); 604 | result.insert("reading", time); 605 | qDebug() << "Time:" << timer.elapsed(); 606 | 607 | // Writing data 608 | qDebug() << "Writing data with size"; 609 | timer.start(); 610 | 611 | ok = testFile.open(QIODevice::WriteOnly); 612 | PRINT_STATE(ok) 613 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 614 | if (!ok) 615 | return result; 616 | 617 | testDataFile.seek(0); 618 | while (!testDataFile.atEnd()) { 619 | chunk = testDataFile.read(99991); // max prime number < 100000 620 | testFile.write(chunk); 621 | testFile.size(); 622 | } 623 | 624 | testFile.close(); 625 | time = timer.elapsed(); 626 | result.insert("writing_with_size", time); 627 | qDebug() << "Time:" << timer.elapsed(); 628 | 629 | return result; 630 | } 631 | 632 | QMap testCryptFilePerformance(const QString &pathToTestData) { 633 | QString fileName = qApp->applicationDirPath() + "/test_qfile"; 634 | QMap result; 635 | 636 | if (QFile::exists(fileName)) { 637 | if (!QFile::remove(fileName)) { 638 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot remove file"); 639 | return result; 640 | } 641 | } 642 | 643 | QFile testDataFile(pathToTestData); 644 | if (!testDataFile.open(QIODevice::ReadOnly)) { 645 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot open file"); 646 | return result; 647 | } 648 | 649 | QFile testFileDevice(fileName); 650 | CryptFileDevice testFile(&testFileDevice, "14rewffsdfsdfsagfdgsd", 651 | "gfdgfdsgfdgfdgfdgfds"); 652 | bool ok = true; 653 | 654 | QElapsedTimer timer; 655 | int time; 656 | QByteArray chunk; 657 | chunk.reserve(100000); 658 | 659 | // Writing data 660 | qDebug() << "Writing data"; 661 | timer.start(); 662 | 663 | ok = testFile.open(QIODevice::WriteOnly); 664 | PRINT_STATE(ok) 665 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 666 | if (!ok) 667 | return result; 668 | 669 | testDataFile.seek(0); 670 | while (!testDataFile.atEnd()) { 671 | chunk = testDataFile.read(99991); // max prime number < 100000 672 | testFile.write(chunk); 673 | } 674 | 675 | testFile.close(); 676 | time = timer.elapsed(); 677 | result.insert("writing", time); 678 | qDebug() << "Time:" << time; 679 | 680 | // Reading data 681 | qDebug() << "Reading data"; 682 | 683 | ok = testFile.open(QIODevice::ReadOnly); 684 | PRINT_STATE(ok) 685 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 686 | if (!ok) 687 | return result; 688 | 689 | timer.start(); 690 | testFile.seek(0); 691 | while (!testFile.atEnd()) { 692 | chunk = testFile.read(99991); // max prime number < 100000 693 | } 694 | 695 | testFile.close(); 696 | time = timer.elapsed(); 697 | result.insert("reading", time); 698 | qDebug() << "Time:" << timer.elapsed(); 699 | 700 | // Writing data 701 | qDebug() << "Writing data with size"; 702 | timer.start(); 703 | 704 | ok = testFile.open(QIODevice::WriteOnly); 705 | PRINT_STATE(ok) 706 | Q_ASSERT_X(ok, Q_FUNC_INFO, "Opening file is failed"); 707 | if (!ok) 708 | return result; 709 | 710 | testDataFile.seek(0); 711 | while (!testDataFile.atEnd()) { 712 | chunk = testDataFile.read(99991); // max prime number < 100000 713 | testFile.write(chunk); 714 | testFile.size(); 715 | } 716 | 717 | testFile.close(); 718 | time = timer.elapsed(); 719 | result.insert("writing_with_size", time); 720 | qDebug() << "Time:" << timer.elapsed(); 721 | 722 | return result; 723 | } 724 | 725 | void testPerformance() { 726 | // Preparing test file 727 | QFile testFile(qApp->applicationDirPath() + "/testdata"); 728 | if (!testFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) { 729 | Q_ASSERT_X(false, Q_FUNC_INFO, "Cannot open file"); 730 | return; 731 | } 732 | 733 | for (int i = 0; i < 1000; i++) // How much MB 734 | { 735 | QByteArray data = generateRandomData(1024 * 1024); // 1MB 736 | 737 | testFile.write(data); 738 | } 739 | 740 | testFile.close(); 741 | 742 | QMap result1 = testQFilePerformance(testFile.fileName()); 743 | qDebug() << "Result:" << result1; 744 | QMap result2 = testCryptFilePerformance(testFile.fileName()); 745 | qDebug() << "Result:" << result2; 746 | QMap result3 = testQFilePerformance(testFile.fileName()); 747 | qDebug() << "Result:" << result3; 748 | QMap result4 = testCryptFilePerformance(testFile.fileName()); 749 | qDebug() << "Result:" << result4; 750 | QMap result5 = testQFilePerformance(testFile.fileName()); 751 | qDebug() << "Result:" << result5; 752 | QMap result6 = testCryptFilePerformance(testFile.fileName()); 753 | qDebug() << "Result:" << result6; 754 | } 755 | 756 | void testShortSalt() { 757 | QFile plainFile("plaintext"); 758 | CryptFileDevice cryptFileDevice("cryptofile", "Password", "salt"); 759 | if (!openDevicePair(&plainFile, &cryptFileDevice, 760 | QIODevice::WriteOnly | QIODevice::Truncate)) { 761 | return; 762 | } 763 | for (int i = 0; i < 200; i++) { 764 | QByteArray data = 765 | generateRandomData(generateRandomInt() % 256).toBase64() + "\r\n"; 766 | plainFile.write(data); 767 | cryptFileDevice.write(data); 768 | } 769 | plainFile.close(); 770 | cryptFileDevice.close(); 771 | 772 | // It has to be a different instance of CryptFileDevice because otherwise 773 | // the encryption key with the wrong salt is already prepared and 774 | // decryption will work 775 | CryptFileDevice cryptFileDevice2("cryptofile", "Password", "salt"); 776 | if (!openDevicePair(&plainFile, &cryptFileDevice2, QIODevice::ReadOnly)) { 777 | return; 778 | } 779 | QByteArray decryptedData = cryptFileDevice2.readAll(); 780 | QByteArray plainData = plainFile.readAll(); 781 | plainFile.close(); 782 | cryptFileDevice2.close(); 783 | Q_ASSERT_X(plainData == decryptedData, Q_FUNC_INFO, 784 | "Original and decrypted text are mismatched"); 785 | } 786 | 787 | int main(int argc, char *argv[]) { 788 | QCoreApplication a(argc, argv); 789 | 790 | initRandomGenerator(); 791 | 792 | QElapsedTimer timer; 793 | for (int i = 0; i < 200; i++) { 794 | qDebug() << i << " times"; 795 | timer.restart(); 796 | testCryptFileDevice(); 797 | testShortSalt(); 798 | qDebug() << "test duration: " << timer.elapsed() << " ms"; 799 | } 800 | 801 | // testPerformance(); 802 | 803 | return 0; 804 | } 805 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/WebViewWithCryptDevice.pro: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by QtCreator 2014-10-02T10:48:35 4 | # 5 | #------------------------------------------------- 6 | 7 | QT += core gui webenginewidgets 8 | 9 | greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 10 | 11 | TARGET = WebViewWithCryptDevice 12 | TEMPLATE = app 13 | CONFIG += c++11 14 | 15 | SRCPATH = $$PWD/../../src 16 | 17 | INCLUDEPATH += $$SRCPATH 18 | 19 | SOURCES += main.cpp \ 20 | mainwindow.cpp \ 21 | $$SRCPATH/cryptfiledevice.cpp \ 22 | schemehandler.cpp 23 | 24 | HEADERS += mainwindow.h \ 25 | $$SRCPATH/cryptfiledevice.h \ 26 | schemehandler.h 27 | 28 | FORMS += mainwindow.ui 29 | 30 | #openssl 31 | win32 { 32 | INCLUDEPATH += c:/OpenSSL-Win32/include 33 | LIBS += -Lc:/OpenSSL-Win32/bin -llibeay32 34 | } 35 | 36 | linux|macx { 37 | LIBS += -lcrypto 38 | } 39 | 40 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/main.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include "schemehandler.h" 8 | 9 | int main(int argc, char *argv[]) 10 | { 11 | SchemeHandler::registerUrlScheme(); 12 | 13 | QApplication a(argc, argv); 14 | 15 | SchemeHandler schemeHandler; 16 | QWebEngineProfile::defaultProfile()->installUrlSchemeHandler(SchemeHandler::schemeName, 17 | &schemeHandler); 18 | 19 | MainWindow w; 20 | w.show(); 21 | 22 | return a.exec(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "ui_mainwindow.h" 3 | 4 | #include 5 | 6 | #include 7 | 8 | #include "cryptfiledevice.h" 9 | 10 | MainWindow::MainWindow(QWidget *parent) : 11 | QMainWindow(parent), 12 | ui(new Ui::MainWindow) 13 | { 14 | ui->setupUi(this); 15 | 16 | connect(ui->browseToolBtn, &QToolButton::clicked, 17 | this, &MainWindow::browseImage); 18 | connect(ui->encryptAndShowBtn, &QPushButton::clicked, 19 | this, &MainWindow::encryptAndShow); 20 | } 21 | 22 | MainWindow::~MainWindow() 23 | { 24 | delete ui; 25 | } 26 | 27 | void MainWindow::browseImage() 28 | { 29 | QString filePath = QFileDialog::getOpenFileName(this, 30 | tr("Select Image File"), 31 | QString(), 32 | tr("Images (*.png)")); 33 | ui->filePathLineEdit->setText(filePath); 34 | 35 | ui->encryptAndShowBtn->setDisabled(filePath.isEmpty()); 36 | } 37 | 38 | void MainWindow::encryptAndShow() 39 | { 40 | QString filePath = ui->filePathLineEdit->text(); 41 | QFile file(filePath); 42 | if (!file.open(QIODevice::ReadOnly)) 43 | { 44 | QMessageBox::warning(this, qApp->applicationName(), tr("Cannot open selected file")); 45 | return; 46 | } 47 | 48 | CryptFileDevice cryptFileDevice; 49 | cryptFileDevice.setFileName(filePath + ".enc"); 50 | cryptFileDevice.setPassword(QByteArrayLiteral("alex_password")); 51 | cryptFileDevice.setSalt(QByteArrayLiteral("alex_salt")); 52 | if (!cryptFileDevice.open(QIODevice::WriteOnly | QIODevice::Truncate)) 53 | { 54 | QMessageBox::warning(this, qApp->applicationName(), tr("Cannot encrypt selected file")); 55 | file.close(); 56 | return; 57 | } 58 | 59 | cryptFileDevice.write(file.readAll()); 60 | 61 | file.close(); 62 | cryptFileDevice.close(); 63 | 64 | QString html = QStringLiteral("").arg(filePath); 65 | ui->webView->setHtml(html, QUrl("cd://")); 66 | } 67 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | 6 | namespace Ui { 7 | class MainWindow; 8 | } 9 | 10 | class MainWindow : public QMainWindow 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | explicit MainWindow(QWidget *parent = 0); 16 | ~MainWindow(); 17 | 18 | private slots: 19 | void browseImage(); 20 | void encryptAndShow(); 21 | 22 | private: 23 | Ui::MainWindow *ui; 24 | }; 25 | 26 | #endif // MAINWINDOW_H 27 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/mainwindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 585 10 | 345 11 | 12 | 13 | 14 | WebViewWithCryptDevice 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | about:blank 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | false 33 | 34 | 35 | false 36 | 37 | 38 | 39 | 40 | 41 | 42 | ... 43 | 44 | 45 | 46 | 47 | 48 | 49 | Qt::Horizontal 50 | 51 | 52 | 53 | 40 54 | 20 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | Encrypt And Show 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | QWebEngineView 75 | QWidget 76 |
QtWebEngineWidgets/qwebengineview.h
77 | 1 78 |
79 |
80 | 81 | 82 |
83 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/schemehandler.cpp: -------------------------------------------------------------------------------- 1 | #include "schemehandler.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "cryptfiledevice.h" 7 | 8 | const QByteArray SchemeHandler::schemeName = QByteArrayLiteral("cd"); 9 | 10 | SchemeHandler::SchemeHandler(QObject *parent) 11 | : QWebEngineUrlSchemeHandler(parent) 12 | { 13 | } 14 | 15 | void SchemeHandler::requestStarted(QWebEngineUrlRequestJob *job) 16 | { 17 | QByteArray method = job->requestMethod(); 18 | QString urlPath = job->requestUrl().path(); 19 | 20 | if (method == QByteArrayLiteral("GET") && urlPath.endsWith(".enc")) 21 | { 22 | auto file = new CryptFileDevice(urlPath, 23 | QByteArrayLiteral("alex_password"), 24 | QByteArrayLiteral("alex_salt"), 25 | job); 26 | file->open(QIODevice::ReadOnly); 27 | job->reply(QByteArrayLiteral("image/*"), file); 28 | } 29 | else 30 | { 31 | job->fail(QWebEngineUrlRequestJob::UrlInvalid); 32 | } 33 | } 34 | 35 | void SchemeHandler::registerUrlScheme() 36 | { 37 | QWebEngineUrlScheme scheme(schemeName); 38 | scheme.setFlags(QWebEngineUrlScheme::SecureScheme | 39 | QWebEngineUrlScheme::LocalScheme | 40 | QWebEngineUrlScheme::LocalAccessAllowed); 41 | QWebEngineUrlScheme::registerScheme(scheme); 42 | } 43 | -------------------------------------------------------------------------------- /examples/WebViewWithCryptDevice/schemehandler.h: -------------------------------------------------------------------------------- 1 | #ifndef SCHEMEHANDLER_H 2 | #define SCHEMEHANDLER_H 3 | 4 | #include 5 | 6 | class SchemeHandler : public QWebEngineUrlSchemeHandler 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit SchemeHandler(QObject *parent = nullptr); 11 | 12 | void requestStarted(QWebEngineUrlRequestJob *job) override; 13 | 14 | static void registerUrlScheme(); 15 | 16 | static const QByteArray schemeName; 17 | }; 18 | 19 | #endif // SCHEMEHANDLER_H 20 | -------------------------------------------------------------------------------- /src/cryptfiledevice.cpp: -------------------------------------------------------------------------------- 1 | #include "cryptfiledevice.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // TODO: remove fixed header length in version 2 18 | static int const kHeaderLength = 128; 19 | static int const kPaddingLength = 54; 20 | static int const kHashLength = 32; 21 | static int const kSaltMaxLength = 8; 22 | 23 | CryptFileDevice::CryptFileDevice(QObject *parent) : QIODevice(parent) {} 24 | 25 | CryptFileDevice::CryptFileDevice(QFileDevice *device, QObject *parent) 26 | : QIODevice(parent), m_device(device) {} 27 | 28 | CryptFileDevice::CryptFileDevice(QFileDevice *device, 29 | const QByteArray &password, 30 | const QByteArray &salt, QObject *parent) 31 | : QIODevice(parent), m_device(device), m_password(password) { 32 | setSalt(salt); 33 | } 34 | 35 | CryptFileDevice::CryptFileDevice(const QString &fileName, 36 | const QByteArray &password, 37 | const QByteArray &salt, QObject *parent) 38 | : QIODevice(parent), 39 | m_device(new QFile(fileName)), 40 | m_deviceOwner(true), 41 | m_password(password) { 42 | setSalt(salt); 43 | } 44 | 45 | CryptFileDevice::~CryptFileDevice() { 46 | close(); 47 | 48 | if (m_deviceOwner) delete m_device; 49 | } 50 | 51 | void CryptFileDevice::setPassword(const QByteArray &password) { 52 | m_password = password; 53 | } 54 | 55 | void CryptFileDevice::setSalt(const QByteArray &salt) { 56 | if (salt.isEmpty()) { 57 | m_salt.clear(); 58 | return; 59 | } 60 | 61 | m_salt = salt.leftJustified(kSaltMaxLength, '\0', true); 62 | } 63 | 64 | void CryptFileDevice::setKeyLength(AesKeyLength keyLength) { 65 | m_aesKeyLength = keyLength; 66 | } 67 | 68 | void CryptFileDevice::setNumRounds(int numRounds) { m_numRounds = numRounds; } 69 | 70 | bool CryptFileDevice::open(OpenMode mode) { 71 | if (m_device == nullptr) return false; 72 | 73 | if (isOpen()) return false; 74 | 75 | if (mode & WriteOnly) mode |= ReadOnly; 76 | 77 | if (mode & Append) mode |= ReadWrite; 78 | 79 | OpenMode deviceOpenMode; 80 | if (mode == ReadOnly) 81 | deviceOpenMode = ReadOnly; 82 | else 83 | deviceOpenMode = ReadWrite; 84 | 85 | if (mode & Truncate) deviceOpenMode |= Truncate; 86 | 87 | bool ok; 88 | if (m_device->isOpen()) 89 | ok = (m_device->openMode() == deviceOpenMode); 90 | else 91 | ok = m_device->open(deviceOpenMode); 92 | 93 | if (!ok) return false; 94 | 95 | if (m_password.isEmpty()) { 96 | setOpenMode(mode); 97 | return true; 98 | } 99 | 100 | if (!initCipher()) return false; 101 | 102 | m_encrypted = true; 103 | setOpenMode(mode); 104 | 105 | qint64 size = m_device->size(); 106 | if (size == 0 && mode != ReadOnly) insertHeader(); 107 | 108 | if (size > 0) { 109 | if (!tryParseHeader()) { 110 | m_encrypted = false; 111 | m_device->seek(0); 112 | m_device->close(); 113 | return false; 114 | } 115 | } 116 | 117 | if (mode & Append) seek(m_device->size() - kHeaderLength); 118 | 119 | return true; 120 | } 121 | 122 | void CryptFileDevice::insertHeader() { 123 | QDataStream ostream(m_device); 124 | ostream << quint8(0xcd); // cryptdevice byte 125 | ostream << quint8(0x01); // version 126 | ostream << static_cast(m_aesKeyLength); // aes key length 127 | ostream << static_cast(m_numRounds); // iteration count to use 128 | ostream.writeRawData( 129 | QCryptographicHash::hash(m_password, QCryptographicHash::Sha3_256), 130 | kHashLength); 131 | ostream.writeRawData( 132 | QCryptographicHash::hash(m_salt, QCryptographicHash::Sha3_256), 133 | kHashLength); 134 | ostream.writeRawData(QByteArray(kPaddingLength, char(0xcd)), kPaddingLength); 135 | } 136 | 137 | bool CryptFileDevice::tryParseHeader() { 138 | QDataStream istream(m_device); 139 | quint8 cdByte; 140 | istream >> cdByte; 141 | if (cdByte != 0xcd) return false; 142 | 143 | quint8 version; 144 | istream >> version; 145 | if (version != 0x01) return false; 146 | 147 | quint32 aesKeyLength; 148 | istream >> aesKeyLength; 149 | if (static_cast(aesKeyLength) != m_aesKeyLength) return false; 150 | 151 | qint32 numRounds; 152 | istream >> numRounds; 153 | if (numRounds != m_numRounds) return false; 154 | 155 | QByteArray hash(kHashLength, '\0'); 156 | int read = istream.readRawData(hash.data(), kHashLength); 157 | if (read != kHashLength) return false; 158 | 159 | QByteArray expectedPasswordHash = 160 | QCryptographicHash::hash(m_password, QCryptographicHash::Sha3_256); 161 | if (hash != expectedPasswordHash) return false; 162 | 163 | read = istream.readRawData(hash.data(), kHashLength); 164 | if (read != kHashLength) return false; 165 | 166 | QByteArray expectedSaltHash = 167 | QCryptographicHash::hash(m_salt, QCryptographicHash::Sha3_256); 168 | if (hash != expectedSaltHash) return false; 169 | 170 | QByteArray padding(kPaddingLength, '\0'); 171 | read = istream.readRawData(padding.data(), kPaddingLength); 172 | if (read != kPaddingLength) return false; 173 | 174 | QByteArray expectedPadding(kPaddingLength, char(0xcd)); 175 | return padding == expectedPadding; 176 | } 177 | 178 | void CryptFileDevice::close() { 179 | if (!isOpen()) return; 180 | 181 | if ((openMode() & WriteOnly) || (openMode() & Append)) flush(); 182 | 183 | seek(0); 184 | m_device->close(); 185 | setOpenMode(NotOpen); 186 | 187 | if (m_encrypted) m_encrypted = false; 188 | } 189 | 190 | void CryptFileDevice::setFileName(const QString &fileName) { 191 | if (m_device) { 192 | m_device->close(); 193 | if (m_deviceOwner) delete m_device; 194 | } 195 | m_device = new QFile(fileName); 196 | m_deviceOwner = true; 197 | } 198 | 199 | QString CryptFileDevice::fileName() const { 200 | if (m_device != nullptr) return m_device->fileName(); 201 | 202 | return QString(); 203 | } 204 | 205 | void CryptFileDevice::setFileDevice(QFileDevice *device) { 206 | if (m_device) { 207 | m_device->close(); 208 | if (m_deviceOwner) delete m_device; 209 | } 210 | m_device = device; 211 | m_deviceOwner = false; 212 | } 213 | 214 | bool CryptFileDevice::flush() { return m_device->flush(); } 215 | 216 | bool CryptFileDevice::isEncrypted() const { return m_encrypted; } 217 | 218 | qint64 CryptFileDevice::readBlock(qint64 len, QByteArray &ba) { 219 | int length = ba.length(); 220 | qint64 readBytes = 0; 221 | do { 222 | qint64 fileRead = m_device->read(ba.data() + ba.length(), len - readBytes); 223 | if (fileRead <= 0) break; 224 | 225 | readBytes += fileRead; 226 | } while (readBytes < len); 227 | 228 | if (readBytes == 0) return 0; 229 | 230 | QScopedArrayPointer plaintext(decrypt(ba.data() + length, readBytes)); 231 | 232 | ba.append(plaintext.data(), readBytes); 233 | 234 | return readBytes; 235 | } 236 | 237 | qint64 CryptFileDevice::readData(char *data, qint64 len) { 238 | if (!m_encrypted) { 239 | qint64 fileRead = m_device->read(data, len); 240 | return fileRead; 241 | } 242 | 243 | if (len == 0) return m_device->read(data, len); 244 | 245 | QByteArray ba; 246 | ba.reserve(len); 247 | do { 248 | qint64 maxSize = len - ba.length(); 249 | 250 | qint64 size = readBlock(maxSize, ba); 251 | 252 | if (size == 0) break; 253 | } while (ba.length() < len); 254 | 255 | if (ba.isEmpty()) return 0; 256 | 257 | memcpy(data, ba.data(), ba.length()); 258 | 259 | return ba.length(); 260 | } 261 | 262 | qint64 CryptFileDevice::writeData(const char *data, qint64 len) { 263 | if (!m_encrypted) return m_device->write(data, len); 264 | 265 | QScopedArrayPointer cipherText(encrypt(data, len)); 266 | m_device->write(cipherText.data(), len); 267 | 268 | return len; 269 | } 270 | 271 | void CryptFileDevice::initCtr(CtrState *state, const unsigned char *iv) { 272 | qint64 position = pos(); 273 | 274 | state->num = position % AES_BLOCK_SIZE; 275 | 276 | memset(state->ecount, 0, sizeof(state->ecount)); 277 | 278 | /* Initialise counter in 'ivec' */ 279 | qint64 count = position / AES_BLOCK_SIZE; 280 | if (state->num > 0) count++; 281 | 282 | qint64 newCount = count; 283 | if (newCount > 0) newCount = qToBigEndian(count); 284 | 285 | int sizeOfIv = sizeof(state->ivec) - sizeof(qint64); 286 | memcpy(state->ivec + sizeOfIv, &newCount, sizeof(newCount)); 287 | 288 | /* Copy IV into 'ivec' */ 289 | memcpy(state->ivec, iv, sizeOfIv); 290 | 291 | if (count > 0) { 292 | count = qToBigEndian(count - 1); 293 | unsigned char prevIvec[AES_BLOCK_SIZE]; 294 | memcpy(prevIvec, state->ivec, sizeOfIv); 295 | 296 | memcpy(prevIvec + sizeOfIv, &count, sizeof(count)); 297 | 298 | AES_encrypt(prevIvec, state->ecount, &m_aesKey); 299 | } 300 | } 301 | 302 | bool CryptFileDevice::initCipher() { 303 | const EVP_CIPHER *cipher = EVP_enc_null(); 304 | if (m_aesKeyLength == AesKeyLength::kAesKeyLength128) 305 | cipher = EVP_aes_128_ctr(); 306 | else if (m_aesKeyLength == AesKeyLength::kAesKeyLength192) 307 | cipher = EVP_aes_192_ctr(); 308 | else if (m_aesKeyLength == AesKeyLength::kAesKeyLength256) 309 | cipher = EVP_aes_256_ctr(); 310 | else 311 | Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown value of AesKeyLength"); 312 | 313 | auto ctx = EVP_CIPHER_CTX_new(); 314 | EVP_CIPHER_CTX_init(ctx); 315 | EVP_EncryptInit_ex(ctx, cipher, nullptr, nullptr, nullptr); 316 | int keyLength = EVP_CIPHER_CTX_key_length(ctx); 317 | int ivLength = EVP_CIPHER_CTX_iv_length(ctx); 318 | 319 | QVector key(keyLength); 320 | QVector iv(ivLength); 321 | 322 | int ok = EVP_BytesToKey( 323 | cipher, EVP_sha256(), 324 | m_salt.isEmpty() ? nullptr 325 | : reinterpret_cast(m_salt.data()), 326 | reinterpret_cast(m_password.data()), m_password.length(), 327 | m_numRounds, key.data(), iv.data()); 328 | 329 | EVP_CIPHER_CTX_free(ctx); 330 | 331 | if (ok == 0) return false; 332 | 333 | int res = AES_set_encrypt_key(key.data(), keyLength * 8, &m_aesKey); 334 | if (res != 0) return false; 335 | 336 | initCtr(&m_ctrState, iv.data()); 337 | 338 | return true; 339 | } 340 | 341 | char *CryptFileDevice::encrypt(const char *plainText, qint64 len) { 342 | auto cipherText = new unsigned char[len]; 343 | 344 | qint64 processLen = 0; 345 | do { 346 | int maxCipherLen = len > std::numeric_limits::max() 347 | ? std::numeric_limits::max() 348 | : len; 349 | CRYPTO_ctr128_encrypt( 350 | reinterpret_cast(plainText) + processLen, 351 | cipherText + processLen, maxCipherLen, &m_aesKey, m_ctrState.ivec, 352 | m_ctrState.ecount, &m_ctrState.num, (block128_f)AES_encrypt); 353 | 354 | processLen += maxCipherLen; 355 | len -= maxCipherLen; 356 | } while (len > 0); 357 | 358 | return reinterpret_cast(cipherText); 359 | } 360 | 361 | char *CryptFileDevice::decrypt(const char *cipherText, qint64 len) { 362 | auto plainText = new unsigned char[len]; 363 | 364 | qint64 processLen = 0; 365 | do { 366 | int maxPlainLen = len > std::numeric_limits::max() 367 | ? std::numeric_limits::max() 368 | : len; 369 | CRYPTO_ctr128_encrypt( 370 | reinterpret_cast(cipherText) + processLen, 371 | plainText + processLen, maxPlainLen, &m_aesKey, m_ctrState.ivec, 372 | m_ctrState.ecount, &m_ctrState.num, (block128_f)AES_encrypt); 373 | 374 | processLen += maxPlainLen; 375 | len -= maxPlainLen; 376 | } while (len > 0); 377 | 378 | return reinterpret_cast(plainText); 379 | } 380 | 381 | bool CryptFileDevice::atEnd() const { return QIODevice::atEnd(); } 382 | 383 | qint64 CryptFileDevice::bytesAvailable() const { 384 | return QIODevice::bytesAvailable(); 385 | } 386 | 387 | qint64 CryptFileDevice::pos() const { return QIODevice::pos(); } 388 | 389 | bool CryptFileDevice::seek(qint64 pos) { 390 | bool result = QIODevice::seek(pos); 391 | if (m_encrypted) { 392 | m_device->seek(kHeaderLength + pos); 393 | initCtr(&m_ctrState, m_ctrState.ivec); 394 | } else { 395 | m_device->seek(pos); 396 | } 397 | 398 | return result; 399 | } 400 | 401 | qint64 CryptFileDevice::size() const { 402 | if (m_device == nullptr) return 0; 403 | 404 | if (!m_encrypted) return m_device->size(); 405 | 406 | return m_device->size() - kHeaderLength; 407 | } 408 | 409 | bool CryptFileDevice::remove() { 410 | if (m_device == nullptr) return false; 411 | 412 | QString fileName = m_device->fileName(); 413 | if (fileName.isEmpty()) return false; 414 | 415 | if (isOpen()) close(); 416 | 417 | bool ok = QFile::remove(fileName); 418 | if (ok) m_device = nullptr; 419 | 420 | return ok; 421 | } 422 | 423 | bool CryptFileDevice::exists() const { 424 | if (m_device == nullptr) return false; 425 | 426 | QString fileName = m_device->fileName(); 427 | if (fileName.isEmpty()) return false; 428 | 429 | return QFile::exists(fileName); 430 | } 431 | 432 | bool CryptFileDevice::rename(const QString &newName) { 433 | if (m_device == nullptr) return false; 434 | 435 | QString fileName = m_device->fileName(); 436 | if (fileName.isEmpty()) return false; 437 | 438 | if (isOpen()) close(); 439 | 440 | bool ok = QFile::rename(fileName, newName); 441 | if (ok) setFileName(newName); 442 | 443 | return ok; 444 | } 445 | -------------------------------------------------------------------------------- /src/cryptfiledevice.h: -------------------------------------------------------------------------------- 1 | #ifndef CRYPTFILEDEVICE_H 2 | #define CRYPTFILEDEVICE_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | class QFileDevice; 9 | 10 | struct CtrState { 11 | unsigned char ivec[AES_BLOCK_SIZE]; 12 | unsigned int num; 13 | unsigned char ecount[AES_BLOCK_SIZE]; 14 | }; 15 | 16 | class CryptFileDevice : public QIODevice { 17 | Q_OBJECT 18 | Q_DISABLE_COPY(CryptFileDevice) 19 | public: 20 | enum class AesKeyLength : quint32 { 21 | kAesKeyLength128, 22 | kAesKeyLength192, 23 | kAesKeyLength256 24 | }; 25 | 26 | explicit CryptFileDevice(QObject *parent = nullptr); 27 | explicit CryptFileDevice(QFileDevice *device, QObject *parent = nullptr); 28 | explicit CryptFileDevice(QFileDevice *device, const QByteArray &password, 29 | const QByteArray &salt, QObject *parent = nullptr); 30 | explicit CryptFileDevice(const QString &fileName, const QByteArray &password, 31 | const QByteArray &salt, QObject *parent = nullptr); 32 | ~CryptFileDevice() override; 33 | 34 | bool open(OpenMode flags) override; 35 | void close() override; 36 | 37 | void setFileName(const QString &fileName); 38 | QString fileName() const; 39 | 40 | void setFileDevice(QFileDevice *device); 41 | 42 | void setPassword(const QByteArray &password); 43 | void setSalt(const QByteArray &salt); 44 | void setKeyLength(AesKeyLength keyLength); 45 | void setNumRounds(int numRounds); 46 | 47 | bool isEncrypted() const; 48 | qint64 size() const override; 49 | 50 | bool atEnd() const override; 51 | qint64 bytesAvailable() const override; 52 | qint64 pos() const override; 53 | bool seek(qint64 pos) override; 54 | bool flush(); 55 | bool remove(); 56 | bool exists() const; 57 | bool rename(const QString &newName); 58 | 59 | protected: 60 | qint64 readData(char *data, qint64 len) override; 61 | qint64 writeData(const char *data, qint64 len) override; 62 | 63 | qint64 readBlock(qint64 len, QByteArray &ba); 64 | 65 | private: 66 | bool initCipher(); 67 | void initCtr(CtrState *state, const unsigned char *iv); 68 | char *encrypt(const char *plainText, qint64 len); 69 | char *decrypt(const char *cipherText, qint64 len); 70 | 71 | void insertHeader(); 72 | bool tryParseHeader(); 73 | 74 | QFileDevice *m_device = nullptr; 75 | bool m_deviceOwner = false; 76 | bool m_encrypted = false; 77 | 78 | QByteArray m_password; 79 | QByteArray m_salt; 80 | AesKeyLength m_aesKeyLength = AesKeyLength::kAesKeyLength256; 81 | int m_numRounds = 5; 82 | 83 | CtrState m_ctrState = {}; 84 | AES_KEY m_aesKey = {}; 85 | }; 86 | 87 | #endif // CRYPTFILEDEVICE_H 88 | --------------------------------------------------------------------------------