将SHA1转换为使用少于40个字符的Ascii的更好算法?

时间:2013-05-05 05:04:50

标签: c# algorithm ascii sha1

160位SHA1的所有转换使用40个ascii字符(320位)来表示160位数据(我能够找到)。我需要优化它并使用尽可能少的ascii字符来表示SHA1哈希。

例如这个字符串"快速的棕色狐狸跳过懒狗"等于ASCII和#34; 2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12"当通过典型算法转换时。

我创建了一个算法,每个ASCII字符使用5位,所以我需要40个ASCII字符到32" F0K1032QD08C1M44U11B0R77P3R31L2I"。

有没有人有更好的方法来获得更少的字符,但不会丢失信息(通过有损压缩技术或使用像MD5这样的小哈希)? 我需要将此哈希表示为Windows上的文件夹,因此使用大写和小写每个字符可以使用6位。

class Program
{
    static byte[] GetBytesForTypical(byte[] hash)
    {
        List<byte> newHash = new List<byte>();

        foreach (byte b in hash)
        {
            int first4Bits = (b & 0xF0) >> 4;
            int last4bits = b & 0x0F;

            newHash.Add((byte)first4Bits);
            newHash.Add((byte)last4bits);
        }

        return newHash.ToArray();
    }

    public static string ConvertHashToFileSystemFriendlyStringTypical(byte[] str)
    {
        StringBuilder strToConvert = new StringBuilder();

        foreach (byte b in str)
        {
            strToConvert.Append(b.ToString("X"));
        }

        return strToConvert.ToString();
    }

    static byte[] GetBytesForCompressedAttempt(byte[] hash)
    {
        byte[] newHash = new byte[32];

        // the bit array 5 bits at a time
        // at 8 bits per bytes that is 40 bits per loop 4 times
        int byteCounter =0;
        int k = 0;
        for(int i=0; i < 4 ;++i)
        {
            //Get 5 bits worth
            newHash[k] = (byte)(hash[byteCounter] & 0x1F);
            hash[byteCounter] >>= 5;
            ++k;

            //Get 3 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x7);
            newHash[k] <<= 2;
            ++byteCounter;

            // get 2 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x3);
            ++k;

            // get 5 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x1F);
            hash[byteCounter] >>= 5;
            ++k;

            // get 1 bit
            newHash[k] = (byte)(hash[byteCounter] & 0x1);
            newHash[k] <<= 7;
            ++byteCounter;

            // get 4 bits
            newHash[k] = (byte)(hash[byteCounter] & 0xF);
            ++k;
            hash[byteCounter] >>= 4;

            // get 4 bits
            newHash[k] = (byte)(hash[byteCounter] & 0xF);
            ++byteCounter;

            // get 1 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x1);
            hash[byteCounter] >>=1;
            ++k;

            // get 5 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x1F);
            ++k;
            hash[byteCounter] >>= 5;

            // get 2 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x3);
            ++byteCounter;

            // get 3 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x7);
            ++k;

            // get 5 bits
            newHash[k] = (byte)(hash[byteCounter] & 0x1F);
            ++byteCounter;
            ++k;

        }

        return newHash;
    }

    public static string ConvertHashToFileSystemFriendlyStringCompressedl(byte[] str)
    {
        StringBuilder strToConvert = new StringBuilder();

        foreach (byte b in str)
        {
            System.Diagnostics.Debug.Assert(b < 32);

            if (b >= 10 && b < 32)
            {
                strToConvert.Append((char)(b - 10 + 'A'));
            }
            else
            {
                strToConvert.Append((char)(b + '0'));
            }
        }

        return strToConvert.ToString();
    }

    static void Main(string[] args)
    {
        System.Security.Cryptography.SHA1 hasher = System.Security.Cryptography.SHA1.Create();

        byte[] data = hasher.ComputeHash(Encoding.Default.GetBytes("The quick brown fox jumps over the lazy dog"));
        byte[] stringBytesTypical = GetBytesForTypical(data);
        string typicalFriendlyHashString = ConvertHashToFileSystemFriendlyStringTypical(stringBytesTypical);
        //2FD4E1C67A2D28FCED849EE1BB76E7391B93EB12 == typicalFriendlyHashString

        byte[] stringBytesCompressedAttempt = GetBytesForCompressedAttempt(data);
        string compressedFriendlyHashString = ConvertHashToFileSystemFriendlyStringCompressedl(stringBytesCompressedAttempt);
        //F0K1032QD08C1M44U11B0R77P3R31L2I == compressedFriendlyHashString

    }
}

编辑: 减少到少于40个字符的需要与Windows文件夹名称无关。 (虽然它可能因为Windows路径有限制)。我需要为人类可读的字符串保留尽可能多的空间,然后为需要查看的任何内容创建一个文件夹。 40个字符的ascii字符串的问题是1/2的位被设置为0并且实质上是浪费的。因此,当存储数百万个哈希空间并且查找速度开始变得交织在一起时。我无法重新设计用户工作流程,但我可以使系统更加活泼,消耗更少的内存

编辑: 这也将改善用户体验。目前,用户必须使用部分哈希来查找内容。更糟糕的情况(在实践中)当前需要使用散列中的前8个字符来通常确保没有重复。这8个字符代表32位真实散列数据。每个字符下降到5位用户只需要6个字符,以确保没有重复。如果我能得到6位,那么用户只需要大约5个字符。这进入了大多数人能够记忆的领域

编辑:我从上面提到的原始代码中取得了一些进展。一旦我将散列转换为hexatridecimal(基数36),我就能够从上面的原始5位实现中删除其中一个字符。所以我目前有31个字符。这意味着从典型的实现中需要8个字符进行检索(实际上),用户应该能够使用6个字符来检索相同的数据。

public static string ConvertHashToFileSystemFriendlyStringCompressed2(byte[] hashData)
        {
            string mapping = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

            BigInteger base10 = new BigInteger(hashData);
            string base36;
            var result = new Stack<char>();

            do
            {
                result.Push(mapping[(int)(base10 % 36)]);
                base10 /= 36;

            } while (base10 != 0);

            base36 = new string(result.ToArray());

            return base36;
        }
编辑:我正在做更多的研究,我想要发布一个图表,显示当你增加你必须选择的ASCII字符数时,你得到的收益递减。你需要越来越多的角色来获得越来越小的收益。我似乎处于你最大的收益(36个字符)的尾端。因此,即使我能够跳转到使用64个字符(我目前无法使用),我只删除了4个最终字符串。但是,如果将原始哈希减少到18个字节,那么相同的36个字符现在只创建一个27个字符的字符串(与转换为base 64相同的长度)。现在的问题是如何可靠地将20字节的哈希值压缩为18个字节。截断不会工作,因为如果我使用截断,用户仍然需要记住6个字符。由于SHA1哈希是随机字节,我不确定我可以无损压缩2个字节(节省10%的空间)。

enter image description here

enter image description here

编辑:所以我压缩哈希字节的尝试没有成功。我期待这一点,但我必须尝试以证明这一点。基本上我所做的是尝试使用Huffman Code来压缩原始哈希。

由于散列中的每个值同样可能(良好散列的定义)使用共同的Huffman树进行所有压缩是不可能的(因为这将产生相同数量的位,我试图压缩没有网络获得)。但是,一旦为特定哈希创建了一个霍夫曼树,你就会得到原始哈希的压缩(例如20个字节到16个字节),只是为了让你保存的4个字节随后丢失,因为你也必须存储霍夫曼树。这种方法可能适用于较长的哈希值(512位等),但似乎不能很好地使所有SHA1哈希值保证实现(只有非常小的SHA1哈希输出子集将从这种类型的压缩中受益)。

0 个答案:

没有答案