6 M/ |* Q2 ~7 C' A, _4 m4 O' W 在很多电子商务和社区应用中,管理用户的Account是一种最常用的基本功能,尽管很多Application Server提供了这些基本组件,但很多应用开发者为了管理的更大的灵活性还是喜欢采用关系数据库来管理用户,懒惰的做法是用户的密码往往使用明文或简单的变换后直接保存在数据库中,因此这些用户的密码对软件开发者或系统管理员来说可以说毫无保密可言,本文的目的是介绍MD5的Java Bean的实现,同时给出用MD5来处理用户的Account密码的例子,这种方法使得管理员和程序设计者都无法看到用户的密码,尽管他们可以初始化它们。但重要的一点是对于用户密码设置习惯的保护。 7 \. o+ r. @2 b- V
1 K+ D! C% Q5 z- A 有兴趣的读者可以从这里取得MD5也就是RFC 1321的文本。http://www.ietf.org/rfc/rfc1321.txt 1 R: n# s7 E: h, X0 h1 \/ E
: E; t3 I$ u }$ l" U0 n
实现策略 6 H% c ~! o+ m, x) I/ p0 ~* _
) V( `- B6 e& [- Q) v" i# P% U MD5的算法在RFC1321中实际上已经提供了C的实现,我们其实马上就能想到,至少有两种用Java实现它的方法,第一种是,用Java语言重新写整个算法,或者再说简单点就是把C程序改写成Java程序。第二种是,用JNI(Java Native Interface)来实现,核心算法仍然用这个C程序,用Java类给它包个壳。 5 ]! H- \+ L4 J" d) f $ c" z5 R4 ~: E 但我个人认为,JNI应该是Java为了解决某类问题时的没有办法的办法(比如与操作系统或I/O设备密切相关的应用),同时为了提供和其它语言的互操作性的一个手段。使用JNI带来的最大问题是引入了平台的依赖性,打破了SUN所鼓吹的“一次编写到处运行”的Java好处。因此,我决定采取第一种方法,一来和大家一起尝试一下“一次编写到处运行”的好处,二来检验一下Java 2现在对于比较密集的计算的效率问题。 ( T) q e! m4 C0 U' V
6 h+ ~' F+ g; H2 e" o 实现过程 6 g5 Q' _9 L& R& K9 P( a+ l5 y! y- r- G4 _/ y0 U
限于这篇文章的篇幅,同时也为了更多的读者能够真正专注于问题本身,我不想就某一种Java集成开发环境来介绍这个Java Bean的制作过程,介绍一个方法时我发现步骤和命令很清晰,我相信有任何一种Java集成环境三天以上经验的读者都会知道如何把这些代码在集成环境中编译和运行。用集成环境讲述问题往往需要配很多屏幕截图,这也是我一直对集成环境很头疼的原因。我使用了一个普通的文本编辑器,同时使用了Sun公司标准的JDK 1.3.0 for Windows NT。 0 O& ~+ U1 a5 T7 J4 U$ g+ A
& f+ w8 N& y- t# e7 T7 f& Z+ c 其实把C转换成Java对于一个有一定C语言基础的程序员并不困难,这两个语言的基本语法几乎完全一致.我大概花了一个小时的时间完成了代码的转换工作,我主要作了下面几件事: + H8 ?. H: ?+ Y. s+ u9 H; d$ r; K D% s! @7 f9 v
# M4 Y1 U+ T' Z8 r 把必须使用的一些#define的宏定义变成Class中的final static,这样保证在一个进程空间中的多个Instance共享这些数据 ; q- w" @9 j7 l6 X! w0 f% X ; c7 E' h9 S1 P' K2 F4 k' h. J 删去了一些无用的#if define,因为我只关心MD5,这个推荐的C实现同时实现了MD2 MD3和 MD4,而且有些#if define还和C不同编译器有关 ^' b% o! h6 |/ Z 1 d7 F' q) Y t7 s8 W# h 将一些计算宏转换成final static 成员函数。 $ @/ Y! j) j5 |4 P6 M' k $ E0 ^& @; K3 G! J 所有的变量命名与原来C实现中保持一致,在大小写上作一些符合Java习惯的变化,计算过程中的C函数变成了private方法(成员函数)。 & `, r1 s) H& e# a; n
A3 m! A8 C: k1 S, J
关键变量的位长调整 5 z. o ^, e( W; ^: r% A
4 v+ j% t9 a8 E% t: k3 X 定义了类和方法 + G1 c1 J1 i% Z8 h) T4 A. ~# M2 @7 l. q9 W2 O/ b
需要注意的是,很多早期的C编译器的int类型是16 bit的,MD5使用了unsigned long int,并认为它是32bit的无符号整数。而在Java中int是32 bit的,long是64 bit的。在MD5的C实现中,使用了大量的位操作。这里需要指出的一点是,尽管Java提供了位操作,由于Java没有unsigned类型,对于右移位操作多提供了一个无符号右移:>>>,等价于C中的 >> 对于unsigned 数的处理。 3 o2 n8 x( R/ d1 d' l, U / |# f4 q( L- d, B 因为Java不提供无符号数的运算,两个大int数相加就会溢出得到一个负数或异常,因此我将一些关键变量在Java中改成了long类型(64bit)。我个人认为这比自己去重新定义一组无符号数的类同时重载那些运算符要方便,同时效率高很多并且代码也易读,OO(Object Oriented)的滥用反而会导致效率低下。 ( |5 ?/ N* d. B2 k0 `7 o- k- o3 H
\2 e# G; C$ W+ ]; C: c" {0 |
限于篇幅,这里不再给出原始的C代码,有兴趣对照的读者朋友可以去看RFC 1321。MD5.java源代码 6 K t$ u \, Q% @# R ?; U ~ " Y! P$ g I* m6 m 测试 1 o6 Q9 d. Q, I2 j' R I* ?
) }* g6 _9 G# p
在RFC 1321中,给出了Test suite用来检验你的实现是否正确: 2 O+ C2 V! b+ ?8 u' s8 Q % v1 B4 `- w- a) u( z. mMD5 ("") = d41d8cd98f00b204e9800998ecf8427e : m. X# p* ^) g0 Q" R: Q; c1 x , Q% B" I. R5 [MD5 ("a") = 0cc175b9c0f1b6a831c399e269772661 2 Q' K' O. B# \4 Z
O$ ^# v# l" t1 b ~
MD5 ("abc") = 900150983cd24fb0d6963f7d28e17f72 1 b& ~6 y' `% ]+ E9 C. O
5 \- M, s. \3 wMD5 ("message digest") = f96b697d7cb7938d525a2f31aaf161d0 + i; R7 Y1 a! N! n+ V. F/ V
; [& \! E# e! m; @) P2 k6 q8 K
MD5 ("abcdefghijklmnopqrstuvwxyz") = c3fcd3d76192e4007dfb496cca67e13b 5 s+ I6 Q* b: |. J0 i
0 u8 i2 a8 w' L0 u" e7 c
…… 8 H ~! q4 z& j- o' f- c$ c % g, b5 F: Q6 H: e6 t8 r* A 这些输出结果的含义是指:空字符串””的MD5值是d41d8cd98f00b204e9800998ecf8427e,字符串”a”的MD5值是0cc175b9c0f1b6a831c399e269772661…… ) i* [/ Z. M. N! x" |