短いハッシュを生成するハッシュ関数?


96

任意の長さの文字列を取り、10文字未満のハッシュを生成できる暗号化の方法はありますか?合理的に一意のIDを生成したいが、ランダムではなく、メッセージの内容に基づいています。

メッセージを整数値に制限しても問題ありませんが、任意の長さの文字列は不可能です。ただし、その場合、ハッシュは2つの連続した整数で類似していてはなりません。


これはハッシュと呼ばれます。それはユニークではありません。
SLaks、

1
これはまた、あるハッシュ切り捨て問題、そうも参照stackoverflow.com/q/4784335
ピーター・クラウス

2
参考までに、ウィキペディアのハッシュ関数のリストを参照してください。
バジルブルク2018年

回答:


75

一般的に利用可能なハッシュアルゴリズム(SHA-1など)を使用できます。これにより、必要な結果よりも少し長い結果が得られます。結果を希望する長さに切り捨てるだけで十分です。

たとえば、Pythonの場合:

>>> import hashlib
>>> hash = hashlib.sha1("my message".encode("UTF-8")).hexdigest()
>>> hash
'104ab42f1193c336aa2cf08a2c946d5c6fd0fcdb'
>>> hash[:10]
'104ab42f11'

2
適切なハッシュ関数は切り捨てることができます。
James K. Polk大統領2010

88
これにより、衝突のリスクが大幅に上昇するのではないでしょうか。
Gabriel Sanmartin 2013

143
@erasmospunk:base64を使用したエンコードは、衝突に抵抗しません。衝突hash(a)するとhash(b)base64(hash(a))も衝突するためbase64(hash(b))です。
グレッグ・ヒューギル2013年

56
@GregHewgill正解ですが、元のハッシュアルゴリズムの衝突については話していません(そうです、sha1衝突しますが、これは別の話です)。10文字のハッシュがある場合、base64vs base16(または16進数)でエンコードすると、エントロピーが高くなります。どれくらい高い?ではbase16であなた、文字あたり4ビットの情報を取得しbase64、この図は6bits /文字です。合計10文字の「16進数」のハッシュは40ビットのエントロピーを持ち、base64は60ビットです。ですから、少し抵抗力があります、すごくはっきりしていなかったら申し訳ありません。
John L. Jegutanis 2013年

19
@erasmospunk:ああ、どういう意味かわかります。結果の固定サイズが限られている場合は、base64エンコーディングと16進エンコーディングでは、より重要なビットをパックできます。
グレッグ

46

意図的な変更に強いアルゴリズムが必要ない場合は、adler32と呼ばれるアルゴリズムを使用して、かなり短い(8文字まで)結果を生成します。ここのドロップダウンから選択して試してください。

http://www.sha1-online.com/


2
それは非常に古く、あまり信頼できません。
マスカルポーネ2018年

1
@マスカルポーネ「あまり信頼できない」-ソース?それは制限があります、あなたがそれらを知っているなら、それが何歳であっても関係ありません。
BT

8
@マスカルポーネ「弱点が少ない」-もう一度、どのような弱点?このアルゴリズムがOPの使用に100%完全ではないと思うのはなぜですか?
BT

3
@マスカルポーネOPは、彼らが暗号級のハッシュを望んでいると言っていません。OTOH、Adler32はチェックサムであり、ハッシュではないため、OPが実際に何を行っているかによっては、適切ではない場合があります。
PM 2Ring

2
Adler32チェックに1つの警告が引用、あるウィキペディアAdler32チェックは数百バイトと短いメッセージのための弱さを持って、これらのメッセージのチェックサムが32ビットで利用できるのが悪いカバレッジを持っているので。
バジルブルク2018年

13

ダイジェストを作成するには、コンテンツをハッシュする必要があります。利用可能なハッシュは多数ありますが、結果セットの10文字はかなり小さいです。昔、人々は33ビットのハッシュ(基本的に4文字と1ビット)を生成するCRC-32を使用していました。65ビットのハッシュを生成するCRC-64もあります。128ビットハッシュ(16バイト/文字)を生成するMD5は、同じハッシュを持つ2つのメッセージが見つかるため、暗号化の目的で壊れていると見なされます。言うまでもなく、任意の長さのメッセージから16バイトのダイジェストを作成すると、結果として重複が発生します。ダイジェストが短いほど、衝突のリスクが高くなります。

ただし、ハッシュが2つの連続したメッセージ(整数であるかどうかに関係なく)で類似していないという懸念は、すべてのハッシュで当てはまるはずです。元のメッセージが1ビット変更されても、結果として大きく異なるダイジェストが生成されます。

したがって、CRC-64(および結果をbase-64変換)のようなものを使用すると、探している近所に移動できます。


1
SHA-1ハッシュをCRCしてから結果をbase-64化すると、結果のIDが衝突に対してより耐性になりますか?

