转载请注明出处
首先看下测试类
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Test {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 创建MD5对象,创建过程不是本文的重点,感兴趣可以跟踪下过程
MessageDigest md5 = MessageDigest.getInstance("md5");
// 进行MD5摘要计算
byte[] bytes = md5.digest("test".getBytes(StandardCharsets.UTF_8));
}
}
MessageDigest.digest(byte[])方法,实际上调用了engineUpdate和engineDigest接口
public abstract class MessageDigest extends MessageDigestSpi {
/**
* 对字节数组进行摘要计算
* @Params input 待计算的字节数组
* @Return 返回哈希结果值
*/
public byte[] digest(byte[] input) {
update(input);
return digest();
}
/**
* 更新MessageDigest对象中待计算的字节数组
* @Params input 待计算的字节数组
*/
public void update(byte[] input) {
engineUpdate(input, 0, input.length);
state = IN_PROGRESS;
}
/**
* 更新MessageDigest对象中待计算的字节数组
* @Params input 待计算的字节数组
* @Params offset 数组中的开始位置
* @Params len 使用的字节数量,从offset开始计算
*/
protected abstract void engineUpdate(byte[] input, int offset, int len);
/**
* 执行数据填充以及完成哈希值计算
* @Return 返回哈希结果值
*/
public byte[] digest() {
/* Resetting is the responsibility of implementors. */
byte[] result = engineDigest();
state = INITIAL;
return result;
}
/**
* MD5算法对象执行数据填充以及完成哈希值计算
* @Return 返回哈希结果值
*/
protected abstract byte[] engineDigest();
}
进入到MD5对象,发现MD5并没有实现engineUpdate和engineDigest接口,默认调用MD5父类DigestBase的实现方法
abstract class DigestBase extends MessageDigestSpi implements Cloneable {
// number of bytes processed so far. subclasses should not modify
// this value.
// also used as a flag to indicate reset status
// -1: need to call engineReset() before next call to update()
// 0: is already reset
long bytesProcessed;
/**
* 更新MessageDigest对象中待计算的字节数组
* @Params input 待计算的字节数组
* @Params offset 数组中的开始位置
* @Params len 使用的字节数量,从offset开始计算
*/
// array update. See JCA doc.
protected final void engineUpdate(byte[] b, int ofs, int len) {
if (len == 0) {
return;
}
if ((ofs < 0) || (len < 0) || (ofs > b.length - len)) {
throw new ArrayIndexOutOfBoundsException();
}
// 在md5.digest("")方法初次调用时不会进入,这时bytesProcessed值为0,预处理state在创建MD5对象时就已经初始化
// 当再次调用md5.digest(byte[])方法时bytesProcessed值为-1,需要重新初始化,注:每次调用完digest方法都会重置bytesProcessed值为-1
if (bytesProcessed < 0) {
// 初始化哈希结果值数据(预处理state),MD5在这个初始数据上进行论计算
engineReset();
}
// 在MessageDigest.update(input)方法调用engineUpdate时,bytesProcessed存储的是待计算的字节数组(input中使用的字节数量)
// 在MessageDigest.digest()方法执行过程中还会再次调用engineUpdate方法,这时bytesProcessed存储的是待计算的字节数组 + 填充值的长度,也就是MD5块大小512bit的倍数 - 64(原消息长度,固定64位)
bytesProcessed += len;
// if buffer is not empty, we need to fill it before proceeding
if (bufOfs != 0) {
int n = Math.min(len, blockSize - bufOfs);
System.arraycopy(b, ofs, buffer, bufOfs, n);
bufOfs += n;
ofs += n;
len -= n;
if (bufOfs >= blockSize) {
// compress completed block now
implCompress(buffer, 0);
bufOfs = 0;
}
}
// compress complete blocks
while (len >= blockSize) {
implCompress(b, ofs);
len -= blockSize;
ofs += blockSize;
}
// copy remainder to buffer
if (len > 0) {
System.arraycopy(b, ofs, buffer, 0, len);
bufOfs = len;
}
}
/**
* 执行数据填充以及完成哈希值计算
* @Return 返回哈希结果值
*/
// return the digest. See JCA doc.
protected final byte[] engineDigest() {
byte[] b = new byte[digestLength];
try {
engineDigest(b, 0, b.length);
} catch (DigestException e) {
throw (ProviderException)
new ProviderException("Internal error").initCause(e);
}
return b;
}
// return the digest in the specified array. See JCA doc.
protected final int engineDigest(byte[] out, int ofs, int len)
throws DigestException {
if (len < digestLength) {
throw new DigestException("Length must be at least "
+ digestLength + " for " + algorithm + "digests");
}
if ((ofs < 0) || (len < 0) || (ofs > out.length - len)) {
throw new DigestException("Buffer too short to store digest");
}
if (bytesProcessed < 0) {
engineReset();
}
// 这里正式开始调用MD5算法
implDigest(out, ofs);
bytesProcessed = -1;
return digestLength;
}
/**
* Return the digest. Subclasses do not need to reset() themselves,
* DigestBase calls implReset() when necessary.
*/
abstract void implDigest(byte[] out, int ofs);
// padding used for the MD5, and SHA-* message digests
static final byte[] padding;
static {
// we need 128 byte padding for SHA-384/512
// and an additional 8 bytes for the high 8 bytes of the 16
// byte bit counter in SHA-384/512
padding = new byte[136];
padding[0] = (byte)0x80;
}
}
最后查看MD5对象摘要计算过程
public final class MD5 extends DigestBase {
/**
* Perform the final computations, any buffered bytes are added
* to the digest, the count is added to the digest, and the resulting
* digest is stored.
*/
void implDigest(byte[] out, int ofs) {
// 经过MessageDigest.update(input)方法调用engineUpdate方法后,bytesProcessed值为待计算的字节数组(input中使用的字节数量)
// 已知1 byte = 8 bit,也就是2的3次幂,byte转bit时左移3位
long bitsProcessed = bytesProcessed << 3;
// 这块反应了好一会儿,其实就是将块大小512 bit转换成byte再求余
// 已知1 byte = 8 bit,也就是2的3次幂,bit转byte时右移3位
// 512 >>> 3 = 64 byte,按位与 -1 = 63 byte,转换成16进制为0x3f
int index = (int)bytesProcessed & 0x3f;
// 计算填充值长度
// 预处理时记录消息长度固定占用64 bit/8 byte,56=64-8,120=128-8
// 取余结果大于等于56 byte,加上固定的8 byte,超过了64 byte
int padLen = (index < 56) ? (56 - index) : (120 - index);
// 在待计算的字节数组中追加填充值
engineUpdate(padding, 0, padLen);
// 在待计算的字节数组中追加消息长度字节,固定占用64 bit
i2bLittle4((int)bitsProcessed, buffer, 56);
i2bLittle4((int)(bitsProcessed >>> 32), buffer, 60);
// 进行轮计算
implCompress(buffer, 0);
// 将计算结果放入out数组中
i2bLittle(state, 0, out, ofs, 16);
}
}