在网上关于JAVA下密码屏蔽输入的文章比较多了,但是都不是多全面和详细,今天在http://java.sun.com上看到了此文章,打算把它翻译出来。因为经验不够,可能翻译的还有出入。

   本篇文章(指原文)第一次发表于2002年9月。在JAVA下,利用AWT/SWING对用户输入密码进行屏蔽是很方便的,但是缺少I/O API,因此在命令行下来实现像UNIX的PASSWORD shadow就不容易了。本文就利用AWT/SWING/COMMAND LINE三种情况都一一作了较详细的介绍。

 

Password Masking in AWT
---------------------------------
如果提供图形界面登录对话框,可以利用AWT组件库中定义的TextField对象,该对象的定义继承结构如下:

java.lang.Object
    |
    +--java.awt.Component
       |
       +--java.awt.TextComponent
          |
          +--java.awt.TextField

在该对象中,定义了用于设置和维护用户输入字符回显方式的方法,这些方法的定义形式为:
char getEchoChar():获取用户定义的文本区回显字符;
boolean echoCharIsSet()判断是否定义了回显字符;
void setEchoChar(char c):设置文本区回显字符为字符。
具体代码为:
TextField password = new TextField(8);
password.setEchoChar('*');
其中8为文本输入的最大字符数,如果为0的话,则密码不会被屏蔽。


Password Masking in Swing
---------------------------------
利用JSWING组件,可以用JPasswordField对象,它的继承结构为:
java.lang.Object
    |
    +--java.awt.Component
       |
       +--java.awt.Container
          |
          +--javax.swing.JComponent
             |
             +--javax.swing.text.JTextComponent
                |
                +--javax.swing.JTextField
                   |
                   +--javax.swing.JPasswordField

具体代码为:
JPasswordField password = new JPasswordField(8);
password.setEchoChar('#');


Command-Line Input Masking
----------------------------------
不像Awt/Swing,在Java中没有专门用于屏蔽命令行下文本输入的API.如果想提供基于命令行文本输入的JAVA应用程序,一种方法是用Java Native Interface(JNI),但是对于一不太熟悉C/C++的开发者,或者坚持要用100%纯JAVA的开发者是难了一些。

这里提供针对此问题一个解决方案,较早的写关于此问题的文章,是像UNIX一样,用一个独立的线程来屏蔽(擦除)回显的字符。这篇文章,现在可以在http://forum.java.sun.com/thread.jsp?forum=9&thread=490728看到。


简单的解决方法
================
用一个独立的线程,在字符输入时擦除回显的字符,并用星号(*)来替换。代码如下:


//EraserThread.java

import java.io.*;

class EraserThread implements Runnable {
   private boolean stop;
 
   /**
    *@param The prompt displayed to the user
    */
   public EraserThread(String prompt) {
       System.out.print(prompt);
   }

   /**
    * Begin masking...display asterisks (*)
    */
   public void run () {
      stop = true;
      while (stop) {
         System.out.print("/010*");
  try {
     Thread.currentThread().sleep(1);
         } catch(InterruptedException ie) {
            ie.printStackTrace();
         }
      }
   }

   /**
    * Instruct the thread to stop masking
    */
   public void stopMasking() {
      this.stop = false;
   }
}

(注:本方法运行运行大量线程,如果电脑承担较大的负荷,系统就不会保证线程运行的稳定性。)

下边的PasswordField类利用EraserThread类,用于用(*)来替换用户输入的密码字符.

//PasswordField.java

public class PasswordField {

