View Javadoc
1   package edu.jiangxin.apktoolbox.file.password.recovery;
2   
3   import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryFactory;
4   import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryType;
5   import edu.jiangxin.apktoolbox.file.password.recovery.category.ICategory;
6   import edu.jiangxin.apktoolbox.file.password.recovery.checker.*;
7   import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdParty7ZipChecker;
8   import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyRarChecker;
9   import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyWinRarChecker;
10  import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
11  import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel;
12  import edu.jiangxin.apktoolbox.utils.Constants;
13  import org.apache.commons.collections4.CollectionUtils;
14  import org.apache.commons.io.FilenameUtils;
15  import org.apache.commons.lang3.ArrayUtils;
16  import org.apache.commons.lang3.StringUtils;
17  
18  import javax.swing.*;
19  import java.awt.*;
20  import java.awt.datatransfer.Clipboard;
21  import java.awt.datatransfer.StringSelection;
22  import java.io.File;
23  import java.io.Serial;
24  import java.text.NumberFormat;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.Set;
30  import java.util.concurrent.ExecutorService;
31  import java.util.concurrent.Executors;
32  
33  public final class RecoveryPanel extends EasyPanel {
34      @Serial
35      private static final long serialVersionUID = 1L;
36  
37      private JPanel optionPanel;
38  
39      private FilePanel recoveryFilePanel;
40  
41      private JTabbedPane categoryTabbedPane;
42  
43      private JPanel bruteForceCategoryPanel;
44  
45      private JPanel dictionaryCategoryPanel;
46  
47      private FilePanel dictionaryFilePanel;
48  
49      private CategoryType currentCategoryType = CategoryType.UNKNOWN;
50  
51      private JPanel operationPanel;
52  
53      private JCheckBox numberCheckBox;
54      private JCheckBox lowercaseLetterCheckBox;
55      private JCheckBox uppercaseLetterCheckBox;
56  
57      private JCheckBox userIncludedCheckBox;
58  
59      private JTextField userIncludedTextField;
60  
61      private JCheckBox userExcludedCheckBox;
62  
63      private JTextField userExcludedTextField;
64  
65      private JSpinner minSpinner;
66      private JSpinner maxSpinner;
67  
68      private JCheckBox isUseMultiThreadCheckBox;
69  
70      private JProgressBar progressBar;
71  
72      private JComboBox<FileChecker> checkerTypeComboBox;
73  
74      private transient FileChecker currentFileChecker;
75  
76      private JButton startButton;
77      private JButton stopButton;
78  
79      private JLabel currentStateLabel;
80  
81      private JLabel currentPasswordLabel;
82  
83      private JLabel currentSpeedLabel;
84  
85      private int passwordTryCount = 0;
86  
87      private NumberFormat numberFormat;
88  
89      private State currentState = State.IDLE;
90  
91      private Timer timer;
92  
93      private final transient ExecutorService startExecutorService = Executors.newFixedThreadPool(1);
94  
95      private final transient ExecutorService stopExecutorService = Executors.newFixedThreadPool(1);
96  
97      public RecoveryPanel() {
98          super();
99          initBase();
100     }
101 
102     private void initBase() {
103         numberFormat = NumberFormat.getPercentInstance();
104         numberFormat.setMinimumFractionDigits(3);
105     }
106 
107     @Override
108     public void initUI() {
109         BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
110         setLayout(boxLayout);
111 
112         createOptionPanel();
113         add(optionPanel);
114         add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
115 
116         createOperationPanel();
117         add(operationPanel);
118 
119         timer = new Timer(1000, e -> {
120             if (currentState == State.WORKING) {
121                 int currentValue = progressBar.getValue();
122                 int speed = currentValue - passwordTryCount;
123                 passwordTryCount = currentValue;
124                 currentSpeedLabel.setText("Speed: " + speed + " passwords/s");
125             }
126         });
127     }
128 
129     private void createOptionPanel() {
130         optionPanel = new JPanel();
131         optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.Y_AXIS));
132 
133         checkerTypeComboBox = new JComboBox<>();
134         checkerTypeComboBox.addItem(new ThirdParty7ZipChecker());
135         checkerTypeComboBox.addItem(new ThirdPartyWinRarChecker());
136         checkerTypeComboBox.addItem(new ThirdPartyRarChecker());
137         checkerTypeComboBox.addItem(new ZipChecker());
138         checkerTypeComboBox.addItem(new RarChecker());
139         checkerTypeComboBox.addItem(new SevenZipChecker());
140         checkerTypeComboBox.addItem(new PdfChecker());
141         checkerTypeComboBox.addItem(new XmlBasedOfficeChecker());
142         checkerTypeComboBox.addItem(new BinaryOfficeChecker());
143         checkerTypeComboBox.setSelectedIndex(0);
144         checkerTypeComboBox.addItemListener(e -> {
145             FileChecker fileChecker = (FileChecker) e.getItem();
146             if (fileChecker == null) {
147                 logger.error("fileChecker is null");
148                 return;
149             }
150             recoveryFilePanel.setDescriptionAndFileExtensions(
151                     fileChecker.getFileDescription(), fileChecker.getFileExtensions());
152         });
153 
154         FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem();
155         if (fileChecker == null) {
156             logger.error("fileChecker is null");
157             return;
158         }
159 
160         recoveryFilePanel = new FilePanel("Choose Recovery File");
161         recoveryFilePanel.initialize();
162         recoveryFilePanel.setDescriptionAndFileExtensions(fileChecker.getFileDescription(), fileChecker.getFileExtensions());
163 
164         categoryTabbedPane = new JTabbedPane();
165 
166         createBruteForcePanel();
167         categoryTabbedPane.addTab("Brute Force", null, bruteForceCategoryPanel, "Brute Force");
168         categoryTabbedPane.setSelectedIndex(0);
169 
170         createDictionaryPanel();
171         categoryTabbedPane.addTab("Dictionary", null, dictionaryCategoryPanel, "Dictionary");
172 
173         progressBar = new JProgressBar();
174         progressBar.setStringPainted(true);
175         String text = numberFormat.format(0);
176         progressBar.setString(text);
177 
178         optionPanel.add(checkerTypeComboBox);
179         optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
180         optionPanel.add(recoveryFilePanel);
181         optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
182         optionPanel.add(categoryTabbedPane);
183         optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
184         optionPanel.add(progressBar);
185     }
186 
187     private void createBruteForcePanel() {
188         bruteForceCategoryPanel = new JPanel();
189 
190         JPanel topLevelPanel = new JPanel();
191         topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS));
192         bruteForceCategoryPanel.add(topLevelPanel);
193 
194         JPanel charsetPanel = new JPanel();
195         charsetPanel.setLayout(new BoxLayout(charsetPanel, BoxLayout.X_AXIS));
196 
197         JPanel passwordLengthPanel = new JPanel();
198         passwordLengthPanel.setLayout(new BoxLayout(passwordLengthPanel, BoxLayout.X_AXIS));
199 
200         topLevelPanel.add(charsetPanel);
201         topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
202         topLevelPanel.add(passwordLengthPanel);
203 
204         numberCheckBox = new JCheckBox("Number");
205         numberCheckBox.setSelected(true);
206         lowercaseLetterCheckBox = new JCheckBox("Lowercase Letter");
207         uppercaseLetterCheckBox = new JCheckBox("Uppercase Letter");
208         userIncludedCheckBox = new JCheckBox("User-defined");
209         userIncludedTextField = new JTextField(10);
210         userExcludedCheckBox = new JCheckBox("User-excluded");
211         userExcludedTextField = new JTextField(10);
212 
213 
214         charsetPanel.add(numberCheckBox);
215         charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
216         charsetPanel.add(lowercaseLetterCheckBox);
217         charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
218         charsetPanel.add(uppercaseLetterCheckBox);
219         charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
220         charsetPanel.add(userIncludedCheckBox);
221         charsetPanel.add(userIncludedTextField);
222         charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
223         charsetPanel.add(userExcludedCheckBox);
224         charsetPanel.add(userExcludedTextField);
225 
226         JLabel minLabel = new JLabel("Minimum Length: ");
227         JLabel maxLabel = new JLabel("Maximum Length: ");
228 
229         minSpinner = new JSpinner();
230         minSpinner.setModel(new SpinnerNumberModel(1, 1, 9, 1));
231         minSpinner.setToolTipText("Minimum Length");
232 
233         maxSpinner = new JSpinner();
234         maxSpinner.setModel(new SpinnerNumberModel(6, 1, 9, 1));
235         maxSpinner.setToolTipText("Maximum Length");
236 
237         passwordLengthPanel.add(minLabel);
238         passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
239         passwordLengthPanel.add(minSpinner);
240         passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
241         passwordLengthPanel.add(maxLabel);
242         passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
243         passwordLengthPanel.add(maxSpinner);
244     }
245 
246     private void createDictionaryPanel() {
247         dictionaryCategoryPanel = new JPanel();
248 
249         JPanel topLevelPanel = new JPanel();
250         topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS));
251         dictionaryCategoryPanel.add(topLevelPanel);
252 
253         dictionaryFilePanel = new FilePanel("Choose Dictionary File");
254         dictionaryFilePanel.initialize();
255         dictionaryFilePanel.setDescriptionAndFileExtensions("*.dic;*.txt", new String[]{"dic", "txt"});
256 
257         JPanel threadPanel = new JPanel();
258         threadPanel.setLayout(new BoxLayout(threadPanel, BoxLayout.X_AXIS));
259 
260         topLevelPanel.add(dictionaryFilePanel);
261         topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
262         topLevelPanel.add(threadPanel);
263 
264         isUseMultiThreadCheckBox = new JCheckBox("Use Multi-thread");
265         isUseMultiThreadCheckBox.setSelected(true);
266 
267         threadPanel.add(isUseMultiThreadCheckBox);
268     }
269 
270     private void createOperationPanel() {
271         operationPanel = new JPanel();
272         operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
273 
274         startButton = new JButton("Start");
275         stopButton = new JButton("Stop");
276         currentStateLabel = new JLabel("State: IDLE");
277         currentPasswordLabel = new JLabel("Trying: ");
278         currentSpeedLabel = new JLabel("Speed: 0 passwords/s");
279 
280         operationPanel.add(startButton);
281         operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
282         operationPanel.add(stopButton);
283         operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
284         operationPanel.add(currentStateLabel);
285         operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
286         operationPanel.add(currentPasswordLabel);
287         operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
288         operationPanel.add(currentSpeedLabel);
289         operationPanel.add(Box.createHorizontalGlue());
290 
291         startButton.addActionListener(e -> startExecutorService.submit(this::onStart));
292         stopButton.addActionListener(e -> stopExecutorService.submit(this::onStop));
293 
294         setCurrentState(State.IDLE);
295     }
296 
297     private void onStart() {
298         FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem();
299         if (fileChecker == null) {
300             JOptionPane.showMessageDialog(this, "fileChecker is null!");
301             return;
302         }
303         if (!fileChecker.prepareChecker()) {
304             JOptionPane.showMessageDialog(this, "onStart failed: Checker condition does not been prepared!");
305             return;
306         }
307 
308         File selectedFile = recoveryFilePanel.getFile();
309         if (!selectedFile.isFile()) {
310             JOptionPane.showMessageDialog(this, "file is not a file!");
311             return;
312         }
313 
314         String extension = FilenameUtils.getExtension(recoveryFilePanel.getFile().getName());
315         if (!ArrayUtils.contains(fileChecker.getFileExtensions(), StringUtils.toRootLowerCase(extension))) {
316             JOptionPane.showMessageDialog(this, "invalid file!");
317             return;
318         }
319 
320         fileChecker.attachFile(selectedFile);
321         currentFileChecker = fileChecker;
322 
323         Component selectedPanel = categoryTabbedPane.getSelectedComponent();
324         if (selectedPanel.equals(bruteForceCategoryPanel)) {
325             currentCategoryType = CategoryType.BRUTE_FORCE;
326         } else if (selectedPanel.equals(dictionaryCategoryPanel)) {
327             if (isUseMultiThreadCheckBox.isSelected()) {
328                 currentCategoryType = CategoryType.DICTIONARY_MULTI_THREAD;
329             } else {
330                 currentCategoryType = CategoryType.DICTIONARY_SINGLE_THREAD;
331             }
332         } else {
333             currentCategoryType = CategoryType.UNKNOWN;
334         }
335         logger.info("onStart: {}", currentCategoryType);
336         if (currentCategoryType == CategoryType.UNKNOWN) {
337             JOptionPane.showMessageDialog(this, "onStart failed: Invalid category!");
338             return;
339         }
340         setCurrentState(State.WORKING);
341         passwordTryCount = 0;
342         timer.start();
343         ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType);
344         category.start(this);
345         if (currentState == State.WORKING) {
346             onStop();
347         }
348     }
349 
350     private void onStop() {
351         logger.info("onStop: currentState: {}, currentCategoryType: {}", currentState, currentCategoryType);
352         if (currentState != State.WORKING) {
353             logger.error("onStop failed: Not in working state!");
354             return;
355         }
356         if (currentCategoryType == CategoryType.UNKNOWN) {
357             logger.error("onStop failed: Invalid category!");
358             return;
359         }
360         timer.stop();
361         setCurrentState(State.STOPPING);
362         ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType);
363         category.cancel();
364         setCurrentState(State.IDLE);
365         currentCategoryType = CategoryType.UNKNOWN;
366     }
367 
368     public void showResultWithDialog(String password) {
369         if (password == null) {
370             logger.error("Can not find password");
371             JOptionPane.showMessageDialog(this, "Can not find password");
372         } else {
373             logger.info("Find out the password: {}", password);
374             JPanel panel = createPasswordShowPanel(password);
375             JOptionPane.showMessageDialog(this, panel, "Find out the password", JOptionPane.INFORMATION_MESSAGE);
376         }
377     }
378 
379     private static JPanel createPasswordShowPanel(String password) {
380         JPanel panel = new JPanel();
381         panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
382         JTextField textField = new JTextField(password);
383         textField.setEditable(false);
384         textField.setColumns(20);
385         JButton button = new JButton("Copy");
386         button.addActionListener(e -> {
387             StringSelection stringSelection = new StringSelection(password);
388             Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
389             clipboard.setContents(stringSelection, null);
390         });
391         panel.add(textField);
392         panel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
393         panel.add(button);
394         return panel;
395     }
396 
397     public void setCurrentState(State currentState) {
398         SwingUtilities.invokeLater(() -> {
399             this.currentState = currentState;
400             if (currentStateLabel != null) {
401                 currentStateLabel.setText("State: " + currentState.toString());
402             }
403             if (currentState == State.WORKING) {
404                 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
405                 startButton.setEnabled(false);
406                 stopButton.setEnabled(true);
407                 updateUiComponent(false);
408             } else if (currentState == State.STOPPING) {
409                 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
410                 startButton.setEnabled(false);
411                 stopButton.setEnabled(false);
412                 updateUiComponent(false);
413             } else if (currentState == State.IDLE) {
414                 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
415                 startButton.setEnabled(true);
416                 stopButton.setEnabled(false);
417                 updateUiComponent(true);
418             }
419         });
420     }
421 
422     private void updateUiComponent(boolean enable) {
423         for (Component component : getComponents(optionPanel)) {
424             component.setEnabled(enable);
425         }
426     }
427 
428     private Component[] getComponents(Component container) {
429         List<Component> list;
430 
431         try {
432             list = new ArrayList<>(Arrays.asList(
433                     ((Container) container).getComponents()));
434             for (int index = 0; index < list.size(); index++) {
435                 list.addAll(Arrays.asList(getComponents(list.get(index))));
436             }
437         } catch (ClassCastException e) {
438             list = new ArrayList<>();
439         }
440 
441         return list.toArray(new Component[0]);
442     }
443 
444     public State getCurrentState() {
445         return currentState;
446     }
447 
448     public void resetProgressMaxValue(int maxValue) {
449         SwingUtilities.invokeLater(() -> {
450             progressBar.setMaximum(maxValue);
451             setProgressBarValue(0);
452         });
453     }
454 
455     public void increaseProgressBarValue() {
456         SwingUtilities.invokeLater(() -> setProgressBarValue(progressBar.getValue() + 1));
457     }
458 
459     private void setProgressBarValue(int value) {
460         int properValue = Math.min(value, progressBar.getMaximum());
461         progressBar.setValue(properValue);
462         String text = numberFormat.format(((double) properValue) / progressBar.getMaximum());
463         progressBar.setString(text);
464     }
465 
466     public void setCurrentPassword(String password) {
467         SwingUtilities.invokeLater(() -> currentPasswordLabel.setText("Trying: " + password));
468     }
469 
470     public String getCharset() {
471         Set<Character> charsetSet = new HashSet<>();
472         if (numberCheckBox.isSelected()) {
473             CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("0123456789".toCharArray()));
474         }
475         if (lowercaseLetterCheckBox.isSelected()) {
476             CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("abcdefghijklmnopqrstuvwxyz".toCharArray()));
477         }
478         if (uppercaseLetterCheckBox.isSelected()) {
479             CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()));
480         }
481         if (userIncludedCheckBox.isSelected()) {
482             CollectionUtils.addAll(charsetSet, ArrayUtils.toObject(userIncludedTextField.getText().toCharArray()));
483         }
484         if (userExcludedCheckBox.isSelected()) {
485             for (char ch : userExcludedTextField.getText().toCharArray()) {
486                 charsetSet.remove(ch);
487             }
488         }
489         return String.valueOf(ArrayUtils.toPrimitive(charsetSet.toArray(new Character[0])));
490     }
491 
492     public File getDictionaryFile() {
493         return dictionaryFilePanel.getFile();
494     }
495 
496     public int getMinLength() {
497         return (Integer) minSpinner.getValue();
498     }
499 
500     public int getMaxLength() {
501         return (Integer) maxSpinner.getValue();
502     }
503 
504     public FileChecker getCurrentFileChecker() {
505         return currentFileChecker;
506     }
507 }