C#:生成哈希字符串

目录

介绍

背景

哈希助手类

考虑

对象到哈希字符串的过程

多个对象组合的哈希

我们将更频繁地使用的方法

整个对象的哈希

重要的是记住

数据值散列

哈希结果

其他测试

奖励:字符串哈希

结论


介绍

散列是将值转换为通常较短的固定长度的键/值,其表示原始值。几天前,我们不得不使用哈希比较来通过API在两个系统之间同步数据(显然,这不是使用API​​进行数据同步的最有效方式,但我们没有选择在源端添加任何更改)

背景

我们在做什么:

  1. 在对象JSON反序列化之后在我们的末尾创建一个哈希字符串
  2. 通过唯一标识符(主键)将该哈希字符串与现有数据库行进行比较
    1. 如果唯一标识符(主键)找不到行,则向DB添加新行
    2. 如果哈希字符串不相同,则使用新值更新现有行
  3. 还有其他几个同步日志进程

一切都按预期工作,直到我们重构现有代码(更改了一些模型和属性的名称)。哈希字符串是从整个对象(包括所有值)生成的,而不是考虑特定的属性。我们创建哈希字符串的方式实际上是错误的。我们来看几个哈希字符串示例。

哈希助手类

这是用于管理与哈希相关的操作的实用程序类。

using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security.Cryptography;
using System.Text;

public class HashHelper
{
    /// <summary>
    /// for custom class need [Serializable]
    /// to ignore https://stackoverflow.com/questions/33489930/
    /// ignore-non-serialized-property-in-binaryformatter-serialization
    /// </summary>
    /// <param name="value"></param>
    /// <returns></returns>
    public byte[] Byte(object value)
    {
        /*https://stackoverflow.com/questions/1446547/
          how-to-convert-an-object-to-a-byte-array-in-c-sharp*/
        using (var ms = new MemoryStream())
        {
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, value == null ? "null" : value);
            return ms.ToArray();
        }
    }
    
    public byte[] Hash(byte[] value)
    {
        /*https://support.microsoft.com/en-za/help/307020/
          how-to-compute-and-compare-hash-values-by-using-visual-cs*/
        /*https://andrewlock.net/why-is-string-gethashcode-
          different-each-time-i-run-my-program-in-net-core*/
        byte[] result = MD5.Create().ComputeHash(value);
        return result;
    }

    public byte[] Combine(params byte[][] values)
    {
        /*https://stackoverflow.com/questions/415291/
          best-way-to-combine-two-or-more-byte-arrays-in-c-sharp*/
        byte[] rv = new byte[values.Sum(a => a.Length)];
        int offset = 0;
        foreach (byte[] array in values)
        {
            System.Buffer.BlockCopy(array, 0, rv, offset, array.Length);
            offset += array.Length;
        }
        return rv;
    }

    public string String(byte[] hash)
    {
        /*https://stackoverflow.com/questions/1300890/
          md5-hash-with-salt-for-keeping-password-in-db-in-c-sharp*/
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < hash.Length; i++)
        {
            sb.Append(hash[i].ToString("x2"));     /*do not make it X2*/
        }
        var result = sb.ToString();
        return result;
    }

    public byte[] Hash(params object[] values)
    {
        byte[][] bytes = new byte[values.Length][];
        for(int i=0; i < values.Length; i++)
        {
            bytes[i] = Byte(values[i]);
        }
        byte[] combined = Combine(bytes);
        byte[] combinedHash = Hash(combined);
        return combinedHash;
    }

    /*https://stackoverflow.com/questions/5868438/c-sharp-generate-a-random-md5-hash*/
    public string HashString(string value, Encoding encoding = null)
    {
        if (encoding == null)
        {
            encoding = Encoding.ASCII;
        }
        byte[] bytes = encoding.GetBytes(value);
        byte[] hash = Hash(bytes);
        string result = String(hash);
        return result;
    }

    public string HashString(params object[] values)
    {
        var hash = Hash(values);    /*Add more not constant properties as needed*/
        var value = String(hash);
        return value;
    }
}

考虑

  • 使用MD5哈希Hash(byte[] value)
  • 任何null 值都被视为'null'字符串Byte(object value)

对象到哈希字符串的过程

  1. 创建该对象的字节 Byte(object value)
  2. 从对象字节Hash(byte[] value)创建哈希字节 
  3. 散列字节String(byte[] hash)中返回的字符串 

多个对象组合的哈希

  1. 创建每个对象的字节 Byte(object value)
  2. 将字节组合或求和 Combine(params byte[][] values)
  3. 从组合创建哈希字节或对字节求和 Hash(byte[] value)
  4. 散列字节String(byte[] hash)中返回的字符串 

或者:

  1. 创建组合哈希字节 Hash(params object[] values)
  2. 散列字节String(byte[] hash)中返回的字符串 

我们将更频繁地使用的方法

  • 创建任何字符串的哈希字符串 HashString(string value, Encoding encoding = null)
  • 创建任何/对象组的哈希/组合哈希字符串 HashString(params object[] values)

整个对象的哈希

数据类或模型:

[Serializable]
class PeopleModel
{
    public long? Id { get; set; }
    public string Name { get; set; }
    public bool? IsActive { get; set; }
    public DateTime? CreatedDateTime { get; set; }
}

创建模型的哈希:

/*9105d073ad276d742c56a049abd4ddef
 * will change if we change 
 *      1. class name
 *      2. property name
 *      3. property data type
 *      4. add/remove new property etc
 */
var peopleModelHashString = hashHelper.HashString(new PeopleModel()
{
    Id = 1,
    Name = "Anders Hejlsberg",
    IsActive = true,
    CreatedDateTime = new DateTime(1960, 12, 2)
});

重要的是记住

此哈希取决于对象结构和分配的值。即使我们为属性分配相同的值,生成的哈希也不会相同,但添加了一些更改,如:

  • /模型名称更改
  • 属性名称更改
  • 命名空间名称更改
  • 属性编号更改(添加或删除任何属性)

到模型。而在开发环境中,重构可能发生的任何时间。

数据值散列

让我们只使用值进行哈希。创建一个接口IHash

public interface IHash
{
    string HashString();
}

使用IHash到模型并在方法HashString()内部使用哈希助手。

class People : IHash
{
    public long? Id { get; set; }         /*unique identifier, avoid it to use 
                                            in hash calculation*/
    public string Name { get; set; }
    public bool? IsActive { get; set; }
    public DateTime? CreatedDateTime { get; set; }

    public string HashString()
    {
        var value = new HashHelper().HashString
        (Name, IsActive, CreatedDateTime);    /*Add more not constant properties as needed*/
        return value;
    }
}

这样,模型结构不参与哈希生成过程中,只有特定的属性值(NameIsActiveCreatedDateTime)正被考虑。

Hash将保持不变,直到没有为这些属性设置任何新值。模型的任何结构更改(名称更改,属性添加/删除等)都不会影响哈希字符串。

哈希结果

/*constant: 3953fbec5b81ccca72c98655c0c4b069*/
people = new People()
{
    Id = 1,
    Name = "Dennis Ritchie",
    IsActive = false,
    CreatedDateTime = new DateTime(1941, 9, 9)
};
hashString = people.HashString();

其他测试

使用null 对象值正常工作:

string hashString;
/*constant: 47ccecfc14f9ed9eff5de591b8614077*/
var people = new People();
hashString = people.HashString();

我们将无法创建整个People类,因为它不使用[Serializable]

var hashHelper = new HashHelper(); 
/*throws error as [Serializable] not been used*/ 
//var peopleHashString = hashHelper.HashString(people);

奖励:字符串哈希

创建密码/字符串哈希是很常见的。所以我们拥有它。

/*constant: e6fb7af54c39f39507c28a86ad98a1fd*/
string name = "Dipon Roy";
string value = new HashHelper().HashString(name);

结论

  • 如果我们只需要比较考虑值或特定值,那么使用数据值哈希是最佳选择。
  • 但是如果我们需要完全比较对象结构和值,请选择整个对象的哈希

 

原文地址:https://www.codeproject.com/Tips/5130239/Csharp-Generating-Hash-String

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值