   /**
    *@param prompt The prompt to display to the user
    *@return The password as entered by the user
    */
   public static String readPassword (String prompt) {
      EraserThread et = new EraserThread(prompt);
      Thread mask = new Thread(et);
      mask.start();

      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
      String password = "";

      try {
         password = in.readLine();
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
      // stop masking
      et.stopMasking();
      // return the password entered by the user
      return password;
   }
}

 
TestApp.java是对此方法的一个应用.

//TestApp.java

class TestApp {
   public static void main(String argv[]) {
      String password = PasswordField.readPassword("Enter password: ");
      System.out.println("The password entered is: "+password);
   }
}

 

让代码更安全和可靠
=======================
1.声明private volatile boolean stop;volatile关键字用于指定和进程同步进行,换句话说就是变量的值直接从内存中讲习取,不在堆栈中留有拷贝。
2.为了保证在担有重负荷的机器上稳定运行,设置线程的优先权更高。

//MaskingThread.java

import java.io.*;

/**
 * This class attempts to erase characters echoed to the console.
 */

class MaskingThread extends Thread {
   private volatile boolean stop;
   private char echochar = '*';

  /**
   *@param prompt The prompt displayed to the user
   */
   public MaskingThread(String prompt) {
      System.out.print(prompt);
   }

  /**
   * Begin masking until asked to stop.
   */
   public void run() {

      int priority = Thread.currentThread().getPriority();
      Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

      try {
         stop = true;
         while(stop) {
           System.out.print("/010" + echochar);
           try {
              // attempt masking at this rate
              Thread.currentThread().sleep(1);
           }catch (InterruptedException iex) {
              Thread.currentThread().interrupt();
              return;
           }
         }
      } finally { // restore the original priority
         Thread.currentThread().setPriority(priority);
      }
   }

  /**
   * Instruct the thread to stop masking.
   */
   public void stopMasking() {
      this.stop = false;
   }
}

在上边的程序我们看到,利用String数据类型来存储Password这样的敏感信息是不合适的,String对象是不可改变的,并且在用过之后是不可重写的,因此下边会用Char型来代替.

PasswordField.java

import java.io.*;
import java.util.*;

/**
 * This class prompts the user for a password and attempts to mask input with "*"
 */

public class PasswordField {

  /**
   *@param input stream to be used (e.g. System.in)
   *@param prompt The prompt to display to the user.
   *@return The password as entered by the user.
   */

   public static final char[] getPassword(InputStream in, String prompt) throws IOException {
      MaskingThread maskingthread = new MaskingThread(prompt);
      Thread thread = new Thread(maskingthread);
      thread.start();
 
      char[] lineBuffer;
      char[] buf;
      int i;

      buf = lineBuffer = new char[128];

      int room = buf.length;
      int offset = 0;
      int c;

      loop:   while (true) {
         switch (c = in.read()) {
            case -1:
            case '/n':
               break loop;

            case '/r':
               int c2 = in.read();
               if ((c2 != '/n') && (c2 != -1)) {
                  if (!(in instanceof PushbackInputStream)) {
                     in = new PushbackInputStream(in);
                  }
                  ((PushbackInputStream)in).unread(c2);
                } else {
                  break loop;
                }

                default:
                   if (--room < 0) {
                      buf = new char[offset + 128];
                      room = buf.length - offset - 1;
                      System.arraycopy(lineBuffer, 0, buf, 0, offset);
                      Arrays.fill(lineBuffer, ' ');
                      lineBuffer = buf;
                   }
                   buf[offset++] = (char) c;
                   break;
         }
      }
      maskingthread.stopMasking();
      if (offset == 0) {
         return null;
      }
      char[] ret = new char[offset];
      System.arraycopy(buf, 0, ret, 0, offset);
      Arrays.fill(buf, ' ');
      return ret;
   }
}

最后给出上述修正后的代码的一个应用PasswordApp Class

// PasswordApp.java

import java.io.*;

public class PasswordApp {
   public static void main(String argv[]) {
      char password[] = null;
      try {
         password = PasswordField.getPassword(System.in, "Enter your password: ");
      } catch(IOException ioe) {
         ioe.printStackTrace();
      }
      if(password == null ) {
         System.out.println("No password entered");
      } else {
         System.out.println("The password entered is: "+String.valueOf(password));
      }
   }
}
===================================================
注:本文转自http://java.sun.com,原始文章:http://java.sun.com/developer/technicalArticles/Security/pwordmask/
原作者:Qusay H. Mahmoud

posted on 2004-08-02 22:06  rickyxing  阅读(332)  评论(0编辑  收藏  举报