Apache源码阅读笔记一:Content-MD5字段
通常在提供下载服务时,服务器都会预先提供一个MD5校验和,用户下载完文件以后,用MD5算法计算下载文件的MD5校验和,然后通过检查这两个校验和是否一致,就能判断下载的文件是否出错。而Content-MD5是HTTP协议中一个有类似功能的字段。
Content-MD5在RFC2616中的说明是用来提供实体主体(entity-body)的 MD5 摘要(digest),为的是提供 end-to-end 消息完整性检测(MIC,可以用来检测实体主体传输过程中的偶然性变动,但不一定能防范恶意攻击)。只有源服务器或客户端可生成 Content-MD5 头域;不得由代理和网关生成,否则会有悖于其作为端到端完整性检验的价值。
任何实体主体的接收者,包括代理和网关,都可以检查此头域里的摘要值与接收到的实体主体的摘要值是否相符。但是这个字段不能保证消息没有被篡改,所以不要将它作为一种安全手段,修改正文的人同样可以修改字段的内容。
在 Apache 中我们可以通过设置 ContentDigest On 打开 Content-MD5 的输出,详细说明猛击这里
那么,在 Apache 中是如何通过设置 ContentDigest 来开启 Content-MD5 字段的输出,此字段生成的算法是怎样的?
控制 Content-MD5 字段
前面我们有说过在配置文件中增加 ContentDigest On 可以打开 Apache 的 Content-MD5 的输出,在 Apache 内核中这个配置项是如何加载的?在生成内容时是根据哪些变量控制 Content-MD5 的输出?
我们知道 Apache 的模块中有一种叫预加载模块。这些模块是 Apache 运行非常重要的模块,我们今天所说的 Content-MD5 字段就包含这些预加载模块中的一个模块 core_module 中。虽然 Apache 针对不同的操作系统有不同的预加载模块列表,但是 core_module 都会作为第一个加载的模块放在列表的最前面。
Apache 在通过 ap_setup_prelinked_modules 加载完这些预加载的模块后,其运行的基本条件已经具备。在各种池初始化后, Apache 会进行配置文件解析,并针对配置文件中每一个有效项进行循环遍历,判断这些配置项与现有模块的指令(directive)是否匹配,如果匹配并且其参数设置为在读取配置时执行(EXEC_ON_READ,Content-MD5的此参数设置为OR_OPTIONS),则执行此字段的执行函数并将此项添加到配置项的指令集中。如果没有匹配,则直接将此节点添加到指令集中。
待所有的参数加载完后,Apache 内核会执行配置树(ap_process_config_tree)的所有执行函数。根据不同的指令参数,Apache 会调用每个指令的func(不同的参数使用不同的宏,虽然现在最终都是调用 func)。回到我们关注的内容,Content-MD5字段对应的是 set_content_md5 。此函数去掉验证输入,错误处理,就剩下一句:
d->content_md5 = arg != 0; |
这里的arg就是我们在配置文件中的 Off 和 On,Off的值为0, On的值为1。即当 ContentDigest On 时,d->content_md5的值为1。
生成Content-MD5字段的内容
前面有说提到 Content-MD5 字段最终是由 d->content_md5 控制。除此之外,此参数的输出还与输出过滤器相关,如果输出的过滤类型不是 AP_FTYPE_RESOURCE,则不会输出 Content-MD5 字段。
如果真输出 Content-MD5 字段,则 Apache 内核会调用 ap_md5digest(/server/util_md5.c文件) 函数。 Apache 实现的 MD5 算法与标准的算法步骤有一些出入。标准算法是按照如下5个步骤进行:
- Append Padding Bits: 信息计算前先要进行位补位
- Append Length
- Initialize MD Buffer: 用一个四个字的缓冲器(A,B,C,D)来计算报文摘要,A,B,C,D分别是32位的寄存器,初始化使用的是十六进制表示的数字。
- Process Message in 16-Word Blocks
- Output: 报文摘要的产生后的形式为:A,B,C,D。也就是低位字节A开始,高位字节D结束。
因为在大多数情况下我们都无法或很难提前计算出输入信息的长度。因此在具体实现时Append Padding Bits和Append Length这两步会放在后面,如下代码:
AP_DECLARE(char *) ap_md5digest(apr_pool_t *p, apr_file_t *infile) { apr_md5_ctx_t context; unsigned char buf[4096]; /* keep this a multiple of 64 */ apr_size_t nbytes; apr_off_t offset = 0L; apr_md5_init(&context); nbytes = sizeof(buf); while (apr_file_read(infile, buf, &nbytes) == APR_SUCCESS) { apr_md5_update(&context, buf, nbytes); nbytes = sizeof(buf); } apr_file_seek(infile, APR_SET, &offset); return ap_md5contextTo64(p, &context); } |
apr_md5_init函数执行标准算法的第三步,初始化MD缓存,而标准算法的第一步、第二步和第四步都在 apr_md5_update 中体现。最后一步输出对应 ap_md5contextTo64 。
关于MD5算法的详细算法在后续的文章中介绍。