Some aspects of the development of payment systems. Part II One time passwords and ECDSA

    Good health,% username%!

    In the first part, I talked about how to protect the database of our payment system with minimal efforts. But, as one of the commentators noted , when a web server is compromised, it becomes possible to spy on all user logins and passwords. Here One time passwords (OTP) come to our aid.
    Under the cut, my free interpretation of the term using elliptic curve cryptography (ECC). By the way, payment systems are far from the only sphere of application of this technology.
    Upd:
    Achtung! When hacking a web server, there is still the possibility of substitution of payment details, so it’s better not to sign a random string (although this will protect against complete compromise of the system, it will not protect against cases when the details are changed directly during payment), and the hash of the payment document , showing the user at the same time all the details of the payment in the program.
    Z.Y. It is better to generate a key on the client side too.

    In order not to come up with one-time cards with passwords, it was decided to use EDS. The case just turned up to use the elliptic curves so loved by me because of their reliability and speed.
    The scheme was as follows:
    1. When registering a user, we give him an encrypted file with the public / private key and password from it. We leave only the public key to ourselves.
    2. When the user wants to make a payment or money transfer, we randomly generate a line that we show him. We save it in the database with reference to the user.
    3. The user copies the string to a special program, which, according to the issued password, decrypts the private key and signs our random string.
    4. The user gives us the signature, we verify it with the public key and give the go-ahead for the operation.


    It was necessary to write external storage for MySQL and related programs. As a crypto library, the good old OpenSSL was chosen. As a result of 2 days without sleep, a working version of:
    1. A program that generates a key pair and places it in the database (written on Builder).
    2. Programs for the user generating a digital signature (on it).
    3. External storage, checking this digital signature (the user needs an answer instantly, the programs do not roll). Wrote on VC.


    I warn you right away: the code does not sparkle with beauty, C is not my main language.

    Now point by point:

    1) Generate a key
      const KEYSIZE = SHA512_DIGEST_LENGTH + 4; // 4 bytes per salt
      unsigned char * pubkey = (unsigned char *) OPENSSL_malloc (10000),
      * privkey = (unsigned char *) OPENSSL_malloc (10000); // buffers for public and private keys
      AnsiString pub, prv, userid, paypass; // lines for storing the public, private keys and password from them
      unsigned char md [KEYSIZE] = {0}; // there will be a password hash
      unsigned int i = ParamCount ();

      paypass = "rAnDom_PaSs";

      EC_GROUP * group = EC_GROUP_new_by_curve_name (NID_sect571r1); // select the elliptic curve
      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 ()); // We will write in memory

      // Code to slow down the brute force

      env_md_ctx_st mdctx; // Context for the hash
      EVP_MD_CTX_init (& mdctx);
      EVP_DigestInit_ex (& mdctx, EVP_sha512 (), NULL); // Hash algorithm - SHA512
      EVP_DigestUpdate (& mdctx, paypass.c_str (), strlen (paypass.c_str ())); // Hash the password for the first time

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

        memcpy (md, mdctx.md_data, SHA512_DIGEST_LENGTH); // copy the hash into the array

        md [64] = i ^ md [0] ^ md [7] ^ md [5] ^ md [23]; // calculate the additional 4 bytes
        md [65] = i ^ md [1] ^ md [9] ^ md [6] ^ md [53]; // based on the previous hash
        md [66] = i ^ md [3] ^ md [25] ^ md [11] ^ md [48]; // they will be the so-called round salt
        md [67] = i ^ md [8] ^ md [18] ^ md [17] ^ md [2];

        EVP_DigestUpdate (& mdctx, md, KEYSIZE); // and hash the previous hash + salt
      }

      EVP_DigestFinal (& mdctx, md, NULL); // finish
      reading the hash EVP_MD_CTX_cleanup (& mdctx);

      EC_KEY_generate_key (x); // generate the key pair

      PEM_write_bio_ECPrivateKey (out, x, EVP_aes_256_cbc (), md, KEYSIZE, NULL, NULL); // write a key pair encrypted using the AES-256 algorithm with the key calculated in the previous step
      BIO_flush (out);
      i = BIO_read (out, privkey, 10000); // find out the number of bytes written

      // for accuracy, squeeze 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 ()); // another buffer for the public key
      PEM_write_bio_EC_PUBKEY (out, x); // write it to memory in the format PEM
      BIO_flush (out);
      BIO_read (out, pubkey, 10000); // read it into a piece of memory

        // Magic code to copy the private encrypted key into an array of bytes
      TByteDynArray cp;
      cp.set_length (comprLenPrv);

      for (i = 0; i <comprLenPrv; i ++) {
        cp [i] = comprPrv [i];
      }
        
        // copy the public key into the string
      AnsiString pubk;
      pubk.sprintf ("% s", pubkey);

        
        // free the memory
      free (comprPrv);
      OPENSSL_free (pubkey);
      BIO_free (out);
      EC_KEY_free (x);  


    2) We sign on the client with this key a random string issued by the server
    ...
    unsigned long i;
    unsigned char * x;
    unsigned char buf [1024] = {0};

    // Sign the line with the key. key - read and decoded key from item 1. s - random line.
    ECDSA_SIG * sig = ECDSA_do_sign (s.c_str (), s.Length (), key);

    x = buf;
    i = i2d_ECDSA_SIG (sig, & x); // convert to binary
    x = buf;
    ECDSA_SIG_free (sig);
    // so that the signature can be copied - convert it to base64
    BIO * b64 = BIO_new (BIO_f_base64 ());
    BIO_set_flags (b64, BIO_FLAGS_BASE64_NO_NL); // without line
    feeds 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; // print the result to the user.
    .

    3) Verify the signature
    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); // read the public key
        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); // read the signature

        i = ECDSA_do_verify ((const unsigned char *) str, (int) strlen (str), sig, ec); // check the signature of the string
        BIO_free_all (mem);
        ECDSA_SIG_free (sig);
        return i;
    }
    .

    Thus, even if a hacker gains access to a user’s account, he will not be able to spend his money \ change password \ soap \ any other functions that you consider necessary to protect using this method.
    And if the key is stolen, then thanks to the password hash cycle with the salt 0x20000 (131072), even simple passwords will be swept brute-force.

    Also popular now: