CPU-функции RDRAND и RDSEED стали доступнее

    Всем привет!

    Сам я криптографией не занимаюсь, но кому то вполне может пригодится мое небольшое исследование. Решил разобраться со встроенными в процессор функциями RDRAND и RDSEED. Компилятор Delphi сказал Undeclared identifier. Хмм. Уже давно существует BMI, BMI2, AVX, AVX2 и даже AVX-512, а дельфийцы остановились на SSE4.2. Не беда. Скомпилируем код сами.

    Сначала сделал проверку на поддержку данных функций процессором. Конечно же CPUID. Использовать CPUID можно начиная с первых Pentium процессоров. Надеюсь никто не додумается запустить CPUID на 486 машине, ибо ее там еще не было. Кстати RDRAND и RDSEED до процессоров IvyBridge также не существует.

    function CPU_support_RDRAND: Boolean;
    asm
      mov rax, $01
      cpuid
      test ecx, 40000000h //тестируем 30-й бит
      setne al
    end;
    
    function CPU_support_RDSEED: Boolean;
    asm
      mov rcx, 0
      mov rax, $07 //страница №7
      cpuid
      test ebx, 40000h //тестируем 18-й бит
      setne al
    end;
    

    Оказалось, что мой Core i7 G6950X Extreme поддерживает данные функции. Поэтому дальше решил скомпилировать байт-код вручную. Для опытных приведу код REX и REX.W префиксов. Возможно вы захотите записать результат в другой регистр:

    const
      REX_RDRAND32: Byte = $F0; //(11b:REG, 110b:OPCODE, 000b:EAX) 
      REX_RDSEED32: Byte = $F8; //(11b:REG, 111b:OPCODE, 000b:EAX)
      REX_W_RDRAND64: Byte = $48; //(11b:REG, 110b:OPCODE, 000b:RAX)
      REX_W_RDSEED64: Byte = $48; //(11b:REG, 111b:OPCODE, 000b:RAX)
    

    Функции могут работать как в 32-х битном режиме, так и в 64-х битном. Поэтому сделал обе и даже в двух вариантах. В итоге получилось 4 функции:

    function RDRand32: DWord;
    asm
     @Retry:
      db $0F, $C7, $F0 //RDRAND EAX (CF = 1 говорит о корректности данных)
      jnc @Retry
    end;
    
    function RDSeed32: DWord;
    asm
     @Retry:
      db $0F, $C7, $F8 //RDSEED EAX (CF = 1 говорит о корректности данных)
      jnc @Retry
    end;
    
    function RDRand64: QWord;
    asm
      .NOFRAME
    
     @Retry:
      db $48, $0F, $C7, $F0  //RDRAND RAX (CF = 1 говорит о корректности данных)
      jnc @Retry
    end;
    
    function RDSeed64: QWord;
    asm
      .NOFRAME
    
     @Retry:
      db $48, $0F, $C7, $F8 //RDSEED RAX (CF = 1 говорит о корректности данных)
      jnc @Retry
    end;

    По скорости они медленней, чем библиотечная функция Random. RDRand примерно на 35%, а RDSeed процентов на 50% и даже более, но качество уникальности генерируемых значений значительно выше. На данном ресурсе есть неплохие статьи на эту тему, ну а моя миссия (сделать функции доступными в Delphi) завершена. В Lazarus не тестировал, но скорее всего будет работать без каких либо изменений. В конец объявления функции нужно лишь добавить резервное слово assembler.

    Здесь исходные тексты тестового консольного приложения. Там можно найти прототип функций Random32 и Random64 на основе встроенных в процессор. Возможно это то, что вы искали. Всем пока!
    Поделиться публикацией

    Комментарии 15

      +3
      Надеюсь никто не додумается запустить CPUID на 486 машине, ибо ее там еще не было.

      На каких-то не было, а на каких-то и была.
        +2
        качество уникальности генерируемых значений значительно выше

        Интересно, кто и как это определил?


        Вообще, как раз для криптографии очень нежелательно использовать один источник случайных чисел, особенно встроенный в процессор. Не знаю, как под виндой, но в линухе используется смесь из нескольких источников, чтобы возможные закладки в любом из них были нивелированы: вышеупомянутый RDRAND плюс события от клавы/мыши/диска/etc.

          +1
          Эти функции сертифицированы.
          RDRAND: Стандарт NIST SP 800-90A
          RDSEED: Стандарт NIST SP 800-90B and NIST SP800-90C.
          Стандарты конечно не наши, но все же.
            +1

            И как это отвечает на мой вопрос? Наличие сертификата говорит только о том, что кто-то вложил ресурсы в сертификацию. Сама по себе сертификация качество продукта не изменяет, и ничего не говорит о качестве этого продукта относительно других.

              0
              И как это отвечает на мой вопрос?

              Вы пишите: используется смесь из нескольких источников...
              В этой статье как раз описывается как устроены данные функции:
              habr.com/ru/post/128666
              Они тоже берут данные из нескольких источников.
                0

                Это не имеет значения, потому что работа RDRAND полностью контролируется одной компанией, и провести аудит как она на самом деле работает в конкретном куске кремния — крайне проблематично.

                  0
                  Я вас ни к чему не агитирую.
            0

            Была даже петиция на тему убрать rdrand из /dev/random :-) но Торвальдс заявляет что если там есть бэкдор то это никак не повляет т.к используются и другие способы как вы упомянули.

              0

              Я в курсе. И, на мой взгляд, он абсолютно прав. Что не отменяет возможности того, что RDRAND содержит бэкдор.

            0
            Первый раз вижу инструкцию, которая не возвращает сразу правильное значение. Прям асинхронный ассемблер какой-то получается: пока генерируется случайное число процессор может заняться чем-то еще.
              +1
              Видимо, вы не застали математический сопроцессор x87 в те времена, когда он был отдельным чипом.
              0
              Здраствуйте, увидел у Вас похожие штуки и решил спросить. Где почитать и найти таблицы с объяснениями всех команд, чисто чтоб подобное
              .text
              Filename: In[35]
              pushq %rbp
              movq %rsp, %rbp
              Source line: 1
              mulsd %xmm0, %xmm0
              popq %rbp
              retq
              nopw (%rax,%rax)

              не представлялось какой-то магией, чтоб можно было такую вещь окинуть взором и сделать вывод, де, тут сумма и цикл, можно сделать и лучше
                +1
                На сайте Intel . А как обычно все делают: опыт — сын ошибок трудных. В вашем случае вроде ассемблер AT&T.
                0
                > По скорости они медленней, чем библиотечная функция Random.

                Я написал функцию для заполнения 32-х байт случайными данными. Получилось:
                Haswell 1400 тиков процессора (3.3 гигатика в секунду, Linux на голом железе)
                Sandy Bridge EP 45 тиков (1.7 гигатика, Xeon в VMWare на Aruba)

                Таким образом на разных процессорах разница на 2 порядка (1400 тактов и 45) при разнице в тактовой частоте процессоров всего в 2 раза. Однако, я не знаю, как оценить влияние виртуализации во втором случае (кроме как запустить на голом железе). Так что выкладывайте свои результаты (проц, количество тактов, количество гигатиков по результатам калибровки).
                Код программы
                /*
                 * Copyright 2019 Alex Syrnikov <san@masterspline.eu>
                 *
                 * Licensed under the Apache License, Version 2.0 (the "License");
                 * you may not use this file except in compliance with the License.
                 * You may obtain a copy of the License at
                 *
                 *     http://www.apache.org/licenses/LICENSE-2.0
                
                 * Unless required by applicable law or agreed to in writing, software
                 * distributed under the License is distributed on an "AS IS" BASIS,
                 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
                 * See the License for the specific language governing permissions and
                 * limitations under the License.
                 */
                
                // clang++-7 -O3 -Wall -Wextra -pedantic -march=native -mtune=native -o rdrand32bytes ./rdrand.cpp
                
                #include <cstdio>
                #include <cstdint>
                
                #include <chrono>
                #include <thread>
                #include <algorithm>
                
                inline uint64_t rdtsc()
                {
                    uint32_t tickl, tickh;
                    __asm__ __volatile__("rdtsc":"=a"(tickl),"=d"(tickh));
                    return ( static_cast<uint64_t>(tickh) << 32) | tickl;
                }
                
                void calibrate_tsc()
                {
                    printf( "tsc calibration\n" );
                
                    auto chrono_begin = std::chrono::steady_clock::now();
                    uint64_t begin_tsc = rdtsc();
                    std::this_thread::sleep_for( std::chrono::milliseconds(100) );
                    uint64_t end_tsc = rdtsc();
                    auto chrono_end   = std::chrono::steady_clock::now();
                    std::chrono::duration<double> diff = chrono_end - chrono_begin;
                
                    auto ticks_per_second = static_cast<size_t>((end_tsc-begin_tsc)/diff.count());
                    printf( "tsc per second %zd\n", ticks_per_second );
                }
                
                int main()
                {
                    calibrate_tsc();
                
                    uint64_t begin_tsc, end_tsc;
                
                    uint8_t rnd[32];
                    std::fill( rnd, rnd+sizeof(rnd), 0 );
                
                    begin_tsc = rdtsc();
                    for( size_t i = 0; i < sizeof(rnd)/8; ++i )
                        __builtin_ia32_rdrand64_step( reinterpret_cast<unsigned long long*>(rnd+8*i) );
                    end_tsc = rdtsc();
                    printf( "rdrand64 for buffer got %ld ticks\n", end_tsc-begin_tsc );
                
                    printf( "rnd:" );
                    for( auto el : rnd )
                    {
                        printf( " %02x", el );
                    }
                    printf( "\n" );
                
                    return 0;
                }
                

                  0
                  Проц Intel i7 G6950X Extreme 3.0ГГц (3.4 ГГц при работе). Разгон до 4 ГГц выключил.
                  Монитор откалиброван, а больше мне ничего калибровать не надо.

                  Генерация данных:
                  Заголовок спойлера
                  Всего итераций (x64): 100 000 000
                  
                  Функция: RDRand32
                  Время: 8,47 сек.
                  11 807 769,51 в сек.
                  
                  Функция: RDRand64
                  Время: 8,25 сек.
                  12 121 212,12 в сек.
                  
                  Функция: System.Random
                  Время: 0,23 сек.
                  427 350 427,35 в сек.
                  
                  Всего итераций (x64): 10 000 000
                  
                  Функция: RDSeed32
                  Время: 9,86 сек.
                  1 014 301,65 в сек.
                  
                  Функция: RDSeed64
                  Время: 9,86 сек.
                  1 014 198,78 в сек.

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

                Самое читаемое