Bem, após um período de inatividade no blog (o meu caro colaborado está demorando pra postar a parte 4 do tutorial java para web =P), venho trazendo um tutorial para a implementação de teclados virtuais em java.

O teclado virtual representa uma forma de inserir a senha para uma autenticação de usuário sem correr o risco de ter a senha capturada pelos comuns keyloggers. Entretanto, realmente existem complicações na utilização de um teclado virtual e tem gente que realmente abomina esse tipo de recurso. Mas como todo bom programador, muitas vezes a decisão de usar ou não um teclado virtual não é sua.

Então vamos ao que interessa! Implementaremos uma applet de um teclado virtual para ser adicionada em um HTML qualquer com um campo para senha. Ao final do post, existem os links para download do projeto e para visualização online do teclado.

Vou utilizar o NetBeans 6.5 e JDK6 apenas. Neste caso, o cliente que deverá visualizar o teclado deverá ter o JRE6 instalado, já que a applet é client side. Mas o JDK5 também pode ser usado. Sobre o 1.4 eu não tenho muita certeza. =P

Crie um novo projeto java (no Netbeans é Arquivo -> Novo Projeto -> Java -> Aplicativo Java). Nomeie como preferir (no meu caso ficou “teclado” mesmo) e crie uma nova classe java na pasta de códigos-fontes para o teclado virtual (a minha classe ficou br.com.teclado.virtual.TecladoVirtual).

Visualizando o Teclado Virtual

Acredito que o primeiro passo seja a construção visual do teclado. E para isto deverá ser criada a imagem base do dele. Então abra o seu PainBrush e mãos à obra! ^^

Ah. Tenha em mente que a disposição e dimensão das teclas é muito importante, pois você deverá ser capaz de dizer qual a tecla pressionada através das coordenadas de um clique. O ideal é fazer um teclado com as teclas de mesmo tamanho e dispostas em um grid, onde uma tecla está sempre exatamente alinhada verticalmente e horizontalmente com outras. Mas isso fica a cargo de cada um. Vejam o teclado que fiz para este tutorial:

teclado1.jpg

Cada tecla possui tamanho 30×30 pixels, exceto o backspace e o enter que possuem 30×60. Assim, considerando o canto superior esquerdo a origem do sistema de coordenadas (0,0), podemos obter a tecla pressionada dividindo por 30 o valor da posição x y. Se y < 1 a tecla pressionada é backspace. Se y > 5, é enter. Caso contrário, faça x+(y*5)-1 para descobrir qual tecla foi pressionada. (Se quiser, pode conferir as contas… =P)

Depois adicione esta imagem em seu projeto. Eu criei uma pasta “resources” no pacote de códigos fontes mesmo.

Bem, chega de brincar de designer e voltemos ao java! Vou listar as operações que devem ser feitas e depois mostrar o código resultante:

  1. Na nossa classe do teclado virtual, vamos extender a classe Applet (java.applet.Applet) e implementar a interface MouseListener (java.awt.event.MouseListener). Inclua a implementação dos métodos da interface MouseListener mas sem conteúdo algum, por enquanto (se quiser, altere o cursor do mouse, como fiz a seguir);
  2. Vamos criar dois atributos privados na classe do teclado virtual; um vetor de caracteres (char[]) que possuirá os valores das teclas numéricas, e uma imagem (java.awt.Image) que carregará a imagem que fizemos anteriormente;
  3. Vamos sobre-escrever o método init() da classe Applet, e nele vamos inicializar o vetor com os valores das teclas, setar o tamanho da applet, inicializar a imagem do teclado com o arquivo de imagem que incluimos no projeto, e adicionar um MouseListener à classe;
  4. Por fim, sobre-escreva o método paint(Graphics g) fazendo com que a imagem do teclado seja pintada na tela.

A classe fica mais ou menos assim:

public class teste extends Applet implements MouseListener {
 
    /**
     * Vetor com os valores das teclas
     */
    private char[] teclas;
 
    /**
     * Imagem do teclado
     */
    private Image teclado;
 
