Java: Onscreen Keyboard to defend Keylogging

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) {}
}