Java 编程语言中的口令屏蔽

Python013

Java 编程语言中的口令屏蔽,第1张

目前 对于 Java 命令行基于文本的输入/输出 API 的批评之一就是它缺乏对命令行输入口令屏蔽的支持 如果借助 AWT/Swing 这便不再成为问题 因为 AWT/Swing 提供了可以提供屏蔽口令的方法

年 月 我发表了本文的早期版本 其后便不断收到大量感谢信 建设性的意见和在应用程序中使用源代码的许可 本文

概述了口令屏蔽描述了用于口令屏蔽的 AWT/Swing 实用程序为命令行输入口令屏蔽问题提供独立于平台的解决方案为口令屏蔽提供一个改进的解决方案(可靠而安全)

口令屏蔽

登录屏幕和登录对话框使用口令屏蔽技术 这种技术要么在输入口令时隐藏口令 要么显示一个字符(比如星号 * )来代替用户输入的字符 例如 当您在一台 Windows 机器上进行登录时 一个登录对话框将会呈现在您眼前 其中的口令一栏使用星号作为屏蔽或回显字符

如果操作系统是 UNIX 则登录屏幕中的口令栏不显示回显字符 它的做法很简单 就是什么都不显示

AWT/Swing 中的口令屏蔽

如果您希望为您的应用程序提供图形化的登录对话框 您可以使用 AWT 的 TextField 类 该类是一个文本组件 允许编辑单行文本 为了屏蔽口令栏 要使用 setEchoChar 方法 例如 为了把回显字符设置为星号 您需要这样做

TextField password = new TextField( )password setEchoChar( * )

基于所使用字体的平均字符宽度 数字 指定了文本栏的宽度 您可以把回显字符设置为任何您喜欢的字符 注意 如果您把它设置为 这意味着输入将会被回显 而不会被屏蔽

在 Swing 中 您可以使用 JPasswordField 它允许编辑单行文本 视图表明正在输入内容 但是不会显示原始字符 JPasswordField 类与和 setEchoChar 一起使用的 AWT 的 TextField 是源代码兼容的 如果您使用 JPasswordField 默认的回显字符是星号 * 但是您可以将其修改为任何您选定的字符 此外 如果您把回显字符设置为 这意味着字符将在输入时显示出来 而不会被屏蔽 图 显示了一个 Swing 登录对话框 其中的回显字符被设置为 # 使用的是下面的代码片断

JPasswordField password = new JPasswordField( )password setEchoChar( # )

命令行输入屏蔽

和 AWT/Swing 不同 在 Java 中没有特殊的 API 可用于屏蔽命令行输入 这也是许多开发人员一直所要求的一项功能 如果您希望为命令行基于文本的 Java 应用程序以及服务器端 Java 应用程序提供一个登录屏幕 它就很有用 提供这种功能的一种方式就是使用 Java 本地接口(Java Native Interface JNI) 对于不了解 C/C++ 或者希望坚持 % 纯 Java 代码的某些 Java 开发人员来说 这可能有一定难度

这里我针对这个问题提出一个解决方案 在本文的早期版本中 所使用的是一个 UNIX 风格的登录屏幕 口令根本不在屏幕上回显 这样做的具体方法是 让一个单独的线程通过重写和打印口令提示命令行 尝试擦除回显到控制台的字符 大家仍然可以从论坛下载该篇文章中专用的代码和改进后的代码

然而 大家最需要的功能之一是使用星号 * 替换回显的字符 因此 本文从为口令屏蔽提供一个简单的解决方案开始 接着给出改进后的 更加可靠和安全的代码

简单的解决方案

这个解决方案使用一个单独的线程 在输入回显字符的时候擦除它们 然后使用星号代替它们 这是使用 EraserThread 类来完成的 如代码示例 所示

代码示例 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( \ * )         try {            Thread currentThread() sleep( )         } catch(InterruptedException ie) {            ie printStackTrace()         }      }   }

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

注意 这个解决方案广泛利用了线程 然而 如果机器负载很重 就不能确保 MaskingThread 能够足够经常地运行 请继续阅读本文的余下部分来了解代码的改进版本

