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