转载请注明出处
首先看下测试类
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); } }