PasswordField 类使用了 EraserThread 类 这一点在代码示例 中体现出来了 这个类提示用户输入口令 而且 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   }}

作为如何使用 PasswordField 类的一个例子 考虑应用程序 TestApp 如示例代码 所示 这个应用程序显示一条提示 并等待用户输入口令 当然 输入被屏蔽为星号(*)

代码示例 TestApp java

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

如果您在 Windows MacOS 或 UNIX 操作系统上运行 TesApp 您将会发现其输出与图 类似 此外还要注意 当您运行该应用程序时 会显示一个初始的星号

使代码安全而可靠

上述的简单解决方案有一个主要缺陷 不应该使用字符串来存储诸如口令这类敏感信息!在本文的余下部分中 将会给出一个经过改进的解决方案

然而 首先 MaskingThread 类能够从几处改进中获益

为了确保跨线程的可见性 尤其是在多 CPU 的机器上 stop 字段应该被标记为 volatile volatile 关键字指定同步线程使用该字段 这样编译器就不会对它进行任何优化 换句话说 应该从内存读取变量的值 而不应该在堆栈中保存任何拷贝

为了确保屏蔽能够在系统高负荷运转时也能够出现 在调用持续期间 调用线程的优先权被设定为最大 返回时再恢复其原始的优先权

代码示例 显示了修订后的 MaskingThread 类 修改的地方均以粗体形式突出显示

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( \ + echochar)           try {              // attempt masking at this rate              Thread currentThread() sleep( )           }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   }}

尽管使用 Strings 收集和存储口令看起来似乎很合逻辑 它们并不适合存储诸如口令这样的敏感信息 这是因为 Strings 类型的对象是不可改变的——使用后不能重写或修改字符串的内容 应该使用一个 chars 数组作为代替 修订后的 PasswordField 如代码示例 所示 它是根据 Using Password Based Encryption 改写而来

代码示例 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[ ]

int room = buf length      int offset =       int c

loop:   while (true) {         switch (c = in read()) {            case :            case :               break loop

case \r :               int c = in read()               if ((c != ) &&(c != )) {                  if (!(in instanceof PushbackInputStream)) {                     in = new PushbackInputStream(in)                  }                  ((PushbackInputStream)in) unread(c )                } else {                  break loop                }

default:                   if ( room <) {                      buf = new char[offset + ]                      room = buf length offset                       System arraycopy(lineBuffer buf offset)                      Arrays fill(lineBuffer )                      lineBuffer = buf                   }                   buf[offset++] = (char) c                   break         }      }      maskingthread stopMasking()      if (offset == ) {         return null      }      char[] ret = new char[offset]      System arraycopy(buf ret offset)      Arrays fill(buf )      return ret   }}

最后 PasswordApp 类如代码示例 所示 它只是一个用于测试修订后代码的测试应用程序

代码示例 PasswordApp java

import java io *

lishixinzhi/Article/program/Java/hx/201311/25934

只能输入数字:“^[0-9]*$”只能输入n位的数字:“^\d{n}$”只能输入至少n位数字:“^\d{n,}$”只能输入m-n位的数字:“^\d{m,n}$”只能输入零和非零开头的数字:“^(0|[1-9][0-9]*)$”只能输入有两位小数的正实数:“^[0-9]+(.[0-9]{2})?$”只能输入有1-3位小数的正实数:“^[0-9]+(.[0-9]{1,3})?$”只能输入非零的正整数:“^\+?[1-9][0-9]*$”只能输入非零的负整数:“^\-[1-9][0-9]*$”只能输入长度为3的字符:“^.{3}$”只能输入由26个英文字母组成的字符串:“^[A-Za-z]+$”只能输入由26个大写英文字母组成的字符串:“^[A-Z]+$”只能输入由26个小写英文字母组成的字符串:“^[a-z]+$”只能输入由数字和26个英文字母组成的字符串:“^[A-Za-z0-9]+$”只能输入由数字、26个英文字母或者下划线组成的字符串:“^\w+$”验证用户密码:“^[a-zA-Z]\w{5,17}$”正确格式为:以字母开头,长度在6-18之间,