Нужна меньшая альтернатива GUID для идентификатора БД, но все же уникальная и случайная для URL - PullRequest
33 голосов
/ 09 февраля 2009

Я искал все места для этого, и я не могу получить полный ответ на это. Так что, если ответ уже существует в стеке, я заранее извиняюсь.

Мне нужен уникальный и случайный идентификатор, чтобы пользователи на моем веб-сайте не могли угадать следующий номер и просто переходить к чужой информации. Я планирую придерживаться возрастающего идентификатора для первичного ключа, но также сохранить случайный и уникальный идентификатор (своего рода хэш) для этой строки в БД и поместить в нее индекс.

Из моих поисков я понимаю, что хотел бы избежать столкновений, и я прочитал некоторые упоминания о SHA1.

Мои основные требования

  • Что-то меньше, чем GUID. (Выглядит ужасно в URL)
  • Должен быть уникальным
  • Избегать столкновений
  • Не длинный список странных символов, которые невозможно прочитать.

Примером того, что я ищу, может быть www.somesite.com/page.aspx?id=AF78FEB

Я не уверен, должен ли я реализовывать это в базе данных (я использую SQL Server 2005) или в коде (я использую C # ASP.Net)

EDIT:

Из всего прочитанного мною я понимаю, что это безопасность через мрак. Я намерен иметь надлежащую авторизацию и аутентификацию для доступа к страницам. Я буду использовать .Net для аутентификации и авторизации. Но как только законный пользователь вошел в систему и получает доступ к легитимной (но динамически создаваемой странице), заполненной ссылками на принадлежащие ему элементы. Например, ссылка может быть www.site.com/page.aspx?item_id=123. Что мешает ему нажать на эту ссылку, а затем изменить вышеуказанный URL-адрес, чтобы перейти на www.site.com/page.aspx?item_id=456, который НЕ принадлежит ему? Я знаю, что некоторые технологии Java, такие как Struts (я исправлюсь), хранят все в сеансе и каким-то образом работают с этим, но я не знаю, как это сделать.

Ответы [ 11 ]

16 голосов
/ 09 февраля 2009

Раймонд Чен имеет хорошую статью о том, почему вы не должны использовать "half guid", и предлагает подходящее решение для генерации собственного значения типа "не совсем guid, но достаточно хорошо" здесь:

GUID уникальны во всем мире, но подстроки GUID не

Его стратегия (без конкретной реализации) была основана на:

  • Четыре бита для кодирования номера компьютера,
  • 56 бит для метки времени и
  • четыре бита в качестве уникального.

Мы можем уменьшить количество битов, чтобы сделать компьютер уникальным, поскольку количество компьютеров в кластере ограничено, и мы можем уменьшить количество бит в метке времени, предполагая, что программа не будет работать 200 лет с этого момента.

Вы можете избежать неприятностей с четырехбитным уникальным индексом, если предположить, что часы не будут отклоняться более, чем на час, скажем, и что часы не будут сбрасываться более шестнадцати раз в час.

15 голосов
/ 09 февраля 2009

ОБНОВЛЕНИЕ (4 февраля 2017 г.):
Вальтер Стабош обнаружил ошибку в оригинальном коде. В ходе расследования были обнаружены дальнейшие ошибки, однако при тщательном тестировании и переработке кода я сам, первоначальный автор ( CraigTP ) теперь исправил все эти проблемы. Я обновил здесь код с правильной рабочей версией, и вы также можете загрузить решение Visual Studio 2015 здесь , которое содержит код генерации «шорткода» и довольно полный набор тестов для подтверждения правильности.

Один интересный механизм, который я использовал в прошлом, заключается в том, чтобы внутренне просто использовать возрастающее целое / длинное, но чтобы «отобразить» это целое число в буквенно-цифровой «код».

Пример

Console.WriteLine($"1371 as a shortcode is: {ShortCodes.LongToShortCode(1371)}");
Console.WriteLine($"12345 as a shortcode is: {ShortCodes.LongToShortCode(12345)}");
Console.WriteLine($"7422822196733609484 as a shortcode is: {ShortCodes.LongToShortCode(7422822196733609484)}");

Console.WriteLine($"abc as a long is: {ShortCodes.ShortCodeToLong("abc")}");
Console.WriteLine($"ir6 as a long is: {ShortCodes.ShortCodeToLong("ir6")}");
Console.WriteLine($"atnhb4evqqcyx as a long is: {ShortCodes.ShortCodeToLong("atnhb4evqqcyx")}");    

// PLh7lX5fsEKqLgMrI9zCIA   
Console.WriteLine(GuidToShortGuid( Guid.Parse("957bb83c-5f7e-42b0-aa2e-032b23dcc220") ) );      

Код

Следующий код показывает простой класс, который изменит long на «code» (и обратно!):

public static class ShortCodes
{
    // You may change the "shortcode_Keyspace" variable to contain as many or as few characters as you
    // please.  The more characters that are included in the "shortcode_Keyspace" constant, the shorter
    // the codes you can produce for a given long.
    private static string shortcodeKeyspace = "abcdefghijklmnopqrstuvwxyz0123456789";

    public static string LongToShortCode(long number)
    {
        // Guard clause.  If passed 0 as input
        // we always return empty string.
        if (number == 0)
        {
            return string.Empty;
        }

        var keyspaceLength = shortcodeKeyspace.Length;
        var shortcodeResult = "";
        var numberToEncode = number;
        var i = 0;
        do
        {
            i++;
            var characterValue = numberToEncode % keyspaceLength == 0 ? keyspaceLength : numberToEncode % keyspaceLength;
            var indexer = (int) characterValue - 1;
            shortcodeResult = shortcodeKeyspace[indexer] + shortcodeResult;
            numberToEncode = ((numberToEncode - characterValue) / keyspaceLength);
        }
        while (numberToEncode != 0);
        return shortcodeResult;
    }

    public static long ShortCodeToLong(string shortcode)
    {
        var keyspaceLength = shortcodeKeyspace.Length;
        long shortcodeResult = 0;
        var shortcodeLength = shortcode.Length;
        var codeToDecode = shortcode;
        foreach (var character in codeToDecode)
        {
            shortcodeLength--;
            var codeChar = character;
            var codeCharIndex = shortcodeKeyspace.IndexOf(codeChar);
            if (codeCharIndex < 0)
            {
                // The character is not part of the keyspace and so entire shortcode is invalid.
                return 0;
            }
            try
            {
                checked
                {
                    shortcodeResult += (codeCharIndex + 1) * (long) (Math.Pow(keyspaceLength, shortcodeLength));
                }
            }
            catch(OverflowException)
            {
                // We've overflowed the maximum size for a long (possibly the shortcode is invalid or too long).
                return 0;
            }
        }
        return shortcodeResult;
    }
}

}

Это по сути ваша собственная система нумерации baseX (где X - количество уникальных символов в константе shortCode_Keyspace.

Чтобы сделать вещи непредсказуемыми, начните свою внутреннюю инкрементную нумерацию с чего-то отличного от 1 или 0 (то есть начинайте с 184723), а также измените порядок символов в константе shortCode_Keyspace (то есть используйте буквы AZ и цифры 0-9 , но используйте их порядок в строке констант. Это поможет сделать каждый код несколько непредсказуемым.

Если вы используете это для «защиты» чего-либо, это все еще безопасность по незаметности, и если данный пользователь может наблюдать достаточное количество этих сгенерированных кодов, он может предсказать соответствующий код для заданной длины. «Безопасность» (если это можно так назвать) заключается в том, что константа shortCode_Keyspace шифруется и остается секретной.

EDIT: Если вы просто хотите сгенерировать GUID и преобразовать его во что-то уникальное, но содержащее на несколько символов меньше, эта маленькая функция поможет:

public static string GuidToShortGuid(Guid gooid)
{
    string encoded = Convert.ToBase64String(gooid.ToByteArray());
    encoded = encoded.Replace("/", "_").Replace("+", "-");
    return encoded.Substring(0, 22);
}
14 голосов
/ 09 февраля 2009

Если вы не хотите, чтобы другие пользователи видели информацию о людях, почему бы вам не защитить страницу, на которой вы используете идентификатор?

Если вы сделаете это, то не будет иметь значения, если вы используете инкрементный идентификатор.

9 голосов
/ 09 февраля 2009

[В ответ на правку]
Вы должны рассматривать строки запроса как «злой ввод». Вам необходимо программно проверить, что аутентифицированному пользователю разрешено просматривать запрошенный элемент.

if( !item456.BelongsTo(user123) )
{
  // Either show them one of their items or a show an error message.
}
3 голосов
/ 09 февраля 2009

Вы можете случайным образом сгенерировать число. Проверьте, что этот номер еще не в БД и использовать его. Если вы хотите, чтобы она отображалась в виде случайной строки, вы можете просто преобразовать ее в шестнадцатеричное, так что вы получите A-F, как в вашем примере.

2 голосов
/ 09 февраля 2009

Возьмите свой идентификатор автоинкремента и HMAC-SHA1 с секретом, известным только вам. Это сгенерирует случайные 160-битные символы, которые скрывают действительный инкрементный идентификатор. Затем возьмите префикс длины, которая делает коллизии достаточно маловероятными для вашего приложения - скажем, 64-битные, которые вы можете кодировать в 8 символов. Используйте это как свою строку.

HMAC гарантирует, что никто не сможет сопоставить показанные биты с базовым числом. Хэшируя идентификатор автоинкремента, вы можете быть уверены, что он будет уникальным. Таким образом, ваш риск коллизий связан с вероятностью 64-битного частичного коллизии в SHA1. С помощью этого метода вы можете заранее определить, возникнут ли какие-либо коллизии, предварительно сгенерировав все случайные строки, которые генерирует этот метод (например, вплоть до ожидаемого числа строк), и проверив.

Конечно, если вы хотите указать уникальное условие для столбца вашей базы данных, то просто будет сгенерировано абсолютно случайное число. Вы просто должны быть осторожны с источником случайности.

2 голосов
/ 09 февраля 2009

GUID - 128 бит. Если вы берете эти биты и не используете набор символов из 16 символов для их представления (16 = 2 ^ 4 и 128/4 = 32 символа), но набор символов, скажем, с 64 символами (например, Base 64) в итоге вы получите только 22 символа (64 = 2 ^ 6 и 128/6 = 21,333, то есть 22 символа).

0 голосов
/ 24 февраля 2017

GUID - это просто число

Последнее поколение GUID (версия 4) - это большое случайное число *

Поскольку это большое случайное число, вероятность столкновения ДЕЙСТВИТЕЛЬНО мала.

Самое большое число, которое вы можете сделать с GUID, закончилось:

5,000,000,000,000,000,000,000,000,000,000,000,000

Таким образом, если вы сгенерируете два GUID, вероятность того, что второй GUID будет таким же, как первый:

1 in 5,000,000,000,000,000,000,000,000,000,000,000,000

Если вы сгенерировали 100 МИЛЛИАРДОВ GUID.

Вероятность того, что ваш 100-миллиардный GUID столкнется с другими 99 999 999 999 GUID, составляет:

1 in 50,000,000,000,000,000,000,000,000

Почему 128 бит?

Одна из причин в том, что компьютеры любят работать с кратными 8 битам.

8, 16, 32, 64, 128 и т. Д.

Другая причина в том, что парень, который придумал GUID, чувствовал, что 64 недостаточно, а 256 слишком много.

Вам нужно 128 бит?

Нет, сколько бит вам нужно, зависит от того, сколько чисел вы ожидаете генерировать и насколько вы хотите быть уверенными, что они не сталкиваются.

64-битный пример

Тогда вероятность того, что ваш второй номер столкнется с первым, будет:

1 in 18,000,000,000,000,000,000 (64 bit)

Вместо:

1 in 5,000,000,000,000,000,000,000,000,000,000,000,000 (128 bit)

А как насчет 100-миллиардного числа?

Вероятность того, что ваше 100-миллиардное число столкнется с остальными 99 999 999 999, составит:

1 in 180,000,000 (64 bit)

Вместо:

1 in 50,000,000,000,000,000,000,000,000 (128 bit)

Так стоит ли использовать 64 бита?

Зависит от того, генерируете ли вы 100 миллиардов чисел? Даже если бы вы были тогда, 180 000 000 вас не устраивают?

Немного подробнее о GUID

Я конкретно говорю о версии 4.

Версия 4 на самом деле не использует все 128 бит для части случайного числа, она использует 122 бита. Остальные 6 бит используются для обозначения версии 4 стандарта GUID.

Числа в этом ответе основаны на 122 битах.

И да, поскольку это просто случайное число, вы можете просто взять из него желаемое количество бит. (Просто убедитесь, что вы не берете ни один из 6 бит версий, которые никогда не меняются - см. Выше).

Вместо того, чтобы брать биты из GUID, хотя вы можете вместо этого использовать тот же генератор случайных чисел, из которого GUID получил свои биты.

В нем , вероятно использовался генератор случайных чисел, поставляемый с операционной системой.

0 голосов
/ 09 февраля 2009

У меня только что была идея, и я вижу, что Грег также указал на нее. У меня есть пользователь, сохраненный в сеансе с идентификатором пользователя. Когда я создаю запрос, я присоединяюсь к таблице «Пользователи» с этим идентификатором пользователя. Если результирующий набор пуст, мы знаем, что он взломал URL, и я могу перенаправить на страницу с ошибкой.

0 голосов
/ 09 февраля 2009

То, что вы можете сделать, - это то, что я делаю, когда хочу именно то, что вы хотите.

  1. Создайте свой GUID.

  2. Получи удалить черточки и получи подстрока того, как долго вы хотите, чтобы ваш ID

  3. Проверьте БД для этого идентификатора, если он существует перейти к шагу 1.

  4. Вставить запись.

Это самый простой способ убедиться, что он скрыт и уникален.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...