5
「しかし、2つの連続したメッセージでハッシュが似ていないという懸念[...]は、すべてのハッシュに当てはまるはずです。」-それは必ずしも本当ではありません。例えば、クラスタリングまたはクローン検出に使用されているハッシュ関数のために、正反対のは、実際に、真である:あなたが望む類似した文書は、同様の(あるいは同じ)のハッシュ値を生成します。同様の入力に対して同一の値を生成するように特別に設計されたハッシュアルゴリズムの有名な例は、Soundexです。
イェルクWミッターク

メッセージの署名を認証するためにハッシュを使用しています。したがって、基本的には、既知のメッセージと指定された署名の場合、ハッシュは正しい必要があります。ただし、誤検知の割合がわずかであってもかまいません。それは完全に受け入れられます。現在、便宜上、base62で圧縮された切り捨てられたSHA-512ハッシュ(すばやくホイップしたもの)を使用しています。

@JörgWMittagSoundExの優れた点。私は修正された立場です。すべてのハッシュが同じ特性を持つわけではありません。
ジョン

12

私に役立つ回答をまとめただけです(base-64エンコーディングの使用に関する@erasmospunkのコメントに注目してください)。私の目標は、ほとんどがユニークな短い文字列を持つことでした...

私は専門家ではないので、明白なエラーがある場合はこれを修正してください(Pythonでも受け入れられた回答のように)。

import base64
import hashlib
import uuid

unique_id = uuid.uuid4()
# unique_id = UUID('8da617a7-0bd6-4cce-ae49-5d31f2a5a35f')

hash = hashlib.sha1(str(unique_id).encode("UTF-8"))
# hash.hexdigest() = '882efb0f24a03938e5898aa6b69df2038a2c3f0e'

result = base64.b64encode(hash.digest())
# result = b'iC77DySgOTjliYqmtp3yA4osPw4='

resultここでは、(あなたが使用している場合、あなたが取得したいものをちょうど進文字以上使用しているhash.hexdigest()ことである(つまり、六角ダイジェストよりも切り捨てることは、より安全でなければなりません)、衝突を持っている可能性が低いですので)。

注:UUID4(ランダム)を使用します。他のタイプについては、http://en.wikipedia.org/wiki/Universally_unique_identifierを参照してください


7

MD5(128ビット)やSHA1(160)などの短いものを生成する既存のハッシュアルゴリズムを使用できます。次に、ダイジェストのセクションを他のセクションとXORすることで、さらに短縮できます。これは衝突の可能性を高めますが、単にダイジェストを切り捨てるほど悪くはありません。

また、元のデータの長さを結果の一部として含めて、一意にすることもできます。たとえば、MD5ダイジェストの前半を後半とXORすると64ビットになります。データの長さに32ビットを追加します(または、長さが常により少ないビットに収まることがわかっている場合は、それより低くします)。これにより、96ビット(12バイト)の結果が得られ、24文字の16進文字列に変換できます。または、base 64エンコーディングを使用してさらに短くすることもできます。


2
FWIW、これはXORフォールディングとして知られています。
PM 2Ring 2018

7

必要な"sub-10-character hash" 場合は、8文字のハッシュ(32ビット)を生成するFletcher-32アルゴリズム、 CRC-32またはAdler-32を使用できます。

CRC-32は、Adler32よりも20〜100%低速です。

Fletcher-32は、Adler-32よりもわずかに信頼性が高くなっています。アドラーチェックサムよりも計算コストが低い:フレッチャーとアドラーの比較

いくつかのFletcher実装を含むサンプルプログラムを以下に示します。

    #include <stdio.h>
    #include <string.h>
    #include <stdint.h> // for uint32_t

    uint32_t fletcher32_1(const uint16_t *data, size_t len)
    {
            uint32_t c0, c1;
            unsigned int i;

            for (c0 = c1 = 0; len >= 360; len -= 360) {
                    for (i = 0; i < 360; ++i) {
                            c0 = c0 + *data++;
                            c1 = c1 + c0;
                    }
                    c0 = c0 % 65535;
                    c1 = c1 % 65535;
            }
            for (i = 0; i < len; ++i) {
                    c0 = c0 + *data++;
                    c1 = c1 + c0;
            }
            c0 = c0 % 65535;
            c1 = c1 % 65535;
            return (c1 << 16 | c0);
    }

    uint32_t fletcher32_2(const uint16_t *data, size_t l)
    {
        uint32_t sum1 = 0xffff, sum2 = 0xffff;

        while (l) {
            unsigned tlen = l > 359 ? 359 : l;
            l -= tlen;
            do {
                sum2 += sum1 += *data++;
            } while (--tlen);
            sum1 = (sum1 & 0xffff) + (sum1 >> 16);
            sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        }
        /* Second reduction step to reduce sums to 16 bits */
        sum1 = (sum1 & 0xffff) + (sum1 >> 16);
        sum2 = (sum2 & 0xffff) + (sum2 >> 16);
        return (sum2 << 16) | sum1;
    }

    int main()
    {
        char *str1 = "abcde";  
        char *str2 = "abcdef";

        size_t len1 = (strlen(str1)+1) / 2; //  '\0' will be used for padding 
        size_t len2 = (strlen(str2)+1) / 2; // 

        uint32_t f1 = fletcher32_1(str1,  len1);
        uint32_t f2 = fletcher32_2(str1,  len1);

        printf("%u %X \n",    f1,f1);
        printf("%u %X \n\n",  f2,f2);

        f1 = fletcher32_1(str2,  len2);
        f2 = fletcher32_2(str2,  len2);

        printf("%u %X \n",f1,f1);
        printf("%u %X \n",f2,f2);

        return 0;
    }

