diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 12ec264f5..98d481969 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -83,3 +83,23 @@ QString SymmetricCipher::errorString() const { return m_backend->errorString(); } + +SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher) +{ + if (cipher == KeePass2::CIPHER_AES) { + return SymmetricCipher::Aes256; + } + else { + return SymmetricCipher::Twofish; + } +} + +Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) +{ + switch (algo) { + case SymmetricCipher::Aes256: + return KeePass2::CIPHER_AES; + default: + return KeePass2::CIPHER_TWOFISH; + } +} diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 4fc06b7de..0070ed7de 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -23,6 +23,7 @@ #include #include "crypto/SymmetricCipherBackend.h" +#include "format/KeePass2.h" class SymmetricCipher { @@ -71,6 +72,9 @@ public: int blockSize() const; QString errorString() const; + static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher); + static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo); + private: static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, SymmetricCipher::Direction direction); diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index b49ae4f6a..91ee48293 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -33,6 +33,7 @@ namespace KeePass2 const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); + const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 1371aaa6a..668165c5f 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -118,7 +118,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); - SymmetricCipherStream cipherStream(m_device, SymmetricCipher::Aes256, + SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()), SymmetricCipher::Cbc, SymmetricCipher::Decrypt); if (!cipherStream.init(finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); @@ -330,7 +330,7 @@ void KeePass2Reader::setCipher(const QByteArray& data) else { Uuid uuid(data); - if (uuid != KeePass2::CIPHER_AES) { + if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) { raiseError("Unsupported cipher"); } else { diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index dfbbf3532..e6ec5f600 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -87,8 +87,8 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); CHECK_RETURN(writeData(header.data())); - SymmetricCipherStream cipherStream(device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, - SymmetricCipher::Encrypt); + SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()), + SymmetricCipher::Cbc, SymmetricCipher::Encrypt); cipherStream.init(finalKey, encryptionIV); if (!cipherStream.open(QIODevice::WriteOnly)) { raiseError(cipherStream.errorString()); diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index b0759bf3a..59a3c5c3a 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -21,6 +21,8 @@ #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" +#include "crypto/SymmetricCipher.h" +#include "format/KeePass2.h" #include "keys/CompositeKey.h" DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) @@ -53,6 +55,7 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->dbDescriptionEdit->setText(meta->description()); m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); + m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher())); m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds()); if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); @@ -82,6 +85,8 @@ void DatabaseSettingsWidget::save() meta->setName(m_ui->dbNameEdit->text()); meta->setDescription(m_ui->dbDescriptionEdit->text()); meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); + m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast + (m_ui->AlgorithmComboBox->currentIndex()))); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); if (static_cast(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index 5d1f3d9f1..1c233bdd4 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -49,35 +49,7 @@ - - - - Transform rounds: - - - - - - - Default username: - - - - - - - true - - - - - - - Use recycle bin: - - - - + @@ -100,7 +72,7 @@ - + @@ -117,7 +89,7 @@ - + @@ -144,23 +116,72 @@ - + Max. history items: - + Max. history size: - + + + + Transform rounds: + + + + + + + + Default username: + + + + + + + Use recycle bin: + + + + + + + true + + + + + + + Algorithm: + + + + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index 698ecb204..3edf735b8 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -123,6 +123,127 @@ void TestSymmetricCipher::testAes256CbcDecryption() plainText); } +void TestSymmetricCipher::testTwofish256CbcEncryption() +{ + // NIST MCT Known-Answer Tests (cbc_e_m.txt) + // https://www.schneier.com/code/twofish-kat.zip + + QVector keys { + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("8D55E4849A4DED08D89881E6708EDD26BEEE942073DFB3790B2798B240ACD74A"), + QByteArray::fromHex("606EFDC2066A837AF0430EBE4CF1F21071CCB236C33B4B9D82404FDB05C74621"), + QByteArray::fromHex("B119AA9485CEEEB4CC778AF21121E54DE4BDBA3498C61C8FD9004AA0C71909C3") + }; + QVector ivs { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"), + QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"), + QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2") + }; + QVector plainTexts { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("D0A260EB41755B19374BABF259A79DB3"), + QByteArray::fromHex("5DF7846FDB38B611EFD32A1429294095"), + QByteArray::fromHex("ED3B19469C276E7228DB8F583C7F2F36"), + QByteArray::fromHex("D177575683A46DCE3C34844C5DD0175D") + }; + QVector cipherTexts { + QByteArray::fromHex("EA7162E65490B03B1AE4871FB35EF23B"), + QByteArray::fromHex("549FF6C6274F034211C31FADF3F22571"), + QByteArray::fromHex("CF222616B0E4F8E48967D769456B916B"), + QByteArray::fromHex("957108025BFD57125B40057BC2DE4FE2"), + QByteArray::fromHex("6F725C5950133F82EF021A94CADC8508") + }; + + SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Encrypt); + bool ok; + + for (int i = 0; i < keys.size(); ++i) { + cipher.init(keys[i], ivs[i]); + QByteArray ptNext = plainTexts[i]; + QByteArray ctPrev = ivs[i]; + QByteArray ctCur; + QCOMPARE(cipher.blockSize(), 16); + for (int j = 0; j < 5000; ++j) { + ctCur = cipher.process(ptNext, &ok); + if (!ok) + break; + ptNext = ctPrev; + ctPrev = ctCur; + + ctCur = cipher.process(ptNext, &ok); + if (!ok) + break; + ptNext = ctPrev; + ctPrev = ctCur; + } + + QVERIFY(ok); + QCOMPARE(ctCur, cipherTexts[i]); + } +} + +void TestSymmetricCipher::testTwofish256CbcDecryption() +{ + // NIST MCT Known-Answer Tests (cbc_d_m.txt) + // https://www.schneier.com/code/twofish-kat.zip + + QVector keys { + QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"), + QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A942CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("EBA31FF8D2A24FDD769A937353E23257294A33394E4D17A668060AD8230811A1"), + QByteArray::fromHex("1DCF1915C389AB273F80F897BF008F058ED89F58A95C1BE523C4B11295ED2D0F"), + QByteArray::fromHex("491B9A66D3ED4EF19F02180289D5B1A1C2596AE568540A95DC5244198A9B8869") + }; + QVector ivs { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("1B1FE8F5A911CD4C0D800EDCE8ED0A94"), + QByteArray::fromHex("F0BCF70D7BB382917B1A9DAFBB0F38C3"), + QByteArray::fromHex("F66C06ED112BE4FA491A6BE4ECE2BD52"), + QByteArray::fromHex("54D483731064E5D6A082E09536D53EA4") + }; + QVector plainTexts { + QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"), + QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"), + QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566"), + QByteArray::fromHex("BD959F5B787037631A37051EA5F369F8") + }; + QVector cipherTexts { + QByteArray::fromHex("00000000000000000000000000000000"), + QByteArray::fromHex("2CBA6271A1044F90C30BA8FE91E1C163"), + QByteArray::fromHex("05F05148EF495836AB0DA226B2E9D0C2"), + QByteArray::fromHex("A792AC61E7110C434BC2BBCAB6E53CAE"), + QByteArray::fromHex("4C81F5BDC1081170FF96F50B1F76A566") + }; + + SymmetricCipher cipher(SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt); + bool ok; + + for (int i = 0; i < keys.size(); ++i) { + cipher.init(keys[i], ivs[i]); + QByteArray ctNext = cipherTexts[i]; + QByteArray ptCur; + QCOMPARE(cipher.blockSize(), 16); + for (int j = 0; j < 5000; ++j) { + ptCur = cipher.process(ctNext, &ok); + if (!ok) + break; + ctNext = ptCur; + + ptCur = cipher.process(ctNext, &ok); + if (!ok) + break; + ctNext = ptCur; + } + + QVERIFY(ok); + QCOMPARE(ptCur, plainTexts[i]); + } +} + void TestSymmetricCipher::testSalsa20() { // http://www.ecrypt.eu.org/stream/svn/viewcvs.cgi/ecrypt/trunk/submissions/salsa20/full/verified.test-vectors?logsort=rev&rev=210&view=markup diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index 1ac45793f..17fa77a49 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -28,6 +28,8 @@ private Q_SLOTS: void initTestCase(); void testAes256CbcEncryption(); void testAes256CbcDecryption(); + void testTwofish256CbcEncryption(); + void testTwofish256CbcDecryption(); void testSalsa20(); void testPadding(); void testStreamReset();