将图像存储为哈希码C#

时间:2015-03-08 00:45:12

标签: c# .net image hash

我正在建立一个存储数百万张图片的网站,因此每张图片都需要一个唯一的ID。什么密码术最适合存储图像。现在这是我的代码看起来像使用SHA1。

是否在sha1旁边使用了标准哈希,两个图像是否可能具有相同的哈希码?

 Image img = Image.FromFile("image.jpg");

 ImageConverter converter = new ImageConverter();
 byte[] byteArray = (byte[])converter.ConvertTo(img, typeof(byte[]));

 string hash;

 using (SHA1CryptoServiceProvidersha1 = new SHA1CryptoServiceProvider())
 {
     hash = Convert.ToBase64String(sha1.ComputeHash(byteArray));
 }

3 个答案:

答案 0 :(得分:5)

如果我理解正确,您希望将SHA1值指定为文件名,以便检测您的集合中是否已有该图像。我不认为这是最好的方法(如果你没有运行数据库那么可能就是这样)但是,如果你计划拥有数百万的图像,那么(出于实际原因)只是认为碰撞不可能发生。

为此目的,我不推荐使用SHA256,因为主要的两个优点(抗冲击性+对某些理论攻击的免疫力)并不值得,因为它比SHA1慢了大约10倍(并且你会很多哈希相当大的文件)。

你不应该害怕它的128位长度:为了有机会找到128位的碰撞,你需要在你的集合中拥有18446744073709600000个图像(sqrt为2 ^ 128)。

哦,我不想让自己听起来很自负,但是哈希和密码学是完全不同的东西。事实上,我认为散列更接近代码签名/数字签名而不是加密。

答案 1 :(得分:3)

您可以使用这两种机制。

  1. 使用GUID作为唯一文件标识符(文件系统,数据库等)
  2. 计算并存储图像上的SHA1或MD5哈希,并使用它来检查重复项。
  3. 因此,在上传图片时,您可以使用哈希来检查可能重复。但是,如果找到一个,那么您可以进行更确定的检查(即检查文件的字节)。实际上,如果文件不相同,你可能永远不会得到一个哈希匹配,但第二次检查肯定会确定。

    然后,一旦确定了唯一性,请使用文件标识符的GUID或重用现有文件。

答案 2 :(得分:0)

两个不同的图像可以具有相同的哈希码吗?不太可能。另一方面,同一张图像的两个副本是否可以具有不同的哈希值?绝对。

获取无损png,将其打开,然后将其重新保存为未压缩状态。两张图片的像素将相同,但文件哈希将不同。

除了像素之外,您的图像还将包含元数据字段,例如地理位置,日期/时间,相机制造商,相机型号,ISO速度,焦距等。

因此,当完整使用图像文件时,您的哈希将受到压缩类型和元数据的影响。

这里的主要问题是:是什么使图片对您“独特”?

例如,如果图像已经上传,那么我将其下载并擦除相机型号或注释并重新上传,这对您来说将是另一幅图像,还是与原始图像相同?位置字段如何?

如果我下载无损png并将其另存为具有相同像素数据的无损tiff,该怎么办?

根据您的要求和哪些字段很重要,您需要创建相关元数据字段(如果有)+图像的实际未压缩像素数据的组合的哈希,而不是使用图像进行哈希整个文件。

System.Security.Cryptography中提供的标准哈希算法中,您可能会发现MD5最适合此应用程序。但是一定要与其他方法一起玩,看看哪种方法最适合您。

这是一个代码示例,可为您提供元数据字段和图像像素组合的哈希值:

public class ImageHash
{
    public string GetHash(string filePath)
    {
        using (var image = (Bitmap) Image.FromFile(filePath))
            return GetHash(image);
    }

    public string GetHash(Bitmap bitmap)
    {
        var formatter = new BinaryFormatter();

        using (var memoryStream = new MemoryStream())
        {
            var metafields = GetMetaFields(bitmap).ToArray();

            if(metafields.Any())
                formatter.Serialize(memoryStream, metafields);

            var pixelBytes = GetPixelBytes(bitmap);
            memoryStream.Write(pixelBytes, 0, pixelBytes.Length);

            using (var hashAlgorithm = GetHashAlgorithm())
            {
                memoryStream.Seek(0, SeekOrigin.Begin);
                var hash = hashAlgorithm.ComputeHash(memoryStream);
                return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
            }
        }
    }

    private static HashAlgorithm GetHashAlgorithm() => MD5.Create();

    private static byte[] GetPixelBytes(Bitmap bitmap, PixelFormat pixelFormat = PixelFormat.Format32bppRgb)
    {
        var lockedBits = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadOnly, pixelFormat);

        var bufferSize = lockedBits.Height * lockedBits.Stride;
        var buffer = new byte[bufferSize];
        Marshal.Copy(lockedBits.Scan0, buffer, 0, bufferSize);

        bitmap.UnlockBits(lockedBits);

        return buffer;
    }

    private static IEnumerable<KeyValuePair<string,string>> GetMetaFields(Image image)
    {
        string manufacturer = System.Text.Encoding.ASCII.GetString(image.PropertyItems[1].Value);

        yield return new KeyValuePair<string, string>("manufacturer", manufacturer);
        
        // return any other fields you may be interested in
    }
}

显然,您可以将其用作:

var hash = new ImageHash().GetHash(@"some file path");

尽管方法不错,但该方法可以改进一些方面,例如:

  1. 调整大小后的同一张图像怎么样?如果那不能使它变成不同的图片(例如,如果需要调整图像大小),则需要在散列之前先将输入图像的大小调整为预定大小。

  2. 环境光的变化如何?这会使它与众不同吗?如果答案是否定的,那么您也需要将其生效,并且使算法在面对亮度变化等方面也很健壮,无论图像的亮度如何变化,仍然会导致相同的哈希值。

  3. 几何变换如何?例如,如果我在重新上传之前旋转或镜像了图像,它是否仍与原始图像相同?如果是这样,该算法将需要足够的智能,以在这些类型的转换之后产生相同的哈希值。

  4. 您想如何处理在图像上添加边框的情况?在图像处理领域中有许多这样的场景。其中一些具有相当标准的解决方案,而许多其他解决方案仍在积极研究中。

  5. 性能:此当前代码可能会证明浪费时间和资源,具体取决于图像的数量和大小以及您可以花多少时间花在每个图像的哈希上。如果您需要它运行得更快和/或消耗更少的内存,则可能需要在获取哈希之前将图像缩小到预定大小。