校验和验证
循环遍历文件夹/子文件夹中的所有文件,并运行存储在数据库中的校验和。 通过电子邮件发送差异和结果。
引言
我们越来越看到存储、存档和保存数字资产的重要性,可能需要几十年甚至几个世纪。数字资产管理系统主要侧重于摄取和用户访问,但资产本身呢?人们花费大量时间创建视频、图像和其他媒体。因此,我们应该定期验证数字资产的数据完整性并在出现差异时发出警报,这是有道理的。下面的程序使用 SHA-256 校验和来执行此操作。这是一个为 .NET 4.0 编写的控制台应用程序。
背景
必备组件
- MySQL 服务器(和 GUI 工具)* 可在 www.mysql.com 上获得
- MySQL Connector/NET
- .NET 4.0/Visual Studio 2010。
- 用于通过 SMTP 验证和发送电子邮件的 Gmail 帐户或其他帐户。
*数据库平台是 MySQL,但它可以轻松地与 SQL Server 一起使用。我喜欢 MySQL Connector/NET 的一点是,使用数据库的语法几乎与 MS SQL Server 完全相同(即,SqlCommand
变为 MySqlCommand
)。
用例
核心用例是检查文件夹/子文件夹中的所有文件,并验证文件的校验和值是否已更改。 如果已更改,则应通过电子邮件通知技术人员或企业所有者。 下面的程序执行此操作; 它还报告文件总数、未更改的文件数和新文件数。
该程序将摘要报告写入电子邮件正文,并附加两个包含详细信息的电子表格 (.csv)。
程序的某些部分可以通过 app.config 文件进行配置,即
- 要验证的文件夹/子文件夹
- 电子邮件发送地址
- 电子邮件发件人地址
- 文件附件的名称和位置
- 日志文件的名称和位置
该示例使用的文件夹结构如下所示
使用代码
首先我们需要一个数据库。 附加的文件名为 DRMC.sql,可以将其还原到 MySQL,或者可以在 Notepad++ 中打开该文件以查看 1 个表、1 个视图和 5 个存储过程。 该程序假定数据库名为“drmc”。
整个程序都在下载文件中。 以下是一些有用的区域需要指出
通过存储过程连接和使用 MySQL 数据库并非总是那么简单。 存储过程包含一些 OUT
参数,以便获得总计数。
下面的代码演示了如何枚举文件夹和子文件夹中的所有文件,运行校验和并将结果插入到表中
// Connect to database
string m_conn =
ConfigurationManager.ConnectionStrings["MySqlConnectionString"].ConnectionString;
MySqlConnection conn = new MySqlConnection(m_conn);
conn.Open();
try
{
// Get the checksum and path for all files in directory.
// Use stored proc to insert data. Log results and errors.
MySqlCommand cmd = new MySqlCommand("drmc.proc_checksum", conn);
cmd.CommandType = CommandType.StoredProcedure;
string[] m_files = Directory.GetFiles(m_path, "*.*",
SearchOption.AllDirectories);
foreach (string m_file in m_files)
{
m_filename = Path.GetFileName(m_file);
cmd.Parameters.Clear();
cmd.Parameters.Add(new MySqlParameter(@"M_FILEPATH",
MySqlDbType.VarChar) { Value = m_file.Replace("\\", "\\\\") });
cmd.Parameters.Add(new MySqlParameter(@"M_FILENAME",
MySqlDbType.VarChar) { Value = m_filename });
cmd.Parameters.Add(new MySqlParameter(@"M_SHA256",
MySqlDbType.VarChar) { Value = GetChecksum(m_file).ToString() });
cmd.ExecuteNonQuery();
using (StreamWriter sw = File.AppendText(m_results))
{
Logger.LogMessage("File " + m_file +
" inserted in database.", sw);
sw.Close();
}
}
...
了解如何使用 MySqlDataAdapter
以及如何从 ExecuteNonQuery
获取 OUT
参数也很有用。 下面的代码片段演示了如何执行此操作。
// Get number and listing of new files
MySqlCommand cmd_newfiles = new MySqlCommand("drmc.proc_newfiles", conn);
cmd_newfiles.CommandType = CommandType.StoredProcedure;
cmd_newfiles.Parameters.AddWithValue("@M_NEWCOUNT", MySqlDbType.Int32);
cmd_newfiles.Parameters["@M_NEWCOUNT"].Direction = ParameterDirection.Output;
cmd_newfiles.ExecuteNonQuery();
Console.WriteLine("New files: " + cmd_newfiles.Parameters["@M_NEWCOUNT"].Value);
Console.WriteLine("\r\n");
string str_newfiles = cmd_newfiles.Parameters["@M_NEWCOUNT"].Value.ToString();
MySqlDataAdapter sda_newfiles = new MySqlDataAdapter(cmd_newfiles);
DataSet ds_newfiles = new DataSet();
ds_newfiles.DataSetName = "New Files";
sda_newfiles.Fill(ds_newfiles);
sda_newfiles.Dispose();
最后,我们开始使用结果构建电子邮件并发送它,包括使用 FileGenerator
类创建的两个附件。
// Begin creating email content and attachments
DataTable dt_newfiles = ds_newfiles.Tables[0];
DataTable dt_changedfiles = ds_changedfiles.Tables[0];
DataTable dt_changedfiles1 = ds_changedfiles.Tables[1];
FileGenerator.CreateFile(dt_changedfiles, m_changedfiles).ToString();
string m_emailbody = "This e-mail is a summary of checksum file integrity " +
"for files located here: \r\n\r\n" + m_path + "\r\n\r\n";
m_emailbody = m_emailbody + "There are a total of " +
str_totalfiles + " files. \r\n\r\n";
m_emailbody = m_emailbody +
"The file location, file name, and checksum are the same for " +
str_samefiles + " files. \r\n\r\n";
m_emailbody = m_emailbody + "There are " + str_newfiles +
" new files. These are listed below, if any. " +
"Detailed information is in the attached " +
"file checksum_new_files.csv\r\n\r\n";
m_emailbody = m_emailbody + "There are " + str_changedfiles +
" files where the CHECKSUM HAS BEEN CHANGED. " +
"The integrity of the file is in doubt, " +
"or it has been changed by a user. " +
"The files are listed below, if any. Detailed information " +
"is in the attached file checksum_changed_files.csv\r\n\r\n";
string m_emailnewbody = "New Files: \r\n" +
FileGenerator.CreateFile(dt_newfiles, m_newfiles).ToString();
string m_emailchangedbody = "Changed Files: \r\n" +
FileGenerator.CreateFile(dt_changedfiles1).ToString();
m_emailbody = m_emailbody + m_emailnewbody + "\r\n" + m_emailchangedbody;
#endregion
#region Email configuration and send
var client = new SmtpClient("smtp.gmail.com", 587)
{
Credentials = new NetworkCredential("gmailuserhere",
"gmailpasswordhere"),
EnableSsl = true
};
MailMessage m_message = new MailMessage(
m_notification_to,
m_notification_from,
m_notification_title + DateTime.Today.ToShortDateString(),
m_emailbody);
//...see downloads for code on creating full attchments
m_message.Attachments.Add(m_new_attachment);
m_message.Attachments.Add(m_changed_attachment);
client.Send(m_message);
代码中的实际工作由下面的函数执行。 这段代码几乎逐字来自 Jeff Barnes, MS MVP, 和他的博客文章: http://jeffbarnes.net/blog/post/2007/01/12/File-Checksum-using-NET.aspx。
private static string GetChecksum(string m_fileinput)
{
try
{
string m_checksum;
using (FileStream stream = File.OpenRead(m_fileinput))
{
SHA256Managed sha = new SHA256Managed();
byte[] checksum = sha.ComputeHash(stream);
m_checksum =
BitConverter.ToString(checksum).Replace("-", String.Empty);
}
return m_checksum;
}
catch (Exception ex)
{
using (StreamWriter swerr = File.AppendText(m_errors))
{
Logger.LogMessage(ex.Message.ToString(), swerr);
swerr.Close();
}
return "unable to retrieve checksum";
}
}
您可能遇到的 SHA-256 问题之一是,由于其扩展的算法,创建校验和所需的时间比 MD5 长。 MD5 将在不到一半的时间内运行,如果您有 TB 级的数据需要扫描并且您更关心校验和值而不是加密,这一点非常重要。 下面的代码片段演示了如何改用 MD5
MD5 m_md5 = new MD5CryptoServiceProvider();
//SHA256Managed sha = new SHA256Managed();
byte[] checksum = m_md5.ComputeHash(stream);
m_checksum = BitConverter.ToString(checksum).Replace("-", String.Empty);
历史
- 2011 年 9 月 23 日 - 添加了 MD5 注释,以便更快地扫描文件。