Некоторые аспекты разработки платежных систем. Часть II. One time passwords и ECDSA

    Доброго здравия, %username%!

    В первой части я рассказал как можно минимальными усилиями защитить БД нашей платежной системы. Но, как заметил один из комментирующих, при компрометации web сервера появляется возможность подсмотреть все логины и пароли пользователей. Тут нам на помощь приходят One time passwords (OTP).
    Под катом моя вольная интерпритация данного термина с использованием криптографии эллиптических кривых (ECC). Кстати говоря, платежные системы далеко не единственная сфера применения этой технологии.
    Upd:
    Ахтунг! При взломе веб сервера все таки есть вероятность подмены платежных реквизитов, так что все таки подписывать лучше не случайную строку (хоть это и защитит от полной компрометации системы, но не защитит от случаев, когда подменяются реквизиты прямо во время платежа), а хэш платежного документа, показывая юзеру при этом все реквизиты платежа в программе.
    З.Ы. Генерировать ключ лучше тоже на стороне клиента

    Чтобы не придумывать одноразовых карточек с паролями решено было применить ЭЦП. Как раз подвернулся случай использовать так любимые мной в силу своей надежности и скорости эллиптические кривые.
    Схема была следующей:
    1. При регистрации юзера выдаем ему зашифрованный файлик с открытым\закрытым ключом и паролем от него. Себе оставляем только открытый ключ.
    2. Когда юзер хочет совершить платеж или перевод денег генерируем случайным образом строку, которую ему показываем. Сохраняем её в бд с привязкой к пользователю.
    3. Юзер копирует строку в специальную программу, которая по выданному паролю дешифрует закрытый ключ и подписывает нашу случайную строку.
    4. Юзер отдает нам подпись, мы её проверяем с помощью открытого ключа и даем добро на операцию.


    Предстояло писать внешнюю хранимку для MySQL и сопутствующие программы. В качестве криптобиблиотеки был выбран старый добрый OpenSSL. В результате 2х суток без сна на свет появился рабочий вариант из:
    1. Програмки, генерирующей ключевую пару и помещающую её в БД (написал на Builder).
    2. Програмки для пользователя, генерирующей цифровую подпись (на нем же).
    3. Внешней хранимки, эту цифровую подпись проверяющей (пользователю нужен ответ мгновенно, програмки не катят). Написал на VC.


    Сразу предупреждаю: код не блещет красотой, C не мой основной ЯП.

    Теперь по пунктам:

    1) Генерируем ключ
      const KEYSIZE = SHA512_DIGEST_LENGTH+4; // 4 байта на «соль»
      unsigned char *pubkey = (unsigned char *)OPENSSL_malloc(10000),
      *privkey = (unsigned char *)OPENSSL_malloc(10000); //буферы для открытого и закрытого ключей
      AnsiString pub, prv, userid, paypass; //строки для хранения открытого, закрытого ключей и пароля от них
      unsigned char md[KEYSIZE]= {0}; // тут будет хеш от пароля
      unsigned int i = ParamCount();

      paypass = «rAnDom_PaSs»;

      EC_GROUP *group = EC_GROUP_new_by_curve_name(NID_sect571r1); // выбираем эллиптическую кривую
      EC_GROUP_set_point_conversion_form(group,POINT_CONVERSION_UNCOMPRESSED);

      EC_KEY *x = EC_KEY_new();
      EC_KEY_set_group(x,group);
      BIO *out = BIO_new(BIO_s_mem()); // Писать будем в память

      // Код для замедления брутфорса

      env_md_ctx_st mdctx; //Контекст для хэша
      EVP_MD_CTX_init(&mdctx);
      EVP_DigestInit_ex(&mdctx,EVP_sha512(),NULL); //Алгоритм хэширования — SHA512
      EVP_DigestUpdate(&mdctx,paypass.c_str(),strlen(paypass.c_str())); // Хэшируем пароль первый раз

      for (i = 0; i < 0x20000; i++) {

        memcpy(md,mdctx.md_data,SHA512_DIGEST_LENGTH); // копируем хэш в массив

        md[64] = i ^ md[0] ^ md[7] ^ md[5] ^ md[23]; //вычисляем дополнительные 4 байта
        md[65] = i ^ md[1] ^ md[9] ^ md[6] ^ md[53]; //на основе предыдущего хэша
        md[66] = i ^ md[3] ^ md[25] ^ md[11] ^ md[48]; // они будут т.н. раундовой «солью»
        md[67] = i ^ md[8] ^ md[18] ^ md[17] ^ md[2];

        EVP_DigestUpdate(&mdctx,md,KEYSIZE); // и хэшируем предыдущий хэш+соль
      }

      EVP_DigestFinal(&mdctx,md,NULL); //заканчиваем считать хэш
      EVP_MD_CTX_cleanup(&mdctx);

      EC_KEY_generate_key(x); //генерируем ключевую пару

      PEM_write_bio_ECPrivateKey(out, x, EVP_aes_256_cbc(), md, KEYSIZE, NULL, NULL); // пишем ключевую пару, зашифрованную по алгоритму AES-256 с ключом, посчитанным на предыдущем шаге
      BIO_flush(out);
      i = BIO_read(out,privkey,10000); // узнаем количество записанных байт

      // для верности сожмем ZLIBом

      zByte *compr;
      uLong comprLenPrv = 1000*sizeof(int);
      zByte *comprPrv  = (zByte*)calloc((uInt)comprLenPrv, 1);
      compress2(comprPrv, &comprLenPrv, (const Bytef*)privkey, i,Z_BEST_COMPRESSION);

      OPENSSL_free(privkey);
      BIO_free(out);

      out = BIO_new(BIO_s_mem()); //еще один буфер для открытого ключа
      PEM_write_bio_EC_PUBKEY(out,x); //пишем его в память в формате PEM
      BIO_flush(out);
      BIO_read(out,pubkey,10000); // читаем его в кусок памяти

        // Волшебный код, чтобы скопировать закрытый зашифрованный ключ в массив байт
      TByteDynArray cp;
      cp.set_length(comprLenPrv);

      for (i = 0; i < comprLenPrv; i++) {
        cp[i] = comprPrv[i];
      }
        
        //копируем открытый ключ в строку
      AnsiString pubk;
      pubk.sprintf("%s",pubkey);

        
        // освобождаем паметь
      free(comprPrv);
      OPENSSL_free(pubkey);
      BIO_free(out);
      EC_KEY_free(x);  


    2) Подписываем на клиенте этим ключом случайную строку, выданную сервером

    unsigned long i;
    unsigned char *x;
    unsigned char buf [1024]={0};

    //Подписываем строчку ключом. key — считанный и расшифрованный ключ из п.1. s — рендомная строчка.
    ECDSA_SIG *sig = ECDSA_do_sign(s.c_str(),s.Length(),key);

    x = buf;
    i = i2d_ECDSA_SIG(sig,&x); // конвертим в двоичную форму
    x = buf;
    ECDSA_SIG_free(sig);
    //чтобы подпись можно было скопировать — конвертим её в base64
    BIO *b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL); // без переводов строк
    BIO *mem = BIO_new(BIO_s_mem());
    mem = BIO_push(b64,mem);
    BIO_write(mem,&buf,i);
    BIO_flush(mem);

    char *res;
    BIO_get_mem_data(mem,&res);
    mAnswer->Text = res; // выводим пользователю результат.
    .

    3) Проверяем подпись
    int ssl_VerifySignature(const char *key, const char *str, const char *csig)
    {
        
        OpenSSL_add_all_algorithms();

        BIO *bkey = BIO_new(BIO_s_mem());
        BIO_write(bkey,key,(int)strlen(key));
        BIO_flush(bkey);

        EC_KEY *ec = PEM_read_bio_EC_PUBKEY(bkey,NULL,NULL,NULL); //читаем открытый ключ
        BIO_free(bkey);

        if (!ec) return -2;

        unsigned long i;
        unsigned char *x;
        unsigned char buf [1024]={0};

        BIO *b64 = BIO_new(BIO_f_base64());
        BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL);
        BIO *mem = BIO_new(BIO_s_mem());
        BIO_write(mem,csig,(int)strlen(csig));
        mem = BIO_push(b64,mem);
        x = buf;
        i = BIO_read(mem,x,1024);
        x = buf;

        ECDSA_SIG *sig = ECDSA_SIG_new();
        sig = d2i_ECDSA_SIG(&sig,(const unsigned char **)&x,i); // читаем подпись

        i = ECDSA_do_verify((const unsigned char *)str,(int)strlen(str),sig,ec); // проверяем подпись строки
        BIO_free_all(mem);
        ECDSA_SIG_free(sig);
        return i;
    }
    .

    Таким образом, даже если хакер получит доступ к аккаунту юзера, он не сможет потратить его деньги\сменить пароль\мыло\любые другие функции которые вы посчитаете нужным защитить с использованием этого метода.
    А если стырит ключ, то благодаря циклу хэширования пароля с солью 0x20000 (131072) раз запарится брутфорсить даже простые пароли.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

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

      0
      Надеюсь, криптографические примитивы (ECC, SHA) вы реализовывали не самостоятельно.

      Вот только повторное хеширование с солью выкините нафиг :) Достаточно будет, если вы сконкатенируете соль с паролем перед хешированием.

      И, кстати, соль надо не вычислять, а брать из настроек сервера. Она должна быть уникальной для сервера, а не вычисляться.
        0
        > При регистрации юзера выдаем ему зашифрованный файлик с открытым\закрытым ключом и паролем от него. Себе оставляем только открытый ключ.
        Т.е. ключи всё-таки проходят через сервера и их можно заполучить при взломе последнего. Более правильным решением будет генерация ключей на стороне пользователя с последующей сертификацией.

        > Юзер копирует строку в специальную программу, которая по выданному паролю дешифрует закрытый ключ и подписывает нашу случайную строку.
        Вот тут тоже полумера. Я понимаю одноразовые пароли, например на бумажке, которые нужно вводить в форму браузера — доступное решение… но если вы уже используете полноценное приложение — не лучше сразу сделать так, чтобы оно подписывало не случайную строку, а платёжный документ целиком: с реквизитами, временем и прочей требухой. Таким образом можно «математически» и юридически переложить ответственность на пользователя ибо только он может подписать, а мы ничего не знаем — ни паролей ни явок.

        > не сможет потратить его деньги\сменить пароль\мыло\любые другие функции которые вы посчитаете нужным защитить с использованием этого метода.
        Так вроде сработает in-the-middle-attack… перехватываем подписанную строку на сервере и меняем реквизиты по операции — и всё, БД это проглотит, а денюжки уйдут не туда :)

          0
          не сработает — операция уже сформированная лежит ждет подписи
            0
            Если описанная выше схема аутентификации полна, то работает это примерно так:
            1. P-->U: r
            2. U-->P: r, SU( r )
            где r — случайное число, SU( r ) подписанное юзером число, P — payment system

            Тогда возможна самая примитивная реализация MiM атаки:
            1. P-->A: r
            … A-->U: r, SU( r )
            2. U-->A: r
            … A-->P: r,SU( r )
            Вуаля, теперь Adversary = User с точки зрения Payment system, м?
              0
              Эм… если случайное число привязано к платежному документу, то имхо единственный способ повлиять на операцию это mim в момент отправки неподписанной платежки на сервер. Тут вы правы, лучше подписывать весь документ (его хэш) целиком.
                0
                Ну да…

                интересно, а неподписанная платёжка чем аутентифицируется?
                0
                Тут, кстати, у меня маленькая ошибочка из-за неаккуратного копи-паста :) Должно быть так, разумеется:
                1. P-->A: r
                … A-->U: r
                2. U-->A: r, SU( r )
                … A-->P: r, SU( r )
              0
              Кстати, вебмани тоже сама выдает и генерирует клиентские сертификаты)
                0
                По-моему это не совсем так:

                https://wiki.webmoney.ru/wiki/show/%D0%9F%D0%B5%D1%80%D1%81%D0%BE%D0%BD%D0%B0%D0%BB%D1%8C%D0%BD%D1%8B%D0%B9+%D1%81%D0%B5%D1%80%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%82

                Идентификация обеспечивается путем применения закрытого ключа, который генерируется на компьютере пользователя в процессе регистрации, хранится только у владельца персонального цифрового сертификата WM Keeper Light и никогда не передается по сети.
              +1
              А зачем внешняя программа? Завтра придет человек с другой операционной системы — как он воспользуется сервисом?
                +1
                Вообще это изобретение велосипеда.
                Генерировать закрытый ключ должен только клиент, а не сервер чтобы избежать перехвата.
                Тем более что браузер умеет генерировать приватный ключ, которым и надо подписывать все операции.
                  0
                  К сожалению, я не был в курсе механизмов с клиентскими сертификатами на тот момент(2007 год), поэтому пришлось выдумывать свое решение
                    +1
                    Вот понимаете, дело в том, что сейчас половина придуманного вами устарела (она и тогда устарела, но не будем об этом). А люди, что характерно, читают и думают, что так и надо писать.
                      0
                      Походу стоило просто отдельно написать статью об ecdsa)
                  +2
                  Помоему в банках с СМС паролем гараздо удобнее.
                    0
                    Пользователь клиентской программы проверяет её подлинность тоже с помощью подписи? Можете рассказать об этом подробней? Например, какой используется сертификат (купленный VeriSign?) или свой.
                      0
                      Усложняется процедура платежа для конечного клиента — доп.софт постепенно вымирает, хотя и предлагает более защищенное решение.
                        0
                        Как-то запутано всё. По-русски говоря у юзера получается два «ключа» — то есть его пароль при регистрации и некий файлик, который он получает вместе с программкой. И дальше вы начинаете свистопляску с этими двумя «кодами»? Или я не прав?

                        По первой части статьи. Насколько я понимаю, там у вас две физические машины — то есть СКЛ-сервер, открытый только для процедур и веб-сервер, который управляет базой через процедуры?

                        Если так, то для меня (не профи) все не выглядит достаточно защищенным. Трафик в SQL запросах между машинами шифруется? Что мешает взломщику веб-сервера установить на нем прокси и дальше оперировать от имени юзера?
                          0
                          Тот самый ключ в идеале и мешает. Сейчас (да и тогда) это можно было помимо ключа с програмкой организовать при помощи клиентских сертификатов.
                            0
                            Все равно не понимаю, простите, я дилетант в этом, но хочу разобраться.

                            Если злоумышленник получает контроль к серверу, то ему нужен не только пароль, но и ключ с компьютера клиента? Чтобы создать «транзакцию».
                            Что мешает взломавшему веб-сервер использовать существующие транзакции клиента (живые сессии), подменяя в них например платежные реквизиты (назначение) или сумму операции?
                              0
                              По поводу шифрации SQL подскажите пожалуйста.

                              И еще сразу, пока вы не ответили. Если мы взломали веб-сервер, то есть фактически поставили свой веб-сервер (сайт) на место настоящего. Что мешает нам самим генерировать одноразовую строку? Где она создается — на компьютере с базой данной или на компьютере с веб-сервером?

                              Где вбиваются платежные реквизиты? Что мешает нам на сайте показывать юзеру одни платежные реквизиты, но заставлять его генерировать строчку под поддельные реквизиты? Другими словами, юзеру мы показываем 50 рублей, а в input формы у нас стоит 500 рублей, которые уходят на подпись в базу данных для временной строки? И мы юзеру даем подписать уже платеж на скрытые 500 рублей?
                                0
                                Одноразовая строка генерится хранимкой в БД, но это фиг с ним. Да, на самом деле выползает такая штука что можно подменить реквизиты платежа. Поэтому, как уже было замечено выше, лучше подписывать весь платежный документ (его хэш), а не одноразовую строку.
                                  0
                                  То есть вы признаете что я могу взломать вашу систему просто взломав  WWW?
                                    0
                                    Вы — вряд ли)
                                      0
                                      Почему? Что меня остановит? Где подписывается строка? Какие поля есть у юзера в программе на компе?
                                        0
                                        строка подписывается на клиенте программой… Взломать единичную операцию да, можно, подменив данные документа в момент его формирования (и не факт что строку для него подпишут, кстати)и только в тот момент когда пользователь решит оплатить что либо. Да и к тому времени скорей всего дыры будут уже закрыты а хацкер изгнан с системы. Поменять подпись случайной строки на подпись хэша документа дело максимум часа
                                          0
                                          То есть в течение часа я могу грабить ваших клиентов, правда?
                                        0
                                        Я вижу две уязвимости по крайней мере. В любых сценариях. Вы не ответили по поводу шифрации команд SQL от ВВВ до базы данных.
                                          0
                                          «шифрация» sql команд тут не нужна(сервера друг с другом напрямую соединены) и не поможет при взломе веб сервера
                                            0
                                            Чем соединены? Машрутизатором, хабом, циской? Сетевая в сетевую?
                                              0
                                              сетевая в сетевую
                                                0
                                                Еще как-то друг друга видят? Если я отключу интерфейс на ВВВ и прокину маршрут через свой роутер, что произойдет?
                                                  0
                                                  Вы название статьи читали? Немножко не в ту тему мне кажется пишете
                                                    0
                                                    Почему не в ту? Вы описали двузвенную структуру и сказали что мне не хватит рута на WWW. Я же вижу, что мне хватит рута полностью и еще мне хватит врезаться между двумя серверами. Возможно я ошибаюсь, еще раз говорю, я не специалист в платежных системах, просто интересно.
                                                      0
                                                      если будет цифровая подпись документа то вам и двух рутов не хватит
                                                        0
                                                        Я вообще вижу что мне не нужен ни один ключ. Ни в базе, ни у пользователя. То есть достаточно подредактировать public_html.
                                                          0
                                                          ага, и бд, проверяющая ЭЦП, помахает вам ручкой, когда подпись оригинального документа(которую формирует на своем компе пользователь) не совпадет с подписью сформированного вами документа. Тут уж хоть вклинивайтесь хоть не вклинивайтесь. Учите матчасть.
                                                            0
                                                            В том и дело что я матчасть не знаю. Поэтому иду по простому пути. Я ставлю прокси на WWW и заставляю юзера подписывать фальшивые чеки. Вернее даю юзеру строчки от других чеков. А сам дальше работаю с базой. Что меня остановит кроме совести?
                                                              0
                                                              Юзер по определению видит что подписывает. В клиентской программе (мы уже рассматриваем вариант с подписью документа) отобразятся все поля, начиная от назначения платежа и заканчивая датой и суммой. Как их заполнять — дело техники, можно автоматом а можно и руками. Разве что при автоматическом заполнении он лишний нолик не заметит, но это уже его проблемы
                                                                0
                                                                Я смотрю вы обновили статью. Надо подумать. Давайте посмотрим. Я владею ВВВ. Создаю лживый чек с правильной суммой. Даю этот чек юзеру в программу. Что остановит юзера?
                                                                  0
                                                                  Если юзер не совсем дурак (примем это за точку отсчета), то он увидит, например, что платеж был на 50 рублей, а стал на 100, или платил он за пчелайн, а тут ему предлагается МТС, да еще и номер телефона левый. Конечно есть вероятность что он этого НЕ заметит, но если заметит то увы и ах, не видать вам его цифровой подписи без которой вы можете хоть удолбиться в сервер бд: Он ничего вам не даст
                                                                    0
                                                                    Тут довольно просто. Я ввожу в систему подставную фирму МТС (заменяя последнюю букву на Ц), и выписываю юзеру в его сессии лживый чек на свою фирму. Чек валидный, он для него им же создан. Что его остановит?
                                                                      0
                                                                      Вы не введете в систему подставную фирму МТС. У вас рут от веб сервера, а не от БД
                                                                        0
                                                                        Почему нет? Кто меня остановит? Я по-белому введу себя как получатель, в вашем офисе. Назову себя МТС Консалт. Мобильные теле-системы для малого бизнеса. Мобильный кошелек. Как угодно.

                                                                        Безопасность строится не только в компьютере, но и вокруг него.
                                                                          0
                                                                          Уверяю вас, те, кто будут забивать название получателя денег в бд позаботятся о том, чтобы не было разночтений с существующими контрагентами. К тому же, если выяснится, что хакер работает на одного из наших контрагентов, за жопу возьмут обоих. У вас поинтереснее аргументы остались? Так то можно и со стволом в офис придти и заставить выдать все пароли от сервера. Тоже к компьютерной безопасности и криптографии это приписывать будете?
                                                                            0
                                                                            Да нифига они не позаботятся. Вообще я тут не беру фактическую ситуацию. По факту бы просто в течение часа слили все деньги на ИЧП и скрылись в неизвестном направлении. Вернее в направлении каких-нибудь южных морей.
                                                                              0
                                                                              попутного ветра!
                                                                                0
                                                                                Предлагаю остановить обсуждение на этой точке. Я не желаю вам зла и на самом деле ни являюсь ни хакером, ни специалистом в платежных системах.

                                                                                Хотя в качестве рекламы могу сообщить, что я принимаю заказ на анализ чужих платежных систем, буквально вчера имел наглость составить такое коммерческое предложение. Смотрите мой профиль — кому интересно.
                                                                                  0
                                                                                  >>Хотя в качестве рекламы могу сообщить,
                                                                                  >>что я принимаю заказ на анализ чужих >>платежных систем

                                                                                  >>и на самом деле ни являюсь ни хакером,
                                                                                  >>ни специалистом в платежных системах.

                                                                                  Удачи )
                                                                                +1
                                                                                По факту, для регистрации ИЧП вы 5 дней регистрировались в налоговой, после чего приходили в банк.
                                                                                И вывод разовый БОЛЬШОЙ суммы денег без предупреждения банка событие маловероятное. Так что минимум на сутки Вас в банке остановят, а за это время ваши фотки будут уже легко лежать на всех пограничных терминалах..:)…
                                                                        0
                                                                        Сумма та же. 50 рублей.
                                  0
                                  Обсуждение взлома. Топик гадкий, но интересный.
                                  habrahabr.ru/blogs/pay_sistem/96438/#comment_2953781
                                    0
                                    точнее обсуждение как я лажанулся :-)
                                    топик глупый, но смешной.
                                    0
                                    Я не понимаю смысла вашего цикла статей, который описывает весьма кривую архитектуру, написанную вами 3 года назад, в комментариях которой на каждый указанный вам ляп архитектуры вы жалуетесь, что это придуманно 3 года назад.
                                      0
                                      Смысл хотя бы в том чтобы не совершать подобных ошибок.
                                        0
                                        Тогда вы и говорите о проблемах этой архитектуры. А то боюсь некоторые наивные пользователи могут счесть эти статьи руководством к действию.
                                          0
                                          в апдейте я указал, что имеется возможность атаки и нужно действовать иначе. К тому же подезно читать каменты.

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

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