为了账号安全,请及时绑定邮箱和手机立即绑定

java.security.MessageDigest实现MD5摘要算法预处理源代码解析

标签:
Java 算法

转载请注明出处

首先看下测试类

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);
    }

}
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消