Java 加密技术:消息摘要。
一个消息摘要就是一个数据块的数字指纹。即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组)。
消息摘要有两个基本属性:
两个不同的报文难以生成相同的摘要
难以对指定的摘要生成一个报文,而由该报文反推算出该指定的摘要(就是通常所说的加密后的密文是单向不可逆的)
代表:美国国家标准技术研究所的SHA1和麻省理工学院Ronald Rivest提出的MD5
-
public abstract class MessageDigest
extends
Object
MessageDigest 提供了消息摘要算法,如 MD5 或 SHA,的功能。消息摘要是安全单向散列函数,它采用任意大小的数据并输出一个固定长度的散列值。
象 Java 安全性中的其它基于算法的类一样,MessageDigest 有两个主要的组件:
-
消息摘要 API ( 应用程序接口 )
- 这是需要消息摘要服务的应用调用的方法的接口。这个 API 由所有公有方法组成。 消息摘要 SPI ( 服务提供者接口 )
-
该接口是由提供特殊算法的提供者实现的接口。它由所有名字前缀为 engine 的方法组成。每个这样的方法由具有相应名字的公有 API 方法调用。例如,
engineReset
方法由
reset
方法调用。SPI 方法是抽象的;提供者必须提供一个具体的实现。
MessageDigest 对象在启动时被初始化。使用 update 方法处理数据。在任何地方都可调用 reset 复位摘要。一旦所有需要修改的数据都被修改了,将调用一个 digest 方法完成散列码的计算。
对于给定次数的修改,只能调用
digest
方法一次。在调用
digest
之后,MessageDigest 对象被复位为初始化的状态。
可以自由的实现 Cloneable 接口,这样做将会使客户应用在复制前用
instanceof Cloneable
测试可复制性:
MessageDigest md = MessageDigest.getInstance("SHA"); if (md instanceof Cloneable) { md.update(toChapter1); MessageDigest tc1 = md.clone(); byte[] toChapter1Digest = tc1.digest; md.update(toChapter2); ...etc. } else { throw new DigestException("couldn't make digest of partial content"); }
注意如果给定的实现是不可复制的,如果事先知道摘要的数目,仍然能以几个实例为例计算中间的摘要。
构造子
MessageDigest
protected MessageDigest(String algorithm)
-
用指定的算法名创建一个消息摘要。
-
-
参数:
- algorithm - 摘要算法的标准字符串名。 Java 密码结构 API 说明书 & 参考 的附录 A。 -->
方法
getInstance
public static MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException
-
生成一个 MessageDigest 对象,它实现指定的摘要算法。 如果缺省的提供者包包含一个实现了该算法 MessageDigest 子类,则返回该子类的一个实例。如果算法在缺省包中是不可用的,将搜索其它的包。
-
-
参数:
- algorithm - 申请的算法名。 Java 密码结构 API 说明书 & 参考 的附录 A。 --> 返回值:
- 一个实现指定算法的 Message Digest 对象。 抛出: NoSuchAlgorithmException
- 如果算法在调用者环境中是不可用的。
getInstance
public static MessageDigest getInstance(String algorithm, String provider) throws NoSuchAlgorithmException, NoSuchProviderException
-
生成一个 MessageDigest 对象,实现指定的算法,如果提供者的算法是可用的,那么该算法由该提供者提供。
-
-
参数:
- algorithm - 申请的算法名。 Java 密码结构 API 说明书 & 参考 的附录 A。 -->
- provider - 提供者的名字。 返回值:
- 一个实现指定算法的 Message Digest 对象。 抛出: NoSuchAlgorithmException
- 如果算法在申请的调用者提供的包中是不可用的。 抛出: NoSuchProviderException
- 如果提供者在环境中是不可用的。 参见:
- Provider
update
public void update(byte input)
-
用指定的字节修改该摘要。
-
-
参数:
- input - 用于修改摘要的字节。
update
public void update(byte input[], int offset, int len)
-
从数组指定的偏移量开始,用指定的字节数组修改摘要。
-
-
参数:
- input - 该字节数组。
- offset - 字节数组中开始的偏移量。
-
len - 从
offset
开始用的字节数。
update
public void update(byte input[])
-
用指定的字节数组修改该摘要。
-
-
参数:
- input - 该字节数组。
digest
public byte[] digest()
-
通过执行最后的诸如填充的操作完成散列码的计算。 在调用之后复位该摘要。
-
-
返回值:
- 存放结果散列值的字节数组。
digest
public byte[] digest(byte input[])
-
使用指定的字节数组执行对摘要最后的修改,然后完成摘要计算。 即,这个方法首先对数组调用
update,然后调用
digest()。
-
-
参数:
- input - 在摘要计算完成之前用于修改的输入值。 返回值:
- 结果散列值的字节数组。
toString
public String toString()
isEqual
public static boolean isEqual(byte digesta[], byte digestb[])
-
比较两个摘要是否相同。 进行简单的比较。
-
-
参数:
- digesta - 要比较的一个摘要。
- digestb - 要比较的另一个摘要。 返回值:
- 如果两个摘要相等则为 true ,否则为 false。
reset
public void reset()
- 为将来的使用复位该摘要。
我们知道,编程中数据的传输,保存,为了考虑安全性的问题,需要将数据进行加密.我们拿数据库做例子.如果一个用户注册系统的数据库,没有对用户的信息进行保存,如,我去页面注册,输入"Vicky","123456".注册.web服务器未对数据进行加密而直接写入数据库,那么数据库中的用户信息,便是一个直接可用的数据!一旦服务器服务器被黑~那么用户的信息将毫无保留的展现在黑客面前...为了解决这个弊端,现在大多数都会将信息进行MD5加密.如"Vicky"与"123456"加密后,会生成16位或者32位字符串.而黑客即便获得这些数据也无法使用...
MD5是常用的加密方法,这里主要讲述JDK中的java.security.MessageDigest加密方式!
- @Test
- public void testMD() {
- try {
- String username = "Vicky";
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.update(username.getBytes());
- String usernameMD5 = messageDigest.digest().toString();
- System.out.println(usernameMD5);
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
打印的是:[B@107077e,这是因为输出的是byte[](messageDigest.digest()得到的是个二进制byte数组,有可能某些byte是不可打印的字符。)...我们可以使用Base64来处理byte[]
- @Test
- public void testMD() {
- try {
- String username = "Vicky";
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.update(username.getBytes());
- System.out.println(Base64.encode(messageDigest.digest()));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
打印的是:AgwpBZPO+ErqxOosJp0ybQ==
当然我们可以编写函数,处理二进制转hex字符串.
如:
- @Test
- public void testMD() {
- try {
- String username = "Vicky";
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.update(username.getBytes());
- System.out.println(toHex(messageDigest.digest()));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- @Test
- public void testMD() {
- try {
- String username = "Vicky";
- MessageDigest messageDigest = MessageDigest.getInstance("MD5");
- messageDigest.update(username.getBytes());
- System.out.println(toHex(messageDigest.digest()));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
打印:020c290593cef84aeac4ea2c269d326d,返回的是32位的字符串!!!
这样我们便可以直接使用JDK为我们提供的加密类与函数了!
MessageDigest不仅仅只为我们提供了"MD5"加密,还提供了"SHA-1"
创建的方法只为: MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
MD5与SHA-1的区别为:MD5是16位,SHA是20位(这是两种报文摘要的算法)
难道 MessageDigest 只能用作数据加密吗?如何使用MessageDigest生成安全令牌!!!
时候,我们必须把用户密码存放到数据库,为了安全起见,我们需要对这些密码进行单向的加密处理,
比如,有明文密码如下:
String originalPwd = "mypassword";
应用报文摘要方法,得到单向的加密字符串
//MD5是16位,SHA是20位(这是两种报文摘要的算法)
//MessageDigest md= MessageDigest.getInstance("MD5");
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
//String digestedPwdString = new String(messageDigest.digest());
String digestedPwdString = new String(Base64.encode(messageDigest.digest()));
System.out.println("pwd:" + digestedPwdString);
这样,就得到密码的报文摘要,把此摘要保存到数据库,
以后用户登陆时,用相同的算法算出摘要,和数据库中的比较,如果一致,则密码正确。
注意:
byte[] digest = messageDigest.digest();
得到的是个二进制byte数组,有可能某些byte是不可打印的字符。
所以用Base64.encode把它转化成可打印字符。
也可以把digest的每个byte转化成hex(16进制)保存。
MessageDigest messageDigest=MessageDigest.getInstance("SHA-1");
messageDigest.update(originalPwd.getBytes());
byte[] bin = messageDigest.digest();
再调用下面的方法生产hex(16进制)保存。
二行制转hex字符串的方法如下:
private static String byte2hex(byte[] b){
String hs="";
String stmp="";
for (int n=0; n<b.length; n++){
stmp=(java.lang.Integer.toHexString(b[n] & 0xFF));
if (stmp.length()==1) hs=hs+"0"+stmp;
else hs=hs+stmp;
}
return hs;
}
或者:
private static String byto2hex2(byte[] bin){
StringBuffer buf = new StringBuffer();
for (int i = 0; i < bin.length; ++i) {
int x = bin[i] & 0xFF, h = x >>> 4, l = x & 0x0F;
buf.append((char) (h + ((h < 10) ? '0' : 'a' - 10)));
buf.append((char) (l + ((l < 10) ? '0' : 'a' - 10)));
}
return buf.toString();
}
或者:
干脆直接用下面的方法生成,用到第三方包:
public static String encryptPwd(String pwd, String algorithm){
//String a = org.apache.catalina.realm.RealmBase.Digest(pwd,"SHA-1");
return org.apache.catalina.realm.RealmBase.Digest(pwd, algorithm);
}