📜 ⬆️ ⬇️

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

Good health,%% username!

In the first part, I told you as little as possible to protect the database of our payment system. But, as one of the commentators noted , when a web server is compromised, it is possible to peep all user logins and passwords. This is where One time passwords (OTP) comes to our rescue.
Under the cut is my free interpretation of this term using elliptic curve cryptography (ECC). By the way, payment systems are far from the only field 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 the system from being completely compromised, but it doesn’t protect it from cases when the details are replaced right during the payment), and the hash of the payment document , showing the user with all the details of the payment program.
ZY Generating a key is also better on the client side.

In order not to invent disposable password cards, it was decided to use EDS. I just turned up the occasion to use the elliptic curves so loved by me because of its reliability and speed.
The scheme was as follows:
  1. When registering a user, we give him an encrypted file with an open / private key and a password from it. We leave only the public key.
  2. When the user wants to make a payment or money transfer, we randomly generate a string that we show him. We save it in a DB with a binding to the user.
  3. The user copies the string into a special program that, using the password given, decrypts the private key and signs our random string.
  4. The user gives us the signature, we check it with the public key and give the go-ahead for the operation.


It was necessary to write an external storage for MySQL and related programs. The good old OpenSSL was chosen as the crypto-library. As a result, 2 days without sleep, the working version of:
  1. Programs that generate a key pair and put it in the database (written in Builder).
  2. Programs for the user, generating a digital signature (on it).
  3. External storage, this digital signature verifier (the user needs an answer instantly, the program does not roll). Posted by vc.

')
Immediately I warn you: the code does not shine with beauty, C is not my main PS.

Now the points:

1) Generate the key
const KEYSIZE = SHA512_DIGEST_LENGTH + 4; // 4 bytes on the "salt"
unsigned char * pubkey = (unsigned char *) OPENSSL_malloc (10000),
* privkey = (unsigned char *) OPENSSL_malloc (10000); // public and private key buffers
AnsiString pub, prv, userid, paypass; // strings for storing public, private keys and their password
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); // choose an 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 hash
EVP_MD_CTX_init (& mdctx);
EVP_DigestInit_ex (& mdctx, EVP_sha512 (), NULL); // Hashing Algorithm - SHA512
EVP_DigestUpdate (& mdctx, paypass.c_str (), strlen (paypass.c_str ())); // Hash password for the first time

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

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

md [64] = i ^ md [0] ^ md [7] ^ md [5] ^ md [23]; // calculate additional 4 bytes
md [65] = i ^ md [1] ^ md [9] ^ md [6] ^ md [53]; // based on previous hash
md [66] = i ^ md [3] ^ md [25] ^ md [11] ^ md [48]; // they will be 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 counting the hash
EVP_MD_CTX_cleanup (& mdctx);

EC_KEY_generate_key (x); // generate key pair

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

// for fidelity let's compress 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 PEM format
BIO_flush ( out );
BIO_read ( out , pubkey, 10,000); // read it in a piece of memory

// Magic code to copy the private encrypted key to the byte array
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);


// release 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 - rendome line.
ECDSA_SIG * sig = ECDSA_do_sign (s.c_str (), s.Length (), key);

x = buf;
i = i2d_ECDSA_SIG (sig, & x); // convert to binary form
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); // no line breaks
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; // output the result to the user.
.

3) We check 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 string signature
BIO_free_all (mem);
ECDSA_SIG_free (sig);
return i;
}
.

Thus, even if the hacker gets access to the 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 hashing cycle with salt 0x20000 (131072), even simple passwords will get stuck once.

Source: https://habr.com/ru/post/96116/


All Articles