Message Digest Algorithm-MD5 为计算机安全领域广泛使用的一种散列函数,用于确保信息传输完整一致,是计算机广泛使用的杂凑算法之一。利用 MD5 算法来进行文件校验的方案被大量应用于软件下载站、论坛数据库、系统文件安全等方面。
MD5 的典型应用是一致性验证,对一段信息(Message)产生信息摘要(Message-Digest),以防止信息被篡改。例如机密资料的检验,下载文件的检验,明文密码的加密等。
本文介绍一款基于 Perl 实现的批量文件的 MD5 自动化校验工具,支持不同文件夹下的批量文件的 MD5 值自动化校验,并返回校验结果。
适用范围
此工具可以应用于软件正式发布前后文件的完整一致性验证和批量文件的 MD5 值读取。
实际案例分析
IBM CSTL DSA(Dynamic System Analysis)项目组的每个软件发布周期内,在软件正式版本发布到 IBM 官网之前,DSA 测试人员需进行软件的完整一致性验证。
初始人工方法验证步骤:
1)使用 MD5 读写工具逐条手动输入原始文件路径,并手动输入读取结果至本地文档。
2)使用该读写工具逐条手动输入待比较文件路径,并手动输入读取结果至本地文档。
3)人工对比原始文件和待比较文件 MD5 值,并手动记录分析结果。
缺点:步骤多,效率低,且准确度难以保证,易受软件测试人员影响。
分析人工方法步骤的缺点,采用 Perl Digest::MD5 模块,将初始方法中需人工完成的部分自动化,减少操作步骤,降低人为因素影响,从而提高效率并保证测试准确度。
采用自动化方法验证步骤:
双击工具可执行文件;
根据提示输入原始文件路径及待比较文件路径;
工具自动判定输入文件有效性,读取有效文件 MD5 值并整理归档至本地文档;
屏幕输出自动判定分析结果;
优点:操作简单,效率高,准确度高,自动化执行并分析结果,不受测试人员影响。
两种方法对比结论:采用脚本语言或模块完成当前工作中繁琐的操作和数据分析,提高工作效率。
当前版本局限性
1)Windows 环境需预安装 Perl,Linux 环境可直接执行脚本文件。
2)此工具当前版本最大支持 4 组(5 次用户输入)不同文件夹下 MD5 值自动化校验;
3)此工具当前版本不支持文件名检测匹配,仅基于文件的唯一 MD5 值检测匹配。
使用 Perl 模块介绍
此工具采用了 Perl 的 Digest::MD5 模块,该模块可以从 CPAN 网站上直接下载。详细文档可参考 Digest::MD5 详细介绍(CPAN)。
此处介绍一种比较方便的模块安装方法:
清单 1. 模块安装
ppm install Digest::MD5
代码解读
1, 工具执行第一步提示用户输入待读取和比较的源文件夹所在的路径并做以下判定:
a,若输入源文件夹路径不存在或文件夹为空,提示用户输入无效并继续输入;
b,若连续 3 次输入无效,提示用户输入无效并自动退出;
c,若工具判定输入源文件夹有效,提示用户输入待比较文件夹路径。
清单 2. 源文件夹输入提示并判定:
sub INPUT_FOLDER { print "Please input the original binary folder path!\n"; chomp( $folder_path = <STDIN> ); if ( -e $folder_path ) { #输入文件夹路径存在性判定 my @dir_files = <$folder_path/*>; if (@dir_files) { #输入文件夹非空性判定 push @ori_folder, $folder_path; opendir DH, $folder_path || die "Can't open @ori_folder!"; @ori_file = readdir DH; splice( @ori_file, 0, 2 ); @ori_file = sort @ori_file; &BANNER; &INPUT_ANOTHER_FOLDER; }
2, 工具执行第二步提示用户输入待比较文件夹路径(最大支持 4 次输入)并做以下判定:
a, 若输入文件夹不存在,忽略此次输入并返回判定结果;
b, 若输入文件夹为空,忽略此次输入并返回判定结果;
c, 若输入文件夹文件数目不等于源文件夹内文件数目,忽略此输入并返回判定结果;
d, 若输入文件夹存在且文件数目等于源文件夹内数目文件,执行 MD5 读取对比代码段。
清单 3.待比较文件夹路径输入提示:
sub INPUT_ANOTHER_FOLDER { $h++; print "Press Q to quit the tool!\n"; print "Press C to end input and start to compare MD5 value!\n"; print "Please input another folder path compare with the original folder:\n"; while (<STDIN>) { chomp $_; if (/^q$/i) { exit; } elsif (/^c$/i) { if ( $h == 2 ) { &BANNER; print "There should be at least 2 folders to be compared!\n"; $h--; &INPUT_ANOTHER_FOLDER; } else { &BANNER; &FOLDER_ESTIMATE; } } else { push @com_folder, $_; if ( $h == 5 ) { #程序设定最大只能同时比较 5 个路径下面的文件 &BANNER; print "You have entered 5 folders to be compared!\n"; &FOLDER_ESTIMATE; } else { &BANNER; &INPUT_ANOTHER_FOLDER; } } } }
清单 4.待比较文件夹路径判定:
#待比较文件夹存在性判定 foreach (@com_folder) { if ( -e $_ ) { push @folder_exist, $_; } else { push @folder_not_exist, $_; } } #待比较文件夹非空性判定 foreach (@folder_exist) { my @dir_files = <$_/*>; if (@dir_files) { push @folder_not_empty, $_; } else { push @folder_empty, $_; } } #待比较文件夹内文件数目判定 foreach (@folder_not_empty) { opendir DH, $_ || die "Can't open @folder_not_empty!"; @com_file = readdir DH; splice( @com_file, 0, 2 ); @com_file = sort @com_file; my $file_count = @com_file; if ( $file_count == @ori_file ) { push @folder_equal, $_; } else { push @folder_not_equal, $_; } }
3,工具执行第三步逐一遍历读取并存储用户输入文件夹内所有文件的 MD5 值。
清单 5.遍历文件夹,读取并存储所有文件的 MD5 值:
sub MD5_COM { print "Start to read and compare the input folder binary MD5 value, Please wait...\n\n"; #批量读取初始文件夹内所有文件 MD5 值 foreach (@ori_file) { my $file = "$folder_path\\$_"; open( FILE, $file ) || die "Can't open '$file':$!"; binmode(FILE); my $md5 = Digest::MD5->new; map { $md5->add($_) } <FILE>; close(FILE); push @ori_array, "$file\n", $md5->hexdigest . "\n"; } #将 MD5 值及发生的错误信息打印输出到日志文件 open RESULT, ">MD5.log"; print RESULT "@folder_not_exist are not exist!\n" if @folder_not_exist; print RESULT "@folder_empty have no files!\n" if @folder_empty; print RESULT "@folder_not_equal have the wrong file numbers!\n" if @folder_not_equal; print RESULT "Original folder:\n", @ori_array, "$line\n";
4, 工具执行第四步终端显示校验统计结果并存储至日志文件。
清单 6.校验结果统计:
sub Com_folder1 { #读取第一个待比较文件夹内所有文件 MD5 值 opendir DH, $folder_equal[0] || die "Can't open @folder_equal!"; @com_file = readdir DH; splice( @com_file, 0, 2 ); @com_file = sort @com_file; foreach (@com_file) { my $file = "$folder_equal[0]\\$_"; open( FILE, $file ) || die "Can't open '$file':$!"; binmode(FILE); my $md5 = Digest::MD5->new; map { $md5->add($_) } <FILE>; close(FILE); push @com_array1, "$file\n", $md5->hexdigest . "\n"; } #屏幕输出 MD5 值比对结果并保存到日志文件 my $pass_count = 0; my $fail_count = 0; for ( my $n = 0 ; $n < $#ori_array ; $n += 2 ) { if ( $ori_array[ $n + 1 ] eq $com_array1[ $n + 1 ] ) { $pass_count++; } else { $fail_count++; } } my $total = $pass_count + $fail_count; print "\nCompare the MD5 value of:\n\n"; print "$folder_equal[0]\t$folder_path\t"; print "PASS $pass_count\tFAIL $fail_count\tTotal $total\n\n"; }