出力:

4031760169 F04FC729                                                                                                                                                                                                                              
4031760169 F04FC729                                                                                                                                                                                                                              

1448095018 56502D2A                                                                                                                                                                                                                              
1448095018 56502D2A                                                                                                                                                                                                                              

テストベクトルに同意する:

"abcde"  -> 4031760169 (0xF04FC729)
"abcdef" -> 1448095018 (0x56502D2A)

Adler-32は、数百バイトの短いメッセージには弱点があります。これは、これらのメッセージのチェックサムの32の使用可能なビットのカバレッジが不十分であるためです。これをチェックして:

Adler32アルゴリズムは、同等のチェックサムと競合するほど複雑ではありません


6

ターミナルでこれを実行するだけです(MacOSまたはLinux):

crc32 <(echo "some string")

長さ8文字。


4

Python のhashlibライブラリを使用できます。shake_128shake_256アルゴリズムは、可変長ハッシュを提供します。ここにいくつかの作業コード(Python3)があります:

import hashlib
>>> my_string = 'hello shake'
>>> hashlib.shake_256(my_string.encode()).hexdigest(5)
'34177f6a0a'

長さパラメーターx(例では5)を使用すると、関数は長さ2xのハッシュ値を返すことに注意してください。


1

それは今2019年であり、より良いオプションがあります。つまり、xxhashです。

~ echo test | xxhsum                                                           
2d7f1808da1fa63c  stdin

このリンクは壊れています。より完全な答えを提供することをお勧めします。
eri0o

0

最近、単純な文字列削減関数に沿って何かが必要になりました。基本的に、コードは次のようになります(C / C ++コードの前):

size_t ReduceString(char *Dest, size_t DestSize, const char *Src, size_t SrcSize, bool Normalize)
{
    size_t x, x2 = 0, z = 0;

    memset(Dest, 0, DestSize);

    for (x = 0; x < SrcSize; x++)
    {
        Dest[x2] = (char)(((unsigned int)(unsigned char)Dest[x2]) * 37 + ((unsigned int)(unsigned char)Src[x]));
        x2++;

        if (x2 == DestSize - 1)
        {
            x2 = 0;
            z++;
        }
    }

    // Normalize the alphabet if it looped.
    if (z && Normalize)
    {
        unsigned char TempChr;
        y = (z > 1 ? DestSize - 1 : x2);
        for (x = 1; x < y; x++)
        {
            TempChr = ((unsigned char)Dest[x]) & 0x3F;

            if (TempChr < 10)  TempChr += '0';
            else if (TempChr < 36)  TempChr = TempChr - 10 + 'A';
            else if (TempChr < 62)  TempChr = TempChr - 36 + 'a';
            else if (TempChr == 62)  TempChr = '_';
            else  TempChr = '-';

            Dest[x] = (char)TempChr;
        }
    }

    return (SrcSize < DestSize ? SrcSize : DestSize);
}

必要以上に衝突が発生する可能性がありますが、暗号化ハッシュ関数としての使用は意図されていません。衝突が多すぎる場合は、さまざまな乗数を試してみてください(つまり、37を別の素数に変更します)。このスニペットの興味深い機能の1つは、SrcがDestより短い場合、Destは入力文字列をそのまま(0 * 37 + value = value)にすることです。プロセスの最後に「読み取り可能」なものが必要な場合、Normalizeは衝突の増加を犠牲にして変換されたバイトを調整します。

ソース:

https://github.com/cubiclesoft/cross-platform-cpp/blob/master/sync/sync_util.cpp


std :: hashは特定のユースケースを解決しません(例:余分な数行のコードで十分な場合に、肥大化したstd ::テンプレートをドラッグしないようにします)。ここにはばかげたことは何もありません。Mac OSXの主要な制限に対処するために慎重に検討されました。整数は欲しくない。そのため、私はdjb2を使用することができたが、それでもstd ::テンプレートの使用を避けた。
CubicleSoft 2016年

これはまだばかげて聞こえます。なぜだろう、これまで使用してDestSizeハッシュ自体はとても安っぽいとき4(32ビット)よりも大きいですか?intよりも大きい出力によって提供される衝突抵抗が必要な場合は、SHAを使用します。
Navin 2016年

見て、それは本当に伝統的なハッシュではありません。特定のOS(Mac OSXなど)でバッファスペースが非常に限られている場所で文字列サイズを宣言できる便利なプロパティがあり、実際のファイル名の制限されたドメイン内に収まる必要があり、単に切り捨てたくない名前は衝突を引き起こすためです(ただし、短い文字列はそのまま残されます)。暗号化ハッシュは常に正しい答えであるとは限らず、std :: hashも常に正しい答えであるとは限りません。
CubicleSoft 2016年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.