Implement incremental cracking
This commit is contained in:
parent
43cf53c0ca
commit
ef15a10a4e
28 changed files with 833 additions and 189 deletions
12
README.md
12
README.md
|
@ -3,7 +3,10 @@
|
|||
[![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
|
||||
but use of this application is prohibited without the express consent of the owner of the database file.
|
||||
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
|
||||
give key file/password pair until it finds a successful password. KeeCrack does not do incremental word list generation
|
||||
at this time, though you can
|
||||
|
||||
## Usage
|
||||
|
||||
|
@ -17,6 +20,13 @@ KeeCrack makes use of Gradle, so to build it yourself, you can just run
|
|||
|
||||
./gradlew jfxJar
|
||||
|
||||
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
|
||||
|
||||
If you'd like to contribute, please fork the repository, make your changes, squash your commits, and send a pull request
|
||||
|
|
15
build.gradle
15
build.gradle
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
|
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
4
gradle/wrapper/gradle-wrapper.properties
vendored
4
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
|||
#Sat Apr 07 16:39:20 CDT 2018
|
||||
#Sun Apr 15 14:52:35 CDT 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
dependencies {
|
||||
compile project(':keecrack-lib')
|
||||
compile project(':keecrack-gui')
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.cli;
|
||||
|
||||
import com.wbrawner.keecrack.lib.Code;
|
||||
|
@ -10,13 +25,22 @@ import net.sourceforge.argparse4j.inf.ArgumentParserException;
|
|||
import net.sourceforge.argparse4j.inf.Namespace;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.Duration;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class Main {
|
||||
private static final String LOG_SEPARATOR = " - ";
|
||||
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss.SSS]");
|
||||
private static boolean isVerbose = false;
|
||||
private static boolean isIncremental = false;
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
com.wbrawner.keecrack.gui.Main.main(new String[]{});
|
||||
return;
|
||||
}
|
||||
ArgumentParser parser = ArgumentParsers.newFor("KeeCrack")
|
||||
.build()
|
||||
.description("Brute force KeePass database files");
|
||||
|
@ -24,13 +48,15 @@ public class Main {
|
|||
.help("Increase logging output")
|
||||
.action(Arguments.storeTrue())
|
||||
.dest("verbose");
|
||||
parser.addArgument("--gui")
|
||||
parser.addArgument("--incremental", "-i")
|
||||
.action(Arguments.storeTrue())
|
||||
.help("launch the graphical interface (ignores other options)")
|
||||
.dest("gui");
|
||||
.help("Use pattern-based (incremental) guesses instead of a list of words from a file")
|
||||
.dest("incremental");
|
||||
parser.addArgument("--word-list", "-w")
|
||||
.help("a file containing newline-separated words to use as the passwords")
|
||||
.help("a file containing newline-separated words to use as the passwords, or the pattern to generate " +
|
||||
"words from if the --incremental flag is set")
|
||||
.dest("wordlist")
|
||||
.required(true)
|
||||
.metavar("WORD-LIST-FILE");
|
||||
parser.addArgument("--key-file", "-k")
|
||||
.help("the key file to use with the database")
|
||||
|
@ -44,6 +70,7 @@ public class Main {
|
|||
try {
|
||||
Namespace res = parser.parseArgs(args);
|
||||
isVerbose = res.getBoolean("verbose");
|
||||
isIncremental = res.getBoolean("incremental");
|
||||
KeeCrack keeCrack = KeeCrack.getInstance();
|
||||
keeCrack.setCrackingView(new CLICrackingView());
|
||||
|
||||
|
@ -57,25 +84,38 @@ public class Main {
|
|||
keeCrack.setKeyFile(new File(keyfilePath));
|
||||
}
|
||||
|
||||
String wordlistPath = res.getString("wordlist");
|
||||
if (wordlistPath != null) ;
|
||||
keeCrack.setWordlistFile(new File(wordlistPath));
|
||||
|
||||
if (res.getBoolean("gui")) {
|
||||
com.wbrawner.keecrack.gui.Main.main(new String[]{});
|
||||
} else {
|
||||
keeCrack.attack();
|
||||
String wordlist = res.getString("wordlist");
|
||||
if (wordlist != null) {
|
||||
if (isIncremental) {
|
||||
keeCrack.setWordListPattern(wordlist);
|
||||
} else {
|
||||
keeCrack.setWordListFile(new File(wordlist));
|
||||
}
|
||||
}
|
||||
|
||||
keeCrack.attack();
|
||||
} catch (ArgumentParserException e) {
|
||||
parser.handleError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void print(String message) {
|
||||
System.out.print(dateFormat.format(new Date()));
|
||||
System.out.print(LOG_SEPARATOR);
|
||||
System.out.println(message);
|
||||
}
|
||||
|
||||
private static void error(String message) {
|
||||
System.err.print(dateFormat.format(new Date()));
|
||||
System.err.print(LOG_SEPARATOR);
|
||||
System.err.println(message);
|
||||
}
|
||||
|
||||
static class CLICrackingView implements CrackingView {
|
||||
@Override
|
||||
public void onPasswordGuess(String password) {
|
||||
if (isVerbose)
|
||||
System.out.println("Guessing password: " + password);
|
||||
print("Guessing password: " + password);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,17 +135,17 @@ public class Main {
|
|||
timeElapsed.toString().toLowerCase().substring(2),
|
||||
password
|
||||
);
|
||||
System.out.println(message);
|
||||
print(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Code code) {
|
||||
String message = "";
|
||||
switch (code) {
|
||||
case ERROR_MISSING_DATABASE_FILE:
|
||||
case ERROR_INVALID_DATABASE_FILE:
|
||||
message = "Please specify a database file that you have read access to";
|
||||
break;
|
||||
case ERROR_MISSING_WORD_LIST_FILE:
|
||||
case ERROR_INVALID_WORD_LIST:
|
||||
message = "Please specify a word list file that you have read access to";
|
||||
break;
|
||||
case ERROR_CRACKING_INTERRUPTED:
|
||||
|
@ -115,7 +155,7 @@ public class Main {
|
|||
message = "An error occurred while trying to read one of the files";
|
||||
break;
|
||||
}
|
||||
System.err.println(message);
|
||||
error(message);
|
||||
System.exit(code.ordinal());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
dependencies {
|
||||
compile project(':keecrack-lib')
|
||||
}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.gui;
|
||||
|
||||
import com.wbrawner.keecrack.lib.Code;
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.gui;
|
||||
|
||||
import javafx.application.Application;
|
||||
|
@ -10,15 +25,15 @@ import java.io.IOException;
|
|||
|
||||
public class Main extends Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start(Stage primaryStage) throws IOException {
|
||||
Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml"));
|
||||
primaryStage.setTitle("Keecrack");
|
||||
primaryStage.setScene(new Scene(root, 400, 400));
|
||||
primaryStage.setScene(new Scene(root, 400, 300));
|
||||
primaryStage.show();
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
launch(args);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.gui;
|
||||
|
||||
import com.wbrawner.keecrack.lib.Code;
|
||||
|
@ -6,10 +21,13 @@ import com.wbrawner.keecrack.lib.view.FormView;
|
|||
import javafx.fxml.FXML;
|
||||
import javafx.fxml.FXMLLoader;
|
||||
import javafx.fxml.Initializable;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.Scene;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.RadioButton;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.ToggleGroup;
|
||||
import javafx.stage.FileChooser;
|
||||
import javafx.stage.Modality;
|
||||
import javafx.stage.Stage;
|
||||
|
@ -21,14 +39,25 @@ import java.util.ResourceBundle;
|
|||
|
||||
public class MainController implements Initializable, FormView {
|
||||
|
||||
@FXML private TextField database;
|
||||
@FXML private TextField key;
|
||||
@FXML private TextField wordlist;
|
||||
@FXML private Button crackButton;
|
||||
private static KeeCrack keeCrack;
|
||||
@FXML
|
||||
private TextField database;
|
||||
@FXML
|
||||
private TextField key;
|
||||
@FXML
|
||||
private TextField wordlist;
|
||||
@FXML
|
||||
private Button crackButton;
|
||||
@FXML
|
||||
private ToggleGroup wordlistType;
|
||||
@FXML
|
||||
private RadioButton wordlistFile;
|
||||
@FXML
|
||||
private RadioButton wordlistPattern;
|
||||
|
||||
@Override
|
||||
public void initialize(URL location, ResourceBundle resources) {
|
||||
KeeCrack keeCrack = KeeCrack.getInstance();
|
||||
keeCrack = KeeCrack.getInstance();
|
||||
keeCrack.setFormView(this);
|
||||
database.setOnMouseClicked(event -> {
|
||||
if (KeeCrack.getInstance().isCracking()) {
|
||||
|
@ -48,14 +77,7 @@ public class MainController implements Initializable, FormView {
|
|||
keeCrack.setKeyFile(keyFile);
|
||||
});
|
||||
|
||||
wordlist.setOnMouseClicked(event -> {
|
||||
if (KeeCrack.getInstance().isCracking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File wordlistFile = getFile("Text Files", "txt");
|
||||
keeCrack.setWordlistFile(wordlistFile);
|
||||
});
|
||||
updateWordListHandler();
|
||||
|
||||
crackButton.setOnMouseClicked(event -> {
|
||||
try {
|
||||
|
@ -79,19 +101,21 @@ public class MainController implements Initializable, FormView {
|
|||
return null;
|
||||
});
|
||||
Parent root = loader.load();
|
||||
stage.setScene(new Scene(root, 200, 200));
|
||||
stage.setScene(new Scene(root, 400, 350));
|
||||
stage.showAndWait();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
wordlistType.selectedToggleProperty().addListener((observableValue, oldToggle, newToggle) ->
|
||||
updateWordListHandler());
|
||||
|
||||
if (keeCrack.getDatabaseFile() != null)
|
||||
onDatabaseFileSet(keeCrack.getDatabaseFile().getName());
|
||||
if (keeCrack.getKeyFile() != null)
|
||||
onKeyFileSet(keeCrack.getKeyFile().getName());
|
||||
if (keeCrack.getWordlistFile() != null)
|
||||
onWordListFileSet(keeCrack.getWordlistFile().getName());
|
||||
if (keeCrack.getWordListName() != null)
|
||||
onWordListSet(keeCrack.getWordListName());
|
||||
}
|
||||
|
||||
|
||||
|
@ -107,6 +131,26 @@ public class MainController implements Initializable, FormView {
|
|||
return fileChooser.showOpenDialog(stage);
|
||||
}
|
||||
|
||||
private void updateWordListHandler() {
|
||||
wordlist.clear();
|
||||
if (wordlistFile.isSelected()) {
|
||||
wordlist.setEditable(false);
|
||||
wordlist.setCursor(Cursor.HAND);
|
||||
wordlist.setOnMouseClicked(event -> {
|
||||
if (KeeCrack.getInstance().isCracking()) {
|
||||
return;
|
||||
}
|
||||
File wordlistFile = getFile("Text Files", "txt");
|
||||
keeCrack.setWordListFile(wordlistFile);
|
||||
});
|
||||
} else if (wordlistPattern.isSelected()) {
|
||||
wordlist.setEditable(true);
|
||||
wordlist.setCursor(Cursor.TEXT);
|
||||
wordlist.setOnMouseExited(mouseEvent -> keeCrack.setWordListPattern(wordlist.getText()));
|
||||
wordlist.setOnMouseClicked(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDatabaseFileSet(String name) {
|
||||
database.setText(name);
|
||||
|
@ -118,7 +162,7 @@ public class MainController implements Initializable, FormView {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onWordListFileSet(String name) {
|
||||
public void onWordListSet(String name) {
|
||||
wordlist.setText(name);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,32 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressIndicator?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8"
|
||||
fx:controller="com.wbrawner.keecrack.gui.CrackingController">
|
||||
<children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
<ProgressIndicator fx:id="progress" />
|
||||
<Label fx:id="timeElapsed" />
|
||||
<Label fx:id="passwordLabel" text="Trying password: " />
|
||||
<Label fx:id="password" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0"
|
||||
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<ProgressIndicator fx:id="progress"/>
|
||||
<Label fx:id="timeElapsed"/>
|
||||
<Label fx:id="passwordLabel" text="Trying password: "/>
|
||||
<Label fx:id="password"/>
|
||||
</VBox>
|
||||
</AnchorPane>
|
||||
|
|
|
@ -1,61 +1,96 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
-->
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.geometry.*?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.Cursor?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8"
|
||||
fx:controller="com.wbrawner.keecrack.gui.MainController">
|
||||
<children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<children>
|
||||
<GridPane alignment="CENTER" prefHeight="90.0" prefWidth="539.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" minWidth="100.0" prefWidth="20.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label alignment="CENTER_RIGHT" text="Database" />
|
||||
<TextField fx:id="database" editable="false" promptText="Select" GridPane.columnIndex="1">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label text="Key File" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="key" editable="false" promptText="Select (Optional)" GridPane.columnIndex="1"
|
||||
GridPane.rowIndex="1">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<TextField fx:id="wordlist" editable="false" promptText="Select" GridPane.columnIndex="1"
|
||||
GridPane.rowIndex="2">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND" />
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0" />
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label text="Word List" GridPane.rowIndex="2" />
|
||||
</children>
|
||||
<padding>
|
||||
<Insets left="20.0" right="20.0" />
|
||||
</padding>
|
||||
</GridPane>
|
||||
<Button fx:id="crackButton" mnemonicParsing="false" prefHeight="25.0" prefWidth="111.0" text="Crack" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0"
|
||||
AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
|
||||
<GridPane alignment="CENTER">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" minWidth="100.0" prefWidth="20.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
|
||||
vgrow="ALWAYS"/>
|
||||
<RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
|
||||
vgrow="ALWAYS"/>
|
||||
<RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
|
||||
vgrow="ALWAYS"/>
|
||||
<RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
|
||||
vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
<padding>
|
||||
<Insets left="20.0" right="20.0"/>
|
||||
</padding>
|
||||
<Label alignment="CENTER_RIGHT" text="Database"/>
|
||||
<TextField fx:id="database" editable="false" promptText="Select" GridPane.columnIndex="1">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND"/>
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0"/>
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label text="Key File" GridPane.rowIndex="1"/>
|
||||
<TextField fx:id="key" editable="false" promptText="Select (Optional)" GridPane.columnIndex="1"
|
||||
GridPane.rowIndex="1">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND"/>
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0"/>
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label alignment="CENTER_RIGHT" text="Word List Type" GridPane.rowIndex="2"/>
|
||||
<HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" GridPane.columnIndex="1"
|
||||
GridPane.rowIndex="2">
|
||||
<RadioButton fx:id="wordlistPattern" text="From Pattern">
|
||||
<HBox.margin>
|
||||
<Insets left="20.0"/>
|
||||
</HBox.margin>
|
||||
<toggleGroup>
|
||||
<ToggleGroup fx:id="wordlistType"/>
|
||||
</toggleGroup>
|
||||
</RadioButton>
|
||||
<RadioButton fx:id="wordlistFile" selected="true" text="From File"
|
||||
toggleGroup="wordlistType">
|
||||
<HBox.margin>
|
||||
<Insets left="20.0"/>
|
||||
</HBox.margin>
|
||||
</RadioButton>
|
||||
</HBox>
|
||||
<TextField fx:id="wordlist" editable="false" promptText="Select" GridPane.columnIndex="1"
|
||||
GridPane.rowIndex="3">
|
||||
<cursor>
|
||||
<Cursor fx:constant="HAND"/>
|
||||
</cursor>
|
||||
<GridPane.margin>
|
||||
<Insets left="20.0"/>
|
||||
</GridPane.margin>
|
||||
</TextField>
|
||||
<Label text="Word List" GridPane.rowIndex="3"/>
|
||||
</GridPane>
|
||||
<Button fx:id="crackButton" mnemonicParsing="false" prefHeight="25.0" prefWidth="111.0" text="Crack"/>
|
||||
</VBox>
|
||||
</AnchorPane>
|
||||
|
|
|
@ -1,13 +1,30 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
apply plugin: 'jacoco'
|
||||
|
||||
dependencies {
|
||||
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
|
||||
implementation group: 'com.github.mifmif', name: 'generex', version: '1.0.2'
|
||||
implementation group: 'org.linguafranca.pwdb', name: 'KeePassJava2-kdbx', version: '2.1.4'
|
||||
}
|
||||
|
||||
jacocoTestReport {
|
||||
reports {
|
||||
xml.enabled true
|
||||
html.enabled true
|
||||
xml.enabled = true
|
||||
html.enabled = true
|
||||
html.destination file("${buildDir}/jacoco")
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +35,4 @@ jar {
|
|||
'Main-Class': 'com.wbrawner.keecrack.lib.KeeCrack'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
|
@ -7,6 +22,7 @@ public enum Code {
|
|||
ERROR_FILE_READ,
|
||||
ERROR_CRACKING_INTERRUPTED,
|
||||
ERROR_CRACKING_IN_PROGRESS,
|
||||
ERROR_MISSING_DATABASE_FILE,
|
||||
ERROR_MISSING_WORD_LIST_FILE
|
||||
ERROR_INVALID_DATABASE_FILE,
|
||||
ERROR_INVALID_WORD_LIST
|
||||
}
|
||||
|
||||
|
|
|
@ -1,34 +1,70 @@
|
|||
/*
|
||||
* 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 com.wbrawner.keecrack.lib.view.CrackingView;
|
||||
import com.wbrawner.keecrack.lib.view.FormView;
|
||||
import com.wbrawner.keecrack.lib.wordlist.WordList;
|
||||
import org.linguafranca.pwdb.kdbx.KdbxCreds;
|
||||
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxHeader;
|
||||
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxSerializer;
|
||||
|
||||
import java.io.*;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)} respectively. These
|
||||
* views will be responsible for displaying information like error messages and status updates. The cracking will
|
||||
* work without these, though it's highly recommended to set them prior to beginning. The database 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 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
|
||||
* to guess. Use {@link #setWordListPattern(String)} or {@link #setWordListFile(File)} respectively to achieve the
|
||||
* desired guess strategy. If you need to interrupt the attack for any reason, you can call {@link #abort} and the
|
||||
* cracking will stop on the next guess, sending {@link Code#ERROR_CRACKING_INTERRUPTED} to the views, provided they
|
||||
* are set. For each guess, {@link CrackingView#onPasswordGuess(String)} is called, in case you'd like to do
|
||||
* something with the passwords that have already been attempted. Upon a successful password guess, the
|
||||
* {@link CrackingView#onResult(String, int, Duration)} method will be called with the first parameter as the correct
|
||||
* password. If the password cannot be guessed with the words provided, then the same method will be called but the
|
||||
* first parameter will be null.
|
||||
*/
|
||||
public class KeeCrack {
|
||||
private static final AtomicReference<KeeCrack> singleton = new AtomicReference<>(null);
|
||||
private final Object keyFileLock = new Object();
|
||||
private WeakReference<FormView> formView = new WeakReference<>(null);
|
||||
private WeakReference<CrackingView> crackingView = new WeakReference<>(null);
|
||||
private final AtomicBoolean isCracking = new AtomicBoolean(false);
|
||||
private File databaseFile;
|
||||
private File keyFile;
|
||||
private File wordlistFile;
|
||||
|
||||
/**
|
||||
* This is used to abort cracking
|
||||
*/
|
||||
private final AtomicBoolean abort = new AtomicBoolean(false);
|
||||
|
||||
private final AtomicReference<FormView> formView = new AtomicReference<>(null);
|
||||
private final AtomicReference<CrackingView> crackingView = new AtomicReference<>(null);
|
||||
private File databaseFile;
|
||||
private File keyFile;
|
||||
private byte[] databaseBytes;
|
||||
private byte[] keyBytes;
|
||||
private WordList wordList;
|
||||
private int guessCount = 0;
|
||||
|
||||
private KeeCrack() {
|
||||
|
@ -49,7 +85,7 @@ public class KeeCrack {
|
|||
public void reset() {
|
||||
setDatabaseFile(null);
|
||||
setKeyFile(null);
|
||||
setWordlistFile(null);
|
||||
setWordListFile(null);
|
||||
setCrackingView(null);
|
||||
setFormView(null);
|
||||
}
|
||||
|
@ -63,12 +99,12 @@ public class KeeCrack {
|
|||
*/
|
||||
public void attack() {
|
||||
if (databaseFile == null || !databaseFile.exists() || !databaseFile.canRead()) {
|
||||
sendErrorCode(Code.ERROR_MISSING_DATABASE_FILE);
|
||||
sendErrorCode(Code.ERROR_INVALID_DATABASE_FILE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (wordlistFile == null || !wordlistFile.exists() || !wordlistFile.canRead()) {
|
||||
sendErrorCode(Code.ERROR_MISSING_WORD_LIST_FILE);
|
||||
if (wordList == null) {
|
||||
sendErrorCode(Code.ERROR_INVALID_WORD_LIST);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -81,56 +117,73 @@ public class KeeCrack {
|
|||
guessCount = 0;
|
||||
Instant startTime = Instant.now();
|
||||
|
||||
try (BufferedReader wordReader = new BufferedReader(new FileReader(wordlistFile))) {
|
||||
String line = null;
|
||||
boolean haveCorrectPassword = false;
|
||||
while (!haveCorrectPassword && (line = wordReader.readLine()) != null) {
|
||||
if (abort.get()) {
|
||||
sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED);
|
||||
abort.set(false);
|
||||
return;
|
||||
}
|
||||
CrackingView view = crackingView.get();
|
||||
if (view != null)
|
||||
view.onPasswordGuess(line);
|
||||
haveCorrectPassword = guessPassword(line);
|
||||
String line = null;
|
||||
boolean haveCorrectPassword = false;
|
||||
prepareByteArrays();
|
||||
while (!haveCorrectPassword && wordList.hasNext()) {
|
||||
if (abort.get()) {
|
||||
sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED);
|
||||
isCracking.set(false);
|
||||
abort.set(false);
|
||||
return;
|
||||
}
|
||||
line = wordList.nextWord();
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
crackingView.get().onPasswordGuess(line);
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
haveCorrectPassword = guessPassword(line);
|
||||
}
|
||||
|
||||
CrackingView view = crackingView.get();
|
||||
if (view != null) {
|
||||
Duration duration = Duration.between(startTime, Instant.now());
|
||||
String password = null;
|
||||
if (haveCorrectPassword) {
|
||||
password = line;
|
||||
}
|
||||
view.onResult(password, guessCount, duration);
|
||||
Duration duration = Duration.between(startTime, Instant.now());
|
||||
String password = null;
|
||||
if (haveCorrectPassword) {
|
||||
password = line;
|
||||
}
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
crackingView.get().onResult(password, guessCount, duration);
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
|
||||
isCracking.set(false);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||
private void prepareByteArrays() {
|
||||
databaseBytes = Utils.getFileBytes(databaseFile);
|
||||
synchronized (keyFileLock) {
|
||||
if (keyFile == null) {
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
sendErrorCode(Code.ERROR_FILE_READ);
|
||||
} finally {
|
||||
isCracking.set(false);
|
||||
keyBytes = Utils.getFileBytes(keyFile);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean guessPassword(String password) {
|
||||
guessCount++;
|
||||
try (InputStream inputStream = new FileInputStream(databaseFile)) {
|
||||
InputStream databaseInput = null;
|
||||
InputStream keyFileInput = null;
|
||||
try {
|
||||
databaseInput = new ByteArrayInputStream(databaseBytes);
|
||||
KdbxHeader databaseHeader = new KdbxHeader();
|
||||
KdbxCreds credentials;
|
||||
synchronized (keyFileLock) {
|
||||
if (keyFile == null) {
|
||||
credentials = new KdbxCreds(password.getBytes());
|
||||
} else {
|
||||
credentials = new KdbxCreds(password.getBytes(), new FileInputStream(keyFile));
|
||||
}
|
||||
if (keyBytes == null || keyBytes.length == 0) {
|
||||
credentials = new KdbxCreds(password.getBytes());
|
||||
} else {
|
||||
keyFileInput = new ByteArrayInputStream(keyBytes);
|
||||
credentials = new KdbxCreds(password.getBytes(), keyFileInput);
|
||||
}
|
||||
KdbxSerializer.createUnencryptedInputStream(credentials, databaseHeader, inputStream);
|
||||
KdbxSerializer.createUnencryptedInputStream(credentials, databaseHeader, databaseInput);
|
||||
return true;
|
||||
} catch (IllegalStateException ignored) {
|
||||
// This happens when an incorrect guess occurs. Expected behavior, so we ignore it
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
Utils.closeQuietly(databaseInput);
|
||||
Utils.closeQuietly(keyFileInput);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -178,26 +231,63 @@ public class KeeCrack {
|
|||
}
|
||||
}
|
||||
|
||||
public File getWordlistFile() {
|
||||
return wordlistFile;
|
||||
}
|
||||
|
||||
public void setWordlistFile(File wordlistFile) {
|
||||
this.wordlistFile = wordlistFile;
|
||||
public void setWordListFile(File wordlistFile) {
|
||||
String response = null;
|
||||
if (wordlistFile == null) {
|
||||
this.wordList = null;
|
||||
} else {
|
||||
if (!wordlistFile.exists() || !wordlistFile.canRead()) {
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
crackingView.get().onError(Code.ERROR_INVALID_WORD_LIST);
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
this.wordList = null;
|
||||
return;
|
||||
}
|
||||
this.wordList = new WordList(wordlistFile);
|
||||
response = wordlistFile.getName();
|
||||
}
|
||||
try {
|
||||
String response = (wordlistFile == null) ? null : wordlistFile.getName();
|
||||
//noinspection ConstantConditions
|
||||
formView.get().onWordListFileSet(response);
|
||||
formView.get().onWordListSet(response);
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
public void setWordListPattern(String pattern) {
|
||||
if (pattern == null) {
|
||||
this.wordList = null;
|
||||
} else {
|
||||
try {
|
||||
this.wordList = new WordList(pattern);
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
// This can be thrown if the user has entered an invalid regular expression
|
||||
}
|
||||
}
|
||||
try {
|
||||
//noinspection ConstantConditions
|
||||
formView.get().onWordListSet(pattern);
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
}
|
||||
|
||||
WordList getWordList() {
|
||||
return this.wordList;
|
||||
}
|
||||
|
||||
public String getWordListName() {
|
||||
if (this.wordList == null)
|
||||
return null;
|
||||
return this.wordList.getName();
|
||||
}
|
||||
|
||||
public void setFormView(FormView formView) {
|
||||
this.formView = new WeakReference<>(formView);
|
||||
this.formView.set(formView);
|
||||
}
|
||||
|
||||
public void setCrackingView(CrackingView crackingView) {
|
||||
this.crackingView = new WeakReference<>(crackingView);
|
||||
this.crackingView.set(crackingView);
|
||||
}
|
||||
|
||||
public boolean isCracking() {
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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[]{};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.view;
|
||||
|
||||
import com.wbrawner.keecrack.lib.Code;
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.view;
|
||||
|
||||
import java.time.Duration;
|
||||
|
@ -5,14 +20,16 @@ import java.time.Duration;
|
|||
public interface CrackingView extends BaseView {
|
||||
/**
|
||||
* Called prior to each guess
|
||||
*
|
||||
* @param password The password that will be guessed
|
||||
*/
|
||||
void onPasswordGuess(final String password);
|
||||
|
||||
/**
|
||||
* Called when the password has been successfully guessed, or there are no more passwords to guess
|
||||
* @param password The password, if successfully guessed, or null if no passwords were successful
|
||||
* @param guessCount The number of passwords guessed
|
||||
*
|
||||
* @param password The password, if successfully guessed, or null if no passwords were successful
|
||||
* @param guessCount The number of passwords guessed
|
||||
* @param timeElapsed The time taken to guess the password
|
||||
*/
|
||||
void onResult(final String password, final int guessCount, final Duration timeElapsed);
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
/*
|
||||
* 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.view;
|
||||
|
||||
public interface FormView extends BaseView {
|
||||
void onDatabaseFileSet(String name);
|
||||
|
||||
void onKeyFileSet(String name);
|
||||
void onWordListFileSet(String name);
|
||||
|
||||
void onWordListSet(String name);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* 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.wordlist;
|
||||
|
||||
import com.mifmif.common.regex.Generex;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WordList {
|
||||
private WordListIterator iterator;
|
||||
private String name;
|
||||
|
||||
public WordList(File wordFile) {
|
||||
java.util.Iterator<String> wordListIterator = null;
|
||||
this.name = wordFile.getName();
|
||||
try {
|
||||
wordListIterator = FileUtils.lineIterator(wordFile);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
iterator = new WordListIterator(wordListIterator);
|
||||
}
|
||||
|
||||
public WordList(String pattern) {
|
||||
Generex generex = new Generex(pattern);
|
||||
this.name = pattern;
|
||||
iterator = new WordListIterator(generex.iterator());
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return iterator.hasNext();
|
||||
}
|
||||
|
||||
public String nextWord() {
|
||||
return iterator.next();
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.wordlist;
|
||||
|
||||
/**
|
||||
* Since Generex uses their own Iterator interface, that complicates things a bit for us, so this class serves as
|
||||
* nothing more than a wrapper around both interfaces to prevent complication in other areas of the code;
|
||||
*/
|
||||
public class WordListIterator {
|
||||
private com.mifmif.common.regex.util.Iterator generexIterator;
|
||||
private java.util.Iterator<String> standardIterator;
|
||||
|
||||
WordListIterator(com.mifmif.common.regex.util.Iterator generexIterator) {
|
||||
this.generexIterator = generexIterator;
|
||||
}
|
||||
|
||||
WordListIterator(java.util.Iterator<String> standardIterator) {
|
||||
this.standardIterator = standardIterator;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
if (generexIterator != null) {
|
||||
return generexIterator.hasNext();
|
||||
}
|
||||
|
||||
return standardIterator != null && standardIterator.hasNext();
|
||||
|
||||
}
|
||||
|
||||
public String next() {
|
||||
if (generexIterator != null) {
|
||||
return generexIterator.next();
|
||||
}
|
||||
|
||||
if (standardIterator != null) {
|
||||
return standardIterator.next();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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 com.wbrawner.keecrack.lib.view.CrackingView;
|
||||
|
@ -10,6 +25,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
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;
|
||||
|
@ -38,26 +54,30 @@ public class KeeCrackTest {
|
|||
public void resetTest() {
|
||||
keeCrack.setDatabaseFile(new File("Database"));
|
||||
keeCrack.setKeyFile(new File("Keyfile"));
|
||||
keeCrack.setWordlistFile(new File("WordList"));
|
||||
keeCrack.setWordListPattern("Some pattern");
|
||||
assertNotNull(keeCrack.getDatabaseFile());
|
||||
assertNotNull(keeCrack.getKeyFile());
|
||||
assertNotNull(keeCrack.getWordlistFile());
|
||||
assertNotNull(keeCrack.getWordList());
|
||||
keeCrack.reset();
|
||||
assertNull(keeCrack.getDatabaseFile());
|
||||
assertNull(keeCrack.getKeyFile());
|
||||
assertNull(keeCrack.getWordlistFile());
|
||||
assertNull(keeCrack.getWordList());
|
||||
assertFalse(keeCrack.isCracking());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void abortTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.abort();
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onError(Code.ERROR_CRACKING_INTERRUPTED);
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that both views receive any errors sent
|
||||
*/
|
||||
@Test
|
||||
public void sendErrorTest() {
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
|
@ -67,6 +87,9 @@ public class KeeCrackTest {
|
|||
verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that the form view still receives the error if the cracking view is null
|
||||
*/
|
||||
@Test
|
||||
public void sendErrorWithoutCrackingViewTest() {
|
||||
keeCrack.setFormView(mockFormView);
|
||||
|
@ -74,6 +97,9 @@ public class KeeCrackTest {
|
|||
verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS);
|
||||
}
|
||||
|
||||
/**
|
||||
* This ensures that the cracking view still receives the error if the form view is null
|
||||
*/
|
||||
@Test
|
||||
public void sendErrorWithoutFormViewTest() {
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
|
@ -83,10 +109,10 @@ public class KeeCrackTest {
|
|||
|
||||
@Test
|
||||
public void errorWithoutDatabaseFileTest() throws IOException {
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onError(Code.ERROR_MISSING_DATABASE_FILE);
|
||||
verify(mockCrackingView, times(1)).onError(Code.ERROR_INVALID_DATABASE_FILE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -94,14 +120,14 @@ public class KeeCrackTest {
|
|||
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onError(Code.ERROR_MISSING_WORD_LIST_FILE);
|
||||
verify(mockCrackingView, times(1)).onError(Code.ERROR_INVALID_WORD_LIST);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void guessCorrectPasswordTest() throws IOException {
|
||||
public void guessCorrectPasswordFromFileTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onPasswordGuess("123456");
|
||||
|
@ -109,20 +135,42 @@ public class KeeCrackTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void guessCorrectPasswordAndKeyTest() throws IOException {
|
||||
public void guessCorrectPasswordFromFileAndKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
|
||||
keeCrack.setKeyFile(Utils.getKeyFile());
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onPasswordGuess("123456");
|
||||
verify(mockCrackingView, times(1)).onResult(eq("123456"), eq(1), any(Duration.class));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void keepGuessingUntilCorrectPasswordTest() throws IOException {
|
||||
public void guessCorrectPasswordFromPatternTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("0000.kdbx"));
|
||||
keeCrack.setWordListPattern("[0-9]{4}");
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onPasswordGuess(anyString());
|
||||
verify(mockCrackingView, times(1)).onResult(eq("0000"), eq(1), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void guessCorrectPasswordFromPatternAndKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("0000-key.kdbx"));
|
||||
keeCrack.setWordListPattern("[0-9]{4}");
|
||||
keeCrack.setKeyFile(Utils.getKeyFile());
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(1)).onPasswordGuess(anyString());
|
||||
verify(mockCrackingView, times(1)).onResult(eq("0000"), eq(1), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepGuessingUntilCorrectPasswordFromFileTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("redwings.kdbx"));
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
|
||||
|
@ -130,20 +178,41 @@ public class KeeCrackTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void keepGuessingUntilCorrectPasswordAndKeyTest() throws IOException {
|
||||
public void keepGuessingUntilCorrectPasswordFromFileAndKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("redwings-key.kdbx"));
|
||||
keeCrack.setKeyFile(Utils.getKeyFile());
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
|
||||
verify(mockCrackingView, times(1)).onResult(eq("redwings"), eq(2), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepGuessingUntilCorrectPasswordFromPatternTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("ab.kdbx"));
|
||||
keeCrack.setWordListPattern("[a-z]{2}");
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
|
||||
verify(mockCrackingView, times(1)).onResult(eq("ab"), eq(2), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void keepGuessingUntilCorrectPasswordFromPatternAndKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("ab-key.kdbx"));
|
||||
keeCrack.setWordListPattern("[a-z]{2}");
|
||||
keeCrack.setKeyFile(Utils.getKeyFile());
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
|
||||
verify(mockCrackingView, times(1)).onResult(eq("ab"), eq(2), any(Duration.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failToCrackPasswordTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
|
||||
keeCrack.setWordlistFile(Utils.getWordList("invalid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("invalid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(3)).onPasswordGuess(anyString());
|
||||
|
@ -154,7 +223,7 @@ public class KeeCrackTest {
|
|||
public void failToCrackInvalidPasswordAndValidKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
|
||||
keeCrack.setKeyFile(Utils.getKeyFile());
|
||||
keeCrack.setWordlistFile(Utils.getWordList("invalid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("invalid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(3)).onPasswordGuess(anyString());
|
||||
|
@ -165,7 +234,7 @@ public class KeeCrackTest {
|
|||
public void failToCrackValidPasswordAndInvalidKeyTest() throws IOException {
|
||||
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
|
||||
keeCrack.setKeyFile(Utils.getInvalidKeyFile());
|
||||
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
|
||||
keeCrack.setCrackingView(mockCrackingView);
|
||||
keeCrack.attack();
|
||||
verify(mockCrackingView, times(3)).onPasswordGuess(anyString());
|
||||
|
|
|
@ -1,3 +1,18 @@
|
|||
/*
|
||||
* 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.*;
|
||||
|
|
BIN
keecrack-lib/src/test/resources/databases/0000-key.kdbx
Normal file
BIN
keecrack-lib/src/test/resources/databases/0000-key.kdbx
Normal file
Binary file not shown.
BIN
keecrack-lib/src/test/resources/databases/0000.kdbx
Normal file
BIN
keecrack-lib/src/test/resources/databases/0000.kdbx
Normal file
Binary file not shown.
BIN
keecrack-lib/src/test/resources/databases/ab-key.kdbx
Normal file
BIN
keecrack-lib/src/test/resources/databases/ab-key.kdbx
Normal file
Binary file not shown.
BIN
keecrack-lib/src/test/resources/databases/ab.kdbx
Normal file
BIN
keecrack-lib/src/test/resources/databases/ab.kdbx
Normal file
Binary file not shown.
|
@ -1 +1,16 @@
|
|||
include 'keecrack-lib', 'keecrack-gui', 'keecrack-cli'
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
include 'keecrack-lib', 'keecrack-gui', 'keecrack-cli'
|
||||
|
|
Loading…
Reference in a new issue