    @Override
    public void init() {
        iniciaTeclado();
        setLocation(0, 0);
        setSize(211, 61);
        teclado = getImage(this.getClass().getResource("/resources/teclado1.JPG"));
        addMouseListener(this);
        setLayout(null);
    }
 
    /**
     * Inicializa os atributos do teclado
     */
    private void iniciaTeclado() {
        teclas = new char[10];
        int i = 0;
        teclas[i++] = '0';
        teclas[i++] = '1';
        teclas[i++] = '2';
        teclas[i++] = '3';
        teclas[i++] = '4';
        teclas[i++] = '5';
        teclas[i++] = '6';
        teclas[i++] = '7';
        teclas[i++] = '8';
        teclas[i++] = '9';
    }
 
    @Override
    public void paint(Graphics g) {
        g.drawImage(teclado, 0, 0, this);
    }
 
    public void mouseClicked(MouseEvent e) {
    }
 
    public void mousePressed(MouseEvent e) {
    }
 
    public void mouseReleased(MouseEvent e) {
    }
 
    public void mouseEntered(MouseEvent e) {
        this.setCursor(new Cursor(Cursor.HAND_CURSOR));
    }
 
    public void mouseExited(MouseEvent e) {
        this.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
    }
 
}

Lembrando que o tamanho do applet deverá ser o mesmo da imagem adicionada.

Feito isto, tente rodar o arquivo. Deverá aparecer uma pequena tela com o teclado que desenhamos.

Capturando a Senha

Com o teclado aparecendo, podemos implementar a construção da senha a partir da detecção do clique nas teclas.

Basicamente, no método MousePressed(), faremos a verificação das coordenadas do clique assim como foi mostrado anteriormente. Por agora, cada tecla obtida afetará uma String “senha” e será impressa no console do java.

    @Override
    public void init() {
        iniciaTeclado();
        setLocation(0, 0);
        setSize(211, 61);
        teclado = getImage(this.getClass().getResource("/resources/teclado1.JPG"));
        addMouseListener(this);
        setLayout(null);
        senha = "";
    }
 
    public void mousePressed(MouseEvent e) {
        int x = e.getX() / 30;
        int y = e.getY() / 30;
        if(x < 1) {
            System.out.println("BACKSPACE");
            if(!senha.isEmpty()) {
                senha = senha.substring(0, senha.length()-1);
            }
        } else if(x > 5) {
            System.out.println("ENTER");
        } else {
            System.out.println(teclas[x+(y*5)-1]);
            senha += String.valueOf(teclas[x+(y*5)-1]);
        }
    }

Bem simples… Agora execute o arquivo e verifique se para cada tecla clicada o valor impresso está correto.

Comunicação com o HTML

Agora vem a parte mais chatinha. Não é por ser complicada, mas se há de ocorrer algum problema no teclado virtual, provavelmente será a seguir.

Para que o teclado virtual  seja realmente funcional, este deve, de alguma forma, se comunicar com o HTML no qual o applet está incluso. A maneira mais direta e sem muita complicação, é realizar a comunicação através de javascript. E para isso, utilizaremos uma biblioteca que você deverá incluir em seu projeto. É a netscape.javascript que pode ser encontrada no plugin.jar dentro da pasta do JDK que você está usando (%JAVA_HOME%/jre/lib/plugin.jar). Basta clicar com o direito sobre a pasta “bibliotecas” de seu projeto e então escolher “Adicionar JAR/Pasta”.

Tendo adicionado esta biblioteca, é hora de criarmos um HTML para os testes. No pacote de testes mesmo eu criei um “testeTecladoVistual.html”.

Basicamente, a comunicação entre a applet e o HTML será feito por chamadas javascript. Dessa forma, se quisermos passar a senha para um campo do HTML, usaremos uma chamada em javascript para isso. Para facilitar, criarei um método para essa operação, de modo que basta a applet chamar esse método para passar a senha.

<html>
<head>
<script type="text/javascript">
var clazz = "br.com.teclado.virtual.TecladoVirtual.class";
var jar =  "../dist/teclado.jar";
var width = "282";
var height = "97";
function escreveTecladoGeral() {
    var applet = "";
    applet += "<object classid=\"clsid:8AD9C840-044E-11D1-B3E9-00805F499D93\" width=\"" + width + "\" height=\"" + height + "\">";
    applet += "<param name=\"archive\" value=\"" + jar + "\">";
    applet += "<param name=\"codebase\" value=\".\">";
    applet += "<param name=\"code\" value=\"" + clazz + "\">";
    applet += "<param name=\"cache_option\" value=\"no\">";
    applet += "<!--[if gte IE 7]> <!-->";
    applet += "<object type=\"application/x-java-applet\" classid=\"java:" + clazz + "\"";
    applet += "archive=\"" + jar + "\" codebase=\".\" width=\"" + width + "\" height=\"" + height + "\">";
    applet += "<!-- Konqueror browser needs the following param -->";
    applet += "<param name=\"archive\" value=\"" + jar + "\">";
    applet += "<param name=\"code\" value=\"" + clazz + "\">";
    applet += "<!-- Safari browser needs the following param -->";
    applet += "<param name=\"JAVA_CODEBASE\" value=\".\">";
    applet += "<param name=\"cache_option\" value=\"no\">";
    applet += "Você necessita de um navegador que suporte Java<br/>Baixe o plugin java <a href='http://www.java.com/pt_BR/download/' target='_blank'>aqui</a>.";
    applet += "</object>";
    applet += "<!--<![endif]-->";
    applet += "<!--[if lt IE 7]>";
    applet += "Você necessita de um navegador que suporte Java<br/>Baixe o plugin java <a href='http://www.java.com/pt_BR/download/' target='_blank'>aqui</a>.";
    applet += "<![endif]-->";
    applet += "</object>";
    return applet;
}
function escreveApplet() {
	if(navigator.appName.indexOf('Internet Explorer') > 0) {
		document.write(escreveTecladoGeral());
	} else {
		document.write(escreveTecladoGeral());
	}
}
function recebeSenha(senha,tamanho) {
    document.getElementById('pass').value = senha;
    var str = '';
    for(i=0;i<tamanho;i++){str += '*';}
    document.getElementById('fakePass').value = str;
}
</script>
</head>
<body>
<script type="text/javascript">escreveApplet();</script>
<form>
    Login: <input type="text" name="login"><br/>
    Senha: <input type="text" readonly id="fakePass"><br/>
    <input name="pass" type="hidden" id="pass">
</form>
</body>
</html>

Primeiramente observem a escrita do applet no HTML através de javascript. Realizei muitos testes em diversos navegadores e cheguei no método acima (escreveTecladoGeral()) que, até onde testei, funciona perfeitamente. Existem alternativas utilizando as tags applet ou embed, caso tenha interesse em utilizá-las.

E temos o método recebeSenha(senha, tamanho) que será utilizado pela applet. Esta função javascript simplesmente repassa a senha para um campo oculto (hidden) e preenche o campo senha a mostra com ‘*‘ conforme a quantidade repassada. Não utilizei a quantidade de caracteres da senha para o preenchimento dos ‘*’ porque o correto seria que a senha seja encriptada.

Agora voltemos ao java e façamos a comunicação efetiva com o HTML.

Temos que criar um novo atributo da classe TecladoVirtual; um netscape.javascript.JSObject que servirá para executar as chamadas em javascript a partir do applet. Fazendo uso da função em javascript recebeSenha, os métodos init() e mousePressed(MouseEvent e) deverão ficar assim:

    @Override
    public void init() {
        iniciaTeclado();
        setLocation(0, 0);
        setSize(211, 61);
        teclado = getImage(this.getClass().getResource("/resources/teclado1.JPG"));
        addMouseListener(this);
        setLayout(null);
        try {
            win = JSObject.getWindow(this);
        } catch(JSException jse) {
            win = null;
            jse.printStackTrace();
        }
        senha = "";
    }
 
    public void mousePressed(MouseEvent e) {
        int x = e.getX() / 30;
        int y = e.getY() / 30;
        if(x < 1) {
            System.out.println("BACKSPACE");
            if(!senha.isEmpty()) {
                senha = senha.substring(0, senha.length()-1);
            }
            if(win != null) {
                win.eval("recebeSenha('" + senha + "'," + senha.length() + ")");
            }
        } else if(x > 5) {
            System.out.println("ENTER");
            if(win != null) {
                win.eval("document.forms[0].submit()");
            }
        } else {
            System.out.println(teclas[x+(y*5)-1]);
            senha += String.valueOf(teclas[x+(y*5)-1]);
            if(win != null) {
                win.eval("recebeSenha('" + senha + "'," + senha.length() + ")");
            }
        }
    }

Observe que no caso da tecla enter, o applet repassa ao navegador uma instrução para submeter o fomulário do HTML que fizemos. Como no form não está definido nenhum action ou method, o formulário submeterá para si próprio por GET e poderemos ver os dados (login e senha) como parâmetros na barra de endereço do navegador.

Se tudo der certo, podemos testar. Como o JSObject opera sobre o navegador, não adianta mais tentarmos executar o arquivo do teclado virtual. Até por isso incluí os testes if(win != null) ao fazer as chamadas javascript. Então temos que testar rodando o HTML que fizemos.

Mande compilar (build) o projeto para criar o JAR na pasta “dist” ou “target”. Feito isso, abra o testeTecladoVirtual.html para avaliar o funcionamento do applet. Caso não tenha aparecido, primeiramente verifique se o caminho para o jar e a classe do teclado (ambos no javascript do HTML) estão corretos. Se mesmo assim não funcionar, verifique o console java (qualquer exceção aparecerá lá, e não na IDE de desenvolvimento).

O java console deverá ser acessível através do ícone que aparecerá na barra de sistema do Windows (próximo ao relógio). Se não aparecer, talvez seja necessário habilitá-lo (Painel de Controle -> Java -> Advanced -> Java Console -> Show Console).

Considerações Finais

Apesar de evitar a captura da senha por um keylogger, o teclado virtual não garante 100% da segurança. Por exemplo, ainda é possível obter a senha através de uma variação de keylogger que, ao invés de capturar o valor das teclas pressionadas, fotografa cada clique do mouse. Ou então ainda é possível “escutar” as requisições de modo que o re-envio de uma requisição de login pode conceder uma autorização indevida ao sistema.

Por isso, o teclado virtual, num caso ideal, não deve ser o único meio de segurança. A utilização de uma autenticação SSL ou de um teclado virtual com um sistema de alternância das teclas (vide bb.com.br, necessário ter conta (O Banco do Brasil retirou o teclado virtual da tela de login)) é altamente recomendado.

Na verdade, acredito que um teclado virtual sirva mais para passar a sensação de segurança do que para realmente assegurar.

Bem, este tutorial abrangeu uma abordagem à implementação de um teclado virtual. Mas existem outras alternativas (inclusive em outras linguagens). É possível, ainda utilizando java, incluir no próprio applet os campos de login e senha e enviá-los como parâmetros de um request à página de login.

    AppletContext ac = this.getAppletContext();
    try {
        ac.showDocument(new URL("http://localhost:8080/login.jsp?user=" + user + "&pass=" + pass), "_self");
    } catch (MalformedURLException ex) {
        Logger.getLogger(TecladoVirtual.class.getName()).log(Level.SEVERE, null, ex);
    }

Realizando a autenticação desse modo, requer que o processo de login altere o response para que a requisição não retorne para o cliente e os parâmetros fiquem visíveis (o Spring Security é um exemplo). Não é um bom jeito de fazer as coisas, mas é uma alternativa.

E caso o teclado não seja visível em algum navegador em alguma máquina de alguma pessoa (não todos), antes de pensar em mexer no teclado, verifique as versões do JRE e do navegador no cliente. Atualizar o JRE consertou as coisas algumas vezes para mim.

Estão é isso! ^^

No mais, nada mais.

Projeto Teclado-Virtual compactado e configurado para NetBeans: teclado-virtual compactado

Exemplo online do resultado final do tutorial: teclado-virtual online

Share