IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope: Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  Linux | Java technology  >

使用 MD5 加密来增强 Linux 上 Java 应用程序的安全性

为应用程序提供 PAM 兼容的认证系统接口

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 中级

Vladimir Silva (vladimir_silva@yahoo.com), 计算机科学家, Freelance Developer

2006 年 2 月 06 日

UNIX®/Linux® PAM 兼容系统使用了这样一种认证机制,即以 crypt() 系统调用的 GNU MD5 扩展为基础。本文对这些扩展进行了介绍,并向您展示了 MD5 加密的一种 Java™ 实现,它可以与 UNIX/Linux 系统兼容。

如果您是一名安全性开发人员,需要为 Java 应用程序提供本地操作系统用户注册接口,那么应该怎样做呢?本文为您提供了答案:这就是 UNIX/Linux PAM(Pluggable Authentication Module,可插入认证模块)兼容的系统,它使用了以 crypt() 系统调用的 GNU MD5 扩展为基础的认证机制。在本文中我们将对这些扩展进行介绍,并展示 MD5 加密的一种 Java 实现(我们使用了 FreeBSD 作为 UNIX 平台)。

MD5(消息摘要算法 5;RFC 1321)是一种广泛应用的加密散列函数,使用了 128 位的散列值。这个算法可以接收任意长度的消息作为输入,并对输入生成一个 128 位的 “指纹(fingerprint)” 或 “消息摘要(message digest)” 作为输出。这个算法并不需要很大的置换表。在这个算法中密钥的概念是,我们不可能产生两条具有相同消息摘要的消息。

旧风格的 crypt(3) 函数

crypt() 是 UNIX 系统上使用的密码加密函数。它基于数据加密标准(Data Encryption Standard)算法,并具有一些用来禁用密钥搜索的硬件实现的变种。这个函数的原型如下:

char *crypt(const char *key, const char *salt)

其中 key 是要被加密的密码字符串,salt 是从 [a-A-0-./] 子集中选择的一个包含两个字符的字符串。这个字符串用来以 4,096 种不同的方法对这个算法进行初始化。这个算法使用密码中前 8 个字符的低 7 位来生成一个密钥。这个密钥用来反复对一个常量字符串(包含 0 )进行转换。返回值是一个包含 13 个可打印字符的字符串,用来表示加密后的密码。

这个函数具有以下缺点:

  • 只使用了密码的前 8 个字符和每个字符的低 7 位。

  • 这 56 位的密钥很容易被目前的计算能力破解。

  • 这个密钥空间包含 2^56 = 7.2e16 个可能值。使用并行计算机对这个密钥空间进行遍历搜索是可能的。目前有一些这方面的开源软件,例如 MD5 Crack 项目(请参阅 参考资料);MD5 Crack 可以对密钥空间的可能值进行搜索,以得到密码。

  • DES 算法本身有一些怪癖,它使用了 crypt(3) 接口,而该接口对于除密码认证之外的任何功能都不是什么好选择。




回页首


GNU 扩展:MD5 加密

GNU 对 crypt() 函数提供了以下扩展:如果 salt 是一个以 3 个字符 "$1$" 开始的字符串,后面是前 8 个字符(可以以 "$" 结束),那么 crypt() 函数就不使用 DES 算法,而是使用基于 MD5 的算法,并输出 34 个字节的内容。具体来说,这些字节是 $1$[string]$[encrypted-string],其中 [string] 代表 salt 中 "$1$" 后面的 8 个字符,后面是 22 个从 [a-zA-Z0-9./] 集合中选出的字符。

这些扩展具有以下优点:

  • 基于 MD5 的加密比已经过时的 DES 提供了更好的安全性和更大的密钥空间。

  • 整个密码都非常重要,而不仅仅是前 8 个字节。




回页首


算法的伪代码

这个算法使用两个 MD5 散列值对密码和 salt 进行一系列转换来进行工作。返回值是一组包含 34 个可打印的 ASCII 字符的组合,其格式如下:

$1$[salt]$[encrypted]

其中:

  • "$1$" 是一个魔力字符串,它告诉 OS 使用 MD5 算法,而不是使用 DES 算法。

  • [salt] 是该算法的参数,是包含 8 个字符的字符串。

  • [encrypted] 是一个 22 字节的转换字符,代表加密后的密码。

这个算法的伪代码如清单 1 所示。


清单 1. MD5 加密的伪代码
                
// md5_crypt P-Code
// Inputs:
//  password: Text to be encrypted
//  salt: 8 character ramdom string from the set [a-zA-Z0-9]
//  magic_string: The string used for encryption
//   $1$ for Linux/BSD compatible encryption
//   $apr1$ for Apache's MD5Crypt
// Output: Password hash
sub md5_crypt( password, salt, magic_string )
{
   MD5 h1;
   MD5 h2;
   byte[] finalState;
   // MD5 Hash #1: pwd, magic string and salt
   h1.update( password );
   h1.update( magic_string );
   h1.update( salt );
   // MD5 Hash #2: password, salt, password
   h2.update( password );
   h2.update( salt );
   h2.update( password );
   finalState = h2.digest();
   //
   // Two sets of transformations based on the length of the password
   //
   for ( int i = password.length(); i > 0; i -= 16 )
   {
      // Update h1 from offset value (i > 16 ? 16 : i)
      h1.update( finalState, 0, i > 16 ? 16 : i );
   }
   for ( int i = password.length(); i != 0; i >> >= 1 )
   {
      if ( ( i & 1 ) != 0 )
      {
         h1.update( finalState, 0, 1 );
      }
      else
      {
         h1.update( password, 0, 1 );
      }
   }
   finalState = h1.digest();
   // Make an output string
   String encoded;
   //
   // Salt is part of the encoded password ($1$<string>$)
   //
   encoded.append( magic_string );
   encoded.append( salt );
   encoded.append( "$" );
   //
   // Hash 22 bytes chosen from the set [a-zA-Z0-9./]
   //
   // Transform the 1st 4 bytes from h1's final state into the set [a-zA-Z0-9./]
   //
   l = ( bytes2u( finalState[0] ) << 16 ) | ( bytes2u( finalState[6] ) << 8 )
         | bytes2u( finalState[12] );
   encoded.append( to64( l, 4 ) );
   // 4 bytes (8 so far...)
   l = ( bytes2u( finalState[1] ) << 16 ) | ( bytes2u( finalState[7] ) << 8 )
         | bytes2u( finalState[13] );
   encoded.append( to64( l, 4 ) );
   // 4 bytes ( 12 so far)
   l = ( bytes2u( finalState[2] ) << 16 ) | ( bytes2u( finalState[8] ) << 8 )
      | bytes2u( finalState[14] );
   encoded.append( to64( l, 4 ) );
   l = ( bytes2u( finalState[3] ) << 16 ) | ( bytes2u( finalState[9] ) << 8 )
      | bytes2u( finalState[15] );
   encoded.append( to64( l, 4 ) );
   // 20 bytes
   l = ( bytes2u( finalState[4] ) << 16 ) | ( bytes2u( finalState[10] ) << 8 )
      | bytes2u( finalState[5] );
   encoded.append( to64( l, 4 ) );
   // 22 bytes from the set [a-zA-Z0-9./]
   l = bytes2u( finalState[11] );
   encoded.append( to64( l, 2 ) );
   return encoded;
}





回页首


MD5 加密的一个纯 Java 实现

纯 Java 实现具有很多优势,它提供了一个简单的接口,Web 和其他 Java 应用程序可以对本地 UNIX 注册系统进行认证。您可以从本文最后的 下载 一节下载这个实现。





回页首


认证是如何工作的?

图 1 给出了 UNIX 系统中典型的认证过程。


图 1. UNIX 系统中的密码认证
UNIX 系统中的密码认证
  • 用户的密码是从终端读取的,并与从 /etc/shadow 中读取出来的文件的散列值(H1)一起发送给 MD5Crypt 子例程。H1 表示传递给 MD5Crypt 的 salt。

  • 上面这个步骤的输出是第二个散列值(H2)。

  • 对 H1 和 H2 进行比较。如果它们相等,那么认证就成功了。




回页首


对本地 Linux 注册系统进行测试

您可以使用下面这个简单的 shell 脚本来测试 MD5Crypt Java 程序在本地 Linux 注册系统上工作的情况。这个脚本接收一个密码作为惟一的参数。然后它会从 shadow 密码文件(/etc/shadow)中检索当前用户 H1(由 $USER 环境变量指定)的加密密码。这个脚本然后会运行 MD5Crypt 程序来获得 H2,并对这两个散列值进行比较。如果 H1 与 H2 相等,那么认证就成功了。


清单 2. 对本地注册系统测试 MD5Crypt 的 Java 实现
                
#!/bin/bash
#
# Simple authentication test for the pure java
# Implementation of MD5Crypt
#
# Password is the 1st argument
PWD=$1
if [ "$PWD" == "" ]
then
   echo "$0 <Password>"
   exit -1
fi
#
# Need super user access to perform this step
# Get the user's salt from /etc/shadow
#
SALT=`grep $USER /etc/shadow | cut -d: -f2`
echo "User: $USER"
echo "Salt(/etc/shadow): $SALT"
#
# Hash the user's pwd using the salt
#
HASH=`java grid.security.MD5Crypt $PWD $SALT`
# check exit status
if [ $? != 0 ] ; then
   echo "Error hashing user $USER password."
   exit -1
fi
echo "MD5Crypt Hash: $HASH"
#
# Compare both hashes. If they match we have a valid login
#
if [ $HASH == $SALT ]
then
   echo Authentication succeeded
else
   echo Authentication failed
fi





回页首


结束语

MD5 加密的一个纯 Java 实现可以提供一个简单的接口,Web 应用程序可以使用这个接口对本地 UNIX 注册系统进行认证。在本文中,我们介绍了 GNU 对 crypt() 系统调用的扩展,并给出了一个可以在 Java 应用程序中使用的 MD5 加密的实现。






回页首


下载

描述名字大小下载方法
Java implementation of MD5 cryptl-md5crypt.zip3KBHTTP
关于下载方法的信息


参考资料

学习

获得产品和技术
  • Ganymede 是一个可移植、可定制的网络目录管理系统,它是按照 GNU GPL 规定发布的。

  • 自由软件基金会(Free Software Foundation)提供了 GNU encryption software directory

  • Installation Verification Utility(alphaWorks,2004 年 9 月)是一个使用 MD5 对两个不同的软件安装进行比较并显示它们之间区别(包括多出或缺少的文件)的工具。

  • GData 是一个在线的 MD5 散列数据库,其中包含了 1200 多万个惟一项。

  • 订购免费的 SEK for Linux,这有两张 DVD,包括最新的 IBM for Linux 试用软件,包括 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere®。

  • 在您的下一个开发项目中采用 IBM 试用软件,这可以从 developerWorks 上直接下载。



讨论


关于作者

Vladimir Silva 出生在厄瓜多尔的基多。他于 1994 年从 Polytechnic Institute of the Army 获得系统分析员(System's Analyst)学位。同年,他作为交换学生来到美国,在 Middle Tennessee State University 学习计算机科学。毕业后,他加入了 IBM "Web-Ahead" 技术智囊团。他的兴趣包括网格计算、神经网络和人工智能。他还拥有包括 OCP、MCSD 和 MCP 在内的众多 IT 证书。




对本文的评价










回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款