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:
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:
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
2) We sign on the client with this key a random string issued by the server
3) Verify the signature
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.
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:
- 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.
- 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.
- The user copies the string to a special program, which, according to the issued password, decrypts the private key and signs our random string.
- 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:
- A program that generates a key pair and places it in the database (written on Builder).
- Programs for the user generating a digital signature (on it).
- 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.