Compare commits

...

3 commits
v1.0 ... master

Author SHA1 Message Date
ed4098983a
Merge pull request #1 from wbrawner/cracker-factory
Implement CrackerFactory
2018-04-18 21:01:38 -05:00
599e155490 Implement CrackerFactory
The idea with this was to get away from the singleton pattern, since it
makes tests difficult and fragile. Dependency injection would have been
an option too, but for simplicity's sake, I chose the factory method.
There were also some strange errors on Windows that I've corrected,
so the program should run on Windows now as well.
2018-04-18 20:56:37 -05:00
81db6720c9 Fix version number 2018-04-15 19:54:55 -05:00
12 changed files with 136 additions and 134 deletions

View file

@ -1,7 +1,7 @@
language: java language: java
jdk: jdk:
- oraclejdk8 - oraclejdk8
os: os:
- linux - linux
script:
- ./gradlew check

View file

@ -1,36 +1,53 @@
# KeeCrack [![Build Status](https://travis-ci.org/wbrawner/keecrack.svg?branch=master)](https://travis-ci.org/wbrawner/keecrack) # KeeCrack [![Build Status](https://travis-ci.org/wbrawner/keecrack.svg?branch=master)](https://travis-ci.org/wbrawner/keecrack)
[![Maintainability](https://api.codeclimate.com/v1/badges/a35dff49221e36abf189/maintainability)](https://codeclimate.com/github/wbrawner/keecrack/maintainability)
[![Test Coverage](https://api.codeclimate.com/v1/badges/a35dff49221e36abf189/test_coverage)](https://codeclimate.com/github/wbrawner/keecrack/test_coverage)
KeeCrack is a Java program used for brute-forcing KeePass database file master passwords. This should go without saying KeeCrack is a Java program used for brute-forcing KeePass database file master passwords. This should go without saying
but use of this application is prohibited without the express consent of the owner of the database file. KeeCrack works but use of this application is prohibited without the express consent of the owner of the database file. KeeCrack works
by taking a KeePass database file, an optional key file, and a word list, then attempts to open the database with the by taking a KeePass database file, an optional key file, and a word list, then attempts to open the database with the
give key file/password pair until it finds a successful password. KeeCrack does not do incremental word list generation give key file/password pair until it finds a successful password. The word list can either be a newline separated
at this time, though you can file, or a regular expression pattern.
## Usage ## Usage
You can download a DEB, JAR, or RPM from the releases page. Each build contains both a <abbr title="graphical user You can download a JAR from the releases page. Each build contains both a <abbr title="graphical user
interface">GUI</abbr> and <abbr title="command line interface">CLI</abbr>. For the GUI, download and double-click the interface">GUI</abbr> and <abbr title="command line interface">CLI</abbr>. For the GUI, download and double-click the
JAR file JAR file. For command line usage, run the jar with `java -jar keecrack.jar -h` to get the following output:
```
usage: KeeCrack [-h] [--verbose] [--incremental] --word-list WORD-LIST-FILE [--key-file KEY-FILE] database
Brute force KeePass database files
positional arguments:
database the database file to brute force
named arguments:
-h, --help show this help message and exit
--verbose, -v Increase logging output
--incremental, -i Use pattern-based (incremental) guesses instead of a list of words from a file
--word-list WORD-LIST-FILE, -w WORD-LIST-FILE
a file containing newline-separated words to use as the passwords, or the pattern to generate words from if the --incremental flag is set
--key-file KEY-FILE, -k KEY-FILE
the key file to use with the database
```
## Building ## Building
KeeCrack makes use of Gradle, so to build it yourself, you can just run KeeCrack makes use of Gradle, so to build it yourself, you can just run
./gradlew jfxJar ./gradlew shadowJar
This will produce a JAR output, though you can also create platform-specific binaries with the following:
./gradlew jfxNative
For more information on building for your OS, please see the README for the
[javafx-gradle-plugin](https://github .com/FibreFoX/javafx-gradle-plugin#requirements)
## Contributing ## Contributing
If you'd like to contribute, please fork the repository, make your changes, squash your commits, and send a pull request If you'd like to contribute, please fork the repository, make your changes, squash your commits, and send a pull
. request.
## Attribution
KeeCrack is possible thanks to the following libraries:
[Generex](https://github.com/mifmif/Generex)
[KeePassJava2](https://github.com/jorabin/KeePassJava2)
## License ## License

View file

@ -30,7 +30,7 @@ allprojects {
subprojects { subprojects {
group 'com.wbrawner' group 'com.wbrawner'
version '1.0-SNAPSHOT' version '1.0'
apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'java' apply plugin: 'java'

View file

@ -1,4 +1,5 @@
#Sun Apr 15 14:52:35 CDT 2018 # suppress inspection "UnusedProperty" for whole file
#Wed Apr 18 08:19:13 CDT 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View file

@ -16,6 +16,7 @@
package com.wbrawner.keecrack.cli; package com.wbrawner.keecrack.cli;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;
import com.wbrawner.keecrack.lib.CrackerFactory;
import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.KeeCrack;
import com.wbrawner.keecrack.lib.view.CrackingView; import com.wbrawner.keecrack.lib.view.CrackingView;
import net.sourceforge.argparse4j.ArgumentParsers; import net.sourceforge.argparse4j.ArgumentParsers;
@ -71,7 +72,7 @@ public class Main {
Namespace res = parser.parseArgs(args); Namespace res = parser.parseArgs(args);
isVerbose = res.getBoolean("verbose"); isVerbose = res.getBoolean("verbose");
isIncremental = res.getBoolean("incremental"); isIncremental = res.getBoolean("incremental");
KeeCrack keeCrack = KeeCrack.getInstance(); KeeCrack keeCrack = new CrackerFactory().getCracker(true);
keeCrack.setCrackingView(new CLICrackingView()); keeCrack.setCrackingView(new CLICrackingView());
String databasePath = res.getString("database"); String databasePath = res.getString("database");

View file

@ -16,6 +16,7 @@
package com.wbrawner.keecrack.gui; package com.wbrawner.keecrack.gui;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;
import com.wbrawner.keecrack.lib.CrackerFactory;
import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.KeeCrack;
import com.wbrawner.keecrack.lib.view.CrackingView; import com.wbrawner.keecrack.lib.view.CrackingView;
import javafx.application.Platform; import javafx.application.Platform;
@ -41,10 +42,11 @@ public class CrackingController implements Initializable, CrackingView {
private Label timeElapsed; private Label timeElapsed;
private Stage stage; private Stage stage;
private KeeCrack keeCrack;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
final KeeCrack keeCrack = KeeCrack.getInstance(); keeCrack = new CrackerFactory().getCracker(true);
keeCrack.setCrackingView(this); keeCrack.setCrackingView(this);
new Thread(keeCrack::attack).start(); new Thread(keeCrack::attack).start();
} }
@ -88,7 +90,6 @@ public class CrackingController implements Initializable, CrackingView {
} }
private void onClose() { private void onClose() {
KeeCrack keeCrack = KeeCrack.getInstance();
keeCrack.abort(); keeCrack.abort();
keeCrack.setCrackingView(null); keeCrack.setCrackingView(null);
} }

View file

@ -16,6 +16,7 @@
package com.wbrawner.keecrack.gui; package com.wbrawner.keecrack.gui;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;
import com.wbrawner.keecrack.lib.CrackerFactory;
import com.wbrawner.keecrack.lib.KeeCrack; import com.wbrawner.keecrack.lib.KeeCrack;
import com.wbrawner.keecrack.lib.view.FormView; import com.wbrawner.keecrack.lib.view.FormView;
import javafx.fxml.FXML; import javafx.fxml.FXML;
@ -48,7 +49,6 @@ public class MainController implements Initializable, FormView {
private TextField wordlist; private TextField wordlist;
@FXML @FXML
private Button crackButton; private Button crackButton;
@FXML
private ToggleGroup wordlistType; private ToggleGroup wordlistType;
@FXML @FXML
private RadioButton wordlistFile; private RadioButton wordlistFile;
@ -57,10 +57,10 @@ public class MainController implements Initializable, FormView {
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
keeCrack = KeeCrack.getInstance(); keeCrack = new CrackerFactory().getCracker(true);
keeCrack.setFormView(this); keeCrack.setFormView(this);
database.setOnMouseClicked(event -> { database.setOnMouseClicked(event -> {
if (KeeCrack.getInstance().isCracking()) { if (keeCrack.isCracking()) {
return; return;
} }
@ -69,7 +69,7 @@ public class MainController implements Initializable, FormView {
}); });
key.setOnMouseClicked(event -> { key.setOnMouseClicked(event -> {
if (KeeCrack.getInstance().isCracking()) { if (keeCrack.isCracking()) {
return; return;
} }
@ -81,7 +81,7 @@ public class MainController implements Initializable, FormView {
crackButton.setOnMouseClicked(event -> { crackButton.setOnMouseClicked(event -> {
try { try {
if (KeeCrack.getInstance().isCracking()) { if (keeCrack.isCracking()) {
return; return;
} }
Stage stage = new Stage(); Stage stage = new Stage();
@ -107,13 +107,16 @@ public class MainController implements Initializable, FormView {
e.printStackTrace(); e.printStackTrace();
} }
}); });
if (wordlistType == null)
wordlistType = new ToggleGroup();
wordlistFile.setToggleGroup(wordlistType);
wordlistPattern.setToggleGroup(wordlistType);
wordlistType.selectedToggleProperty().addListener((observableValue, oldToggle, newToggle) -> wordlistType.selectedToggleProperty().addListener((observableValue, oldToggle, newToggle) ->
updateWordListHandler()); updateWordListHandler());
if (keeCrack.getDatabaseFile() != null) if (keeCrack.getDatabaseFile() != null)
onDatabaseFileSet(keeCrack.getDatabaseFile().getName()); onDatabaseFileSet(keeCrack.getDatabaseFile().getName());
if (keeCrack.getKeyFile() != null) if (keeCrack.getKeyFile() != null)
onKeyFileSet(keeCrack.getKeyFile().getName());
if (keeCrack.getWordListName() != null) if (keeCrack.getWordListName() != null)
onWordListSet(keeCrack.getWordListName()); onWordListSet(keeCrack.getWordListName());
} }
@ -127,7 +130,7 @@ public class MainController implements Initializable, FormView {
new FileChooser.ExtensionFilter(description, extensions) new FileChooser.ExtensionFilter(description, extensions)
); );
Stage stage = new Stage(); Stage stage = new Stage();
stage.setOnHidden(e -> KeeCrack.getInstance().setFormView(null)); stage.setOnHidden(e -> keeCrack.setFormView(null));
return fileChooser.showOpenDialog(stage); return fileChooser.showOpenDialog(stage);
} }
@ -137,7 +140,7 @@ public class MainController implements Initializable, FormView {
wordlist.setEditable(false); wordlist.setEditable(false);
wordlist.setCursor(Cursor.HAND); wordlist.setCursor(Cursor.HAND);
wordlist.setOnMouseClicked(event -> { wordlist.setOnMouseClicked(event -> {
if (KeeCrack.getInstance().isCracking()) { if (keeCrack.isCracking()) {
return; return;
} }
File wordlistFile = getFile("Text Files", "txt"); File wordlistFile = getFile("Text Files", "txt");

View file

@ -69,12 +69,8 @@
<HBox.margin> <HBox.margin>
<Insets left="20.0"/> <Insets left="20.0"/>
</HBox.margin> </HBox.margin>
<toggleGroup>
<ToggleGroup fx:id="wordlistType"/>
</toggleGroup>
</RadioButton> </RadioButton>
<RadioButton fx:id="wordlistFile" selected="true" text="From File" <RadioButton fx:id="wordlistFile" selected="true" text="From File">
toggleGroup="wordlistType">
<HBox.margin> <HBox.margin>
<Insets left="20.0"/> <Insets left="20.0"/>
</HBox.margin> </HBox.margin>

View file

@ -0,0 +1,36 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib;
/**
* Class responsible for acquiring a {@link KeeCrack} instance
*/
public class CrackerFactory {
private static final Object singletonLock = new Object();
private static KeeCrack singleton;
public KeeCrack getCracker(boolean asSingleton) {
if (asSingleton) {
synchronized (singletonLock) {
if (singleton == null) {
singleton = new KeeCrack();
}
return singleton;
}
}
return new KeeCrack();
}
}

View file

@ -22,9 +22,7 @@ import org.linguafranca.pwdb.kdbx.KdbxCreds;
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxHeader; import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxHeader;
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxSerializer; import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxSerializer;
import java.io.ByteArrayInputStream; import java.io.*;
import java.io.File;
import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Objects; import java.util.Objects;
@ -33,11 +31,11 @@ import java.util.concurrent.atomic.AtomicReference;
/** /**
* The main class responsible for handling the brute forcing of the KeePass database. You do not contruct the * The main class responsible for handling the brute forcing of the KeePass database. You do not contruct the
* KeeCrack instance directly, but rather call {@link #getInstance()}. To begin, you should set the form view and * KeeCrack instance directly, but rather call {@link CrackerFactory#getCracker(boolean)}. To begin, you should set the
* cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)} respectively. These * form view and cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)}
* views will be responsible for displaying information like error messages and status updates. The cracking will * respectively. These views will be responsible for displaying information like error messages and status updates.
* work without these, though it's highly recommended to set them prior to beginning. The database and wordlist must * The cracking will work without these, though it's highly recommended to set them prior to beginning. The database
* be set, while the key file is also optional. If either of the required parameters are missing, the * and wordlist must be set, while the key file is also optional. If either of the required parameters are missing, the
* {@link #attack()} operation will abort, sending either {@link Code#ERROR_INVALID_DATABASE_FILE} or * {@link #attack()} operation will abort, sending either {@link Code#ERROR_INVALID_DATABASE_FILE} or
* {@link Code#ERROR_INVALID_WORD_LIST}, respectively. The word list can either be a pattern, in which case * {@link Code#ERROR_INVALID_WORD_LIST}, respectively. The word list can either be a pattern, in which case
* incremental guessing will take place, or a file, in which case each line of the file will be considered a password * incremental guessing will take place, or a file, in which case each line of the file will be considered a password
@ -51,7 +49,6 @@ import java.util.concurrent.atomic.AtomicReference;
* first parameter will be null. * first parameter will be null.
*/ */
public class KeeCrack { public class KeeCrack {
private static final AtomicReference<KeeCrack> singleton = new AtomicReference<>(null);
private final Object keyFileLock = new Object(); private final Object keyFileLock = new Object();
private final AtomicBoolean isCracking = new AtomicBoolean(false); private final AtomicBoolean isCracking = new AtomicBoolean(false);
/** /**
@ -67,33 +64,40 @@ public class KeeCrack {
private WordList wordList; private WordList wordList;
private int guessCount = 0; private int guessCount = 0;
private KeeCrack() {
}
public static KeeCrack getInstance() {
if (singleton.get() == null) {
singleton.set(new KeeCrack());
}
return singleton.get();
}
/** /**
* Call this to reset the state of the KeeCrack instance. Note that you will need to set the views again after * To get an instance of the class, use the {@link CrackerFactory}
* calling this
*/ */
public void reset() { KeeCrack() {
setDatabaseFile(null);
setKeyFile(null);
setWordListFile(null);
setCrackingView(null);
setFormView(null);
} }
public void abort() { public void abort() {
abort.set(true); abort.set(true);
} }
private static void closeQuietly(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (IOException ignored) {
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private static byte[] getFileBytes(File file) {
byte[] fileBytes;
try (InputStream inputStream = new FileInputStream(file)) {
fileBytes = new byte[inputStream.available()];
inputStream.read(fileBytes);
return fileBytes;
} catch (IOException e) {
e.printStackTrace();
return new byte[]{};
}
}
/** /**
* Call this to begin brute-forcing the database file. * Call this to begin brute-forcing the database file.
*/ */
@ -123,6 +127,7 @@ public class KeeCrack {
while (!haveCorrectPassword && wordList.hasNext()) { while (!haveCorrectPassword && wordList.hasNext()) {
if (abort.get()) { if (abort.get()) {
sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED); sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED);
cleanup();
isCracking.set(false); isCracking.set(false);
abort.set(false); abort.set(false);
return; return;
@ -147,17 +152,23 @@ public class KeeCrack {
} catch (NullPointerException ignored) { } catch (NullPointerException ignored) {
} }
cleanup();
isCracking.set(false); isCracking.set(false);
} }
private void cleanup() {
databaseBytes = null;
keyBytes = null;
}
@SuppressWarnings("ResultOfMethodCallIgnored") @SuppressWarnings("ResultOfMethodCallIgnored")
private void prepareByteArrays() { private void prepareByteArrays() {
databaseBytes = Utils.getFileBytes(databaseFile); databaseBytes = getFileBytes(databaseFile);
synchronized (keyFileLock) { synchronized (keyFileLock) {
if (keyFile == null) { if (keyFile == null) {
return; return;
} }
keyBytes = Utils.getFileBytes(keyFile); keyBytes = getFileBytes(keyFile);
} }
} }
@ -182,8 +193,8 @@ public class KeeCrack {
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
Utils.closeQuietly(databaseInput); closeQuietly(databaseInput);
Utils.closeQuietly(keyFileInput); closeQuietly(keyFileInput);
} }
return false; return false;
} }

View file

@ -1,44 +0,0 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib;
import java.io.*;
class Utils {
static void closeQuietly(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (IOException ignored) {
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
static byte[] getFileBytes(File file) {
byte[] fileBytes;
try (InputStream inputStream = new FileInputStream(file)) {
fileBytes = new byte[inputStream.available()];
inputStream.read(fileBytes);
return fileBytes;
} catch (IOException e) {
e.printStackTrace();
return new byte[]{};
}
}
}

View file

@ -21,13 +21,9 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -41,30 +37,14 @@ public class KeeCrackTest {
public void setUp() { public void setUp() {
mockCrackingView = mock(CrackingView.class); mockCrackingView = mock(CrackingView.class);
mockFormView = mock(FormView.class); mockFormView = mock(FormView.class);
keeCrack = KeeCrack.getInstance(); keeCrack = new CrackerFactory().getCracker(false);
} }
@After @After
public void tearDown() { public void tearDown() {
keeCrack.reset();
Utils.rmdir(Utils.getTmpDir()); Utils.rmdir(Utils.getTmpDir());
} }
@Test
public void resetTest() {
keeCrack.setDatabaseFile(new File("Database"));
keeCrack.setKeyFile(new File("Keyfile"));
keeCrack.setWordListPattern("Some pattern");
assertNotNull(keeCrack.getDatabaseFile());
assertNotNull(keeCrack.getKeyFile());
assertNotNull(keeCrack.getWordList());
keeCrack.reset();
assertNull(keeCrack.getDatabaseFile());
assertNull(keeCrack.getKeyFile());
assertNull(keeCrack.getWordList());
assertFalse(keeCrack.isCracking());
}
@Test @Test
public void abortTest() throws IOException { public void abortTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));