Keyloggers and Virtual Keyboeard
Keyloggers have the ability to record every keystroke. They can register
your passwords and they can make even strong encryption useless.
Virtual keyboard are intended to defend keylogging. They can easily defend hardware
keyloggers, but securely defending software keyloggers is almost impossible.
See: Article about keyloggers for more informations.
Here is some Java source code for an uncomfortable but easy to read virtual keyboard and the buttons:
package cologne.eck.peafactory.peas.gui;
/*
* Peafactory - Production of Password Encryption Archives
* Copyright (C) 2015 Axel von dem Bruch
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* See: http://www.gnu.org/licenses/gpl-2.0.html
* You should have received a copy of the GNU General Public License
* along with this library.
*/
/**
* A simple keyboard (US layout) to hamper key logging.
*/
import java.awt.Color;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.SwingConstants;
import cologne.eck.peafactory.tools.Zeroizer;
@SuppressWarnings("serial")
public class Keyboard extends JDialog implements ActionListener {
private char[][] enTable = {
{ '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+' },// backspace <-
{'\'', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=' },
{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', '\u007B' , '\u007D' , '|'},
{'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']', '\\'},
{' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ':'},
{'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';'},
{' ', ' ', ' ', ' ', ' ', ' ', ' ', '<', '>', '?'},
{'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/'},//shift left, '<', shift right
// extra table for < and > if images fail
{'\u1438', '\u1433'},// U+1433, U+1438 CANADIAN SYLLABICS PA and PO to avoid html conflicts
};
private boolean shiftEnabled = false;
private JPasswordField pswField = null;
private static JPanel topPanel = null;
private static Color bgColor = new Color(234, 234, 234);
private static Color shiftColor = new Color(200,200,200);// show shifting
public Keyboard(Window owner, JPasswordField _pswField) {
super(owner);// sets the image icon of owner
this.setTitle( "Keyboard" );
// this is maybe used together with char table
this.setModalityType(Dialog.ModalityType.MODELESS);
this.pswField = _pswField;
this.setAlwaysOnTop(true);
topPanel = (JPanel) this.getContentPane();
topPanel.setBackground(bgColor);
topPanel.setLayout(new BoxLayout(topPanel, BoxLayout.Y_AXIS));
JPanel firstPanel = new JPanel();
firstPanel.setBackground(bgColor);
firstPanel.setLayout(new BoxLayout(firstPanel, BoxLayout.X_AXIS));
topPanel.add(firstPanel);
for (int i = 0; i < enTable[0].length; i++) {
KeyButton firstLineButton = new KeyButton();
firstLineButton.setText("<html>" + enTable[0][i] + "<br/>" + enTable[1][i] + "<html>");
firstLineButton.addActionListener(this);
firstLineButton.setActionCommand("" + enTable[1][i] + i + 1);
if (i == 0 || (i > 10)){
firstLineButton.setPreferredSize(new Dimension(30, 40));
firstLineButton.setMinimumSize(new Dimension(30, 40));
firstLineButton.setMaximumSize(new Dimension(30, 40));
} else {
firstLineButton.setPreferredSize(new Dimension(40, 40));
firstLineButton.setMinimumSize(new Dimension(40, 40));
firstLineButton.setMaximumSize(new Dimension(40, 40));
}
firstPanel.add(firstLineButton);
}
firstPanel.add(Box.createHorizontalStrut(5));
// use icon for button:
JButton backspace = new KeyButton();
Image image = null;
URL url = this.getClass().getResource("resources/back24.png");
if (url == null) {
try{
image = new ImageIcon(getClass().getClassLoader().getResource("resources/back24.png")).getImage();
} catch (Exception e) {
//System.out.println("image not found");
}
} else {
image = new ImageIcon(url).getImage();
}
if (image != null) {
backspace.setIcon(new ImageIcon(image));
} else { // set characters
backspace.setText(" <-");
backspace.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 15));
}
backspace.addActionListener(this);
backspace.setActionCommand("backspace");
backspace.setPreferredSize(new Dimension(45, 40));
backspace.setMaximumSize(new Dimension(45, 40));
backspace.setMaximumSize(new Dimension(45, 40));
firstPanel.add(backspace);
firstPanel.add(Box.createHorizontalGlue());
JPanel secondPanel = new JPanel();
secondPanel.setBackground(bgColor);
secondPanel.setLayout(new BoxLayout(secondPanel, BoxLayout.X_AXIS));
topPanel.add(secondPanel);
secondPanel.add(Box.createHorizontalStrut(40));
for (int i = 0; i < enTable[2].length; i++) {
JButton secondLineButton = new KeyButton();
secondLineButton.setText("<html>" + enTable[2][i] + "<br/>" + enTable[3][i] + "<html>");
secondLineButton.addActionListener(this);
secondLineButton.setActionCommand("" + enTable[3][i] + i + 3);
if (i > 9){
secondLineButton.setPreferredSize(new Dimension(30, 40));
secondLineButton.setMinimumSize(new Dimension(30, 40));
secondLineButton.setMaximumSize(new Dimension(30, 40));
} else {
secondLineButton.setPreferredSize(new Dimension(40, 40));
secondLineButton.setMinimumSize(new Dimension(40, 40));
secondLineButton.setMaximumSize(new Dimension(40, 40));
}
secondPanel.add(secondLineButton);
}
secondPanel.add(Box.createHorizontalGlue() );
JPanel thirdPanel = new JPanel();
thirdPanel.setBackground(bgColor);
thirdPanel.setLayout(new BoxLayout(thirdPanel, BoxLayout.X_AXIS));
topPanel.add(thirdPanel);
thirdPanel.add(Box.createHorizontalStrut(50));
for (int i = 0; i < enTable[4].length; i++) {
JButton thirdLineButton = new KeyButton();
thirdLineButton.setText("<html>" + enTable[4][i] + "<br/>" + enTable[5][i] + "<html>");
thirdLineButton.addActionListener(this);
thirdLineButton.setActionCommand("" + enTable[5][i] + i + 5);
if (i > 8) {
thirdLineButton.setPreferredSize(new Dimension(30, 40));
thirdLineButton.setMinimumSize(new Dimension(30, 40));
thirdLineButton.setMaximumSize(new Dimension(30, 40));
} else {
thirdLineButton.setPreferredSize(new Dimension(40, 40));
thirdLineButton.setMinimumSize(new Dimension(40, 40));
thirdLineButton.setMaximumSize(new Dimension(40, 40));
}
thirdPanel.add(thirdLineButton);
}
thirdPanel.add(Box.createHorizontalStrut(5));
thirdPanel.add(Box.createHorizontalGlue() );
JPanel fourthPanel = new JPanel();
fourthPanel.setBackground(bgColor);
fourthPanel.setLayout(new BoxLayout(fourthPanel, BoxLayout.X_AXIS));
topPanel.add(fourthPanel);
// less than sign and greater than sign result in conflicts with
// html tags and the similar looking
// CANADIAN SYLLABICS PA and PO are not always displayed (Windows 7)
// => icon are used
Image lessThan = null;// <
URL urlSmaller = this.getClass().getResource("resources/kleiner.png");
if (urlSmaller == null) {
try{
lessThan = new ImageIcon(getClass().getClassLoader().getResource("resources/kleiner.png")).getImage();
} catch (Exception e) {
//System.out.println("image not found");
}
} else {
lessThan = new ImageIcon(urlSmaller).getImage();
}
Image greaterThan = null; // >
URL urlLarger = this.getClass().getResource("resources/groesser.png");
if (urlLarger == null) {
try{
greaterThan = new ImageIcon(getClass().getClassLoader().getResource("resources/groesser.png")).getImage();
} catch (Exception e) {
//System.out.println("image not found");
}
} else {
greaterThan = new ImageIcon(urlLarger).getImage();
}
JButton shift = new KeyButton("shift");
shift.addActionListener(this);
shift.setActionCommand("shift");
shift.setMargin(new Insets( 10, 1, 5, 1));
shift.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 15));
shift.setPreferredSize(new Dimension(60, 40));
shift.setMinimumSize(new Dimension(60, 40));
shift.setMaximumSize(new Dimension(60, 40));
fourthPanel.add(shift);
fourthPanel.add(Box.createHorizontalStrut(5));
for (int i = 0; i < enTable[6].length; i++) {
JButton fourthLineButton = new KeyButton();
if (i == 7) {// the button < - this can not be used inside html
// try to use the icon, because Windows does not display the character
if (lessThan != null) {
fourthLineButton.setIcon(new ImageIcon(lessThan));
fourthLineButton.setVerticalTextPosition(SwingConstants.BOTTOM);
fourthLineButton.setHorizontalTextPosition(SwingConstants.CENTER);
fourthLineButton.setText("" + enTable[7][i]);
} else { // use the characters CANADIAN SYLLABICS PA and PO
fourthLineButton.setText("<html>" + enTable[8][0] + "<br/>" + enTable[7][i] + "<html>");
}
} else if (i == 8) {// the button > - this can not be used inside html
// try to use the icon, because Windows does not display the character
if (greaterThan != null) {
fourthLineButton.setIcon(new ImageIcon(greaterThan));
fourthLineButton.setVerticalTextPosition(SwingConstants.BOTTOM);
fourthLineButton.setHorizontalTextPosition(SwingConstants.CENTER);
fourthLineButton.setText("" + enTable[7][i]);
} else { // use the characters CANADIAN SYLLABICS PA and PO
fourthLineButton.setText("<html>" + enTable[8][1] + "<br/>" + enTable[7][i] + "<html>");
}
// all other characters: use table
} else {
fourthLineButton.setText("<html>" + enTable[6][i] + "<br/>" + enTable[7][i] + "<html>");
}
fourthLineButton.addActionListener(this);
fourthLineButton.setActionCommand("" + enTable[7][i] + i + 7);
if (i > 6) { // less space for the characters ,./
fourthLineButton.setPreferredSize(new Dimension(30, 40));
fourthLineButton.setMinimumSize(new Dimension(30, 40));
fourthLineButton.setMaximumSize(new Dimension(30, 40));
} else { // more space for letters
fourthLineButton.setPreferredSize(new Dimension(40, 40));
fourthLineButton.setMinimumSize(new Dimension(40, 40));
fourthLineButton.setMaximumSize(new Dimension(40, 40));
}
fourthPanel.add(fourthLineButton);
}
fourthPanel.add(Box.createHorizontalStrut(5));
JButton shift2 = new KeyButton("shift");
shift2.addActionListener(this);
shift2.setActionCommand("shift");
shift2.setMargin(new Insets( 10, 1, 5, 1));
shift2.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 15));
shift2.setPreferredSize(new Dimension(100, 40));
shift2.setMinimumSize(new Dimension(100, 40));
shift2.setMaximumSize(new Dimension(100, 40));
fourthPanel.add(shift2);
fourthPanel.add(Box.createHorizontalGlue() );
JPanel fifthPanel = new JPanel();
fifthPanel.setBackground(bgColor);
fifthPanel.setLayout(new BoxLayout(fifthPanel, BoxLayout.X_AXIS));
topPanel.add(fifthPanel);
JButton space = new KeyButton("space");
space.addActionListener(this);
space.setActionCommand("space");
space.setPreferredSize(new Dimension (390, 30));
space.setMinimumSize(new Dimension (390, 30));
space.setMaximumSize(new Dimension (390, 30));
space.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 16));
fifthPanel.add(Box.createHorizontalStrut(55));
fifthPanel.add(space);
fifthPanel.add(Box.createHorizontalGlue());
if (pswField != null) {
this.setLocation((int) pswField.getLocation().getX() + 100,
(int) pswField.getLocation().getY() + 100);
}
this.pack();
}
private void setChar(char c) {
if (pswField != null) {
char[] oldPsw = pswField.getPassword();
char[] tmp = new char[oldPsw.length + 1];
System.arraycopy(oldPsw, 0, tmp, 0, oldPsw.length);
tmp[oldPsw.length] = c;
Zeroizer.zero(oldPsw);
//System.out.println("+ char: " + new String(tmp));
pswField.setText(new String(tmp));
Zeroizer.zero(tmp);
} else {
System.err.println("Password field for table was closed.");
}
}
private void deleteLastChar() {
if (pswField != null) {
if (pswField.getPassword().length == 0) {
return;
}
char[] oldPsw = pswField.getPassword();
char[] tmp = new char[oldPsw.length - 1];
System.arraycopy(oldPsw, 0, tmp, 0, tmp.length);
Zeroizer.zero(oldPsw);
//System.out.println("deleted char: " + new String(tmp));
pswField.setText(new String(tmp));
Zeroizer.zero(tmp);
} else {
System.err.println("Password field for table was closed.");
}
}
@Override
public void actionPerformed(ActionEvent ape) {
String command = ape.getActionCommand();
//System.out.println("command: " + command);
if (command.equals("shift")){
if (shiftEnabled == false) {
// change background color to indicate shifting
shiftEnabled = true;
topPanel.getComponent(3).setBackground(shiftColor);
topPanel.getComponent(4).setBackground(shiftColor);
} else {
shiftEnabled = false;
topPanel.getComponent(3).setBackground(bgColor);
topPanel.getComponent(4).setBackground(bgColor);
}
} else if (command.equals("space")){
setChar(' ');
} else if (command.equals("backspace")) {
deleteLastChar();
/* } else if (command.startsWith("" + '\u1438') ) {// '\u1438' U+1438 CANADIAN SYLLABICS PA to avoid html conflicts
setChar('<');
} else if (command.startsWith("" + '\u1433') ){ // '\u1433' U+1433 CANADIAN SYLLABICS PO
setChar('>');
*/
} else { // single char
//=========== TEST ==================
//System.out.println( command.charAt(0));
if (shiftEnabled == false) {
setChar(command.charAt(0));
} else { // command = char + index + array
int arrayIndex = Integer.parseInt("" + command.charAt(command.length() - 1));
int charIndex = Integer.parseInt(command.substring(1, command.length() - 1));
if (enTable[arrayIndex - 1][charIndex] == ' ') {
setChar( Character.toUpperCase(command.charAt(0)) );
} else {
setChar(enTable[arrayIndex - 1][charIndex]);
}
}
}
}
//=========== TEST ==================
public static void main(String[] args) {
JDialog dialog = new JDialog();
JPasswordField psw = new JPasswordField();
psw.setPreferredSize(new Dimension (300,30));
dialog.add(psw);
//dialog.setPreferredSize(new Dimension (300,30));
dialog.setLocation(300,300);
dialog.pack();
dialog.setVisible(true);
Keyboard kb = new Keyboard(dialog, psw);
kb.setVisible(true);
}
}
The Keyboard class calls the class KeyButton:
package cologne.eck.peafactory.peas.gui;
/*
* Peafactory - Production of Password Encryption Archives
* Copyright (C) 2015 Axel von dem Bruch
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published
* by the Free Software Foundation; either version 2 of the License,
* or (at your option) any later version.
* This library is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
* See: http://www.gnu.org/licenses/gpl-2.0.html
* You should have received a copy of the GNU General Public License
* along with this library.
*/
/**
* Button to prevent hardware key logging and to hamper software key logging
*/
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.BorderFactory;
import javax.swing.Icon;
import javax.swing.JButton;
@SuppressWarnings("serial")
class KeyButton extends JButton implements MouseListener {
/* // this is for the strategy "hiding mouse cursor"
private static final BufferedImage img = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_ARGB);
private static final Cursor zeroCursor = Toolkit.getDefaultToolkit().createCustomCursor(
img , new Point(0,0), "zeroCursor");
*/
// the current default cursor
private static final Cursor defaultCursor = new Cursor(Cursor.DEFAULT_CURSOR);
public KeyButton() { // avoid any displaying of events
super.setContentAreaFilled(false);
this.setRolloverEnabled(false);
this.setFocusPainted(false);
this.setBorder(BorderFactory.createBevelBorder(1, Color.gray, Color.LIGHT_GRAY)); // Two Colors Inner Bevel
this.addMouseListener(this);
}
public KeyButton(String text) {
super(text);
super.setContentAreaFilled(false);
this.setRolloverEnabled(false);
this.setFocusPainted(false);
this.setBorder(BorderFactory.createBevelBorder(1, Color.gray, Color.LIGHT_GRAY)); // Two Colors Inner Bevel
this.addMouseListener(this);
}
public KeyButton(Icon icon) {
super(icon);
}
@Override
protected void paintComponent(Graphics g) {
// the same background for all actions
// avoid vulnerabilities to register keystroke
if (getModel().isPressed()) {
g.setColor(PswDialogView.getPeaColor());//Color.WHITE);
} else if (getModel().isRollover()) {
g.setColor(PswDialogView.getPeaColor());//Color.WHITE);
} else {
g.setColor(PswDialogView.getPeaColor());//Color.WHITE);
}
g.fillRect(0, 0, getWidth(), getHeight());
super.paintComponent(g);
}
public void setNewBackground(Color color) {
this.setBackground(color);
}
@Override
public void mouseClicked(MouseEvent mc) {}
@Override
public void mouseEntered(MouseEvent e) {
// Blinking with swingTimer?
}
@Override
public void mouseExited(MouseEvent e) {
this.setCursor(defaultCursor);
}
@Override
public void mousePressed(MouseEvent e) {
/*
// Variant 1: hide cursor - against screenshots of whole screen
this.setCursor(zeroCursor);
try {
Thread.sleep(200);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.setCursor(defaultCursor);
*/
// Variant 2: cover the area of the button - against 50*50 screenshots of buttons
int size = 64;
BufferedImage img = new BufferedImage(size, size,// win max: 32 * 32...
BufferedImage.TYPE_BYTE_INDEXED);
Graphics2D graph = img.createGraphics();
// graph.setColor(PswDialogView.getPeaColor());//Color.WHITE);//LIGHT_GRAY);
graph.fill(new Rectangle(0, 0, size, size));
graph.dispose();
Toolkit toolkit = Toolkit.getDefaultToolkit();
Cursor c = null;
try {
c = toolkit.createCustomCursor(
img , new Point(size / 2, size / 2), "img");
} catch (IndexOutOfBoundsException ioobe){ // Windows: max 32 * 32
c = toolkit.createCustomCursor(
img , new Point(size / 4, size / 4), "img");
}
this.setCursor((c));
try { // cover only 0.1 second
Thread.sleep(100);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
this.setCursor(defaultCursor);
}
@Override
public void mouseReleased(MouseEvent e) {}
}