9
votes

Google, Stripe and many other companies have public API key and Secret API key.

It is easy to generate random strings but my question is, how can I generate public and secret keys, store them and use them properly?

The public API key is to tell who the user is and the secret is to confirm their identity.

My flow is as follow: - User create an account - User activates a service (in-house) - The service return a public and a secret API key (UARRHAtPtJcLxx5RmMWo9oTrca4gRt2k, C9YS7Mhzichq2vqBuRkNJxkNci5W2Xua) - User use the public key on his/her website and the private key on the server-side

I am using nodejs and the public key is generated on demand, when the user asks for an API key:

let public = await crypto.randomBytes(32).toString('base64');

Storing the secret in a database would be like storing password in plaintext. I presume we do not want this and it needs to be hashed somehow. Do I generate a "private" key and hash it using argon2 for example? The user will never be able to see his/her key again and will need to save it right away, is this good practice?

I couldn't find much information on how this is suppose to work.

2
Did you find solution?NIKHIL C M
No, I didn't continue my research for that. If you come up with anything, please share.HypeWolf
Key management is an extensive topic. Storing the key is one decision point in the overall framework of managing a key. Have a look at cheatsheetseries.owasp.org/cheatsheets/… and NIST docs referenced therein.identigral

2 Answers

8
votes

Technically what you are referring to are just a username and a password. The only important difference is these are typically generated by the API and very random, as opposed to a real username and password which are chosen by a user, and usually not very random. (Calling these public and private keys is a little misleading as public key cryptography is different - you don't typically need that for API keys, managing a PKI is a can of worms, and also very costly to do it properly.)

As these are technically the same as a username and a password, you want to treat them similarly. Let's call these client id (the "public" part) and client key (the "secret" part).

A few thoughts:

  • You should use a cryptographically secure random generator to generate random strings. crypto.randomBytes() as above is fine.
  • You should consider entropy to set an appropriate length for the key. Entropy is basically the "randomness" of your keys, and is measured in bits. Just for an example, if the key space is say 1024 different possible keys with equal probabilities, then you can say the key has log2(1024) = 10 bits of entropy. Entropy decouples the length of the key from the security of the random source, for example you can have very long keys which are still not secure, because the random source is flawed. How much entropy you want for your keys depends on the usecase, like if offline attacks are possible in any scenario or online requests only and so on (you should likely count with offline attacks too, which are very fast). As a rule of thumb, you should not go below 128 bits of entropy, and for higher security you should probably go 256+ bits. If the key is case sensitive and alphanumeric, then there are 62 different possible characters, a key length of 22 provides ~131 bits (log2(62^22) =~ 130.99). You can always go longer of course, for 256 bits you would need a length of 43 with case sensitive alphanumeric.
  • As you correctly noted, storing such keys is critical. At the very least, you would want to store them hashed (with an appropriate hash, see below), just like any other password, so that even if an attacker gains access to your database, they won't get to see your api keys. Any proper key derivation function (Argon2, bcrypt, PBKDF2 and so on) is fit for this purpose, but plain crypto hash functions (sha1, sha2 and so on) are not.
  • You are right that if you hash these secrets, the user will only be able to see them when they are generated and never again. That's exactly what happens in secure online services, and is generally a good practice. You can warn your user to take note of the secret because you will not be able to show them again. (Also they can ideally generate a new one if they forgot it, so it's usually not a big deal.)
  • Even better would be to store these keys somewhere else. Thinking about cloud, a good place for such secrets would be a service provided by your cloud provider, like for example Secrets Manager in AWS. The benefits include encryption with a KMS key, auditing, and you can make sure that the only way to access your keys is through appropriate IAM roles (you don't need to worry about things like a disclosed backup for example).
  • While the client id is not a secret, you need to protect its integrity (and the integrity of the association with a client secret) in your storage. Imagine a scenario where an attacker can somehow alter your database and assign a different (attacker-known) client secret to an existing client id. That would mean a total compromise of data associated with that client id. So you want to make sure that besides keeping the clietn secret secure, it is also not possible for an attacker to change those (via for example access control to components involved).
1
votes

I think we can generate pair of public key and secret key (private key) using the below code..you can refer the link link to generate key pair here doc

var pk="";
var sk="";
var string= payload;
const { generateKeyPair } = require('crypto');
generateKeyPair('rsa', {
      modulusLength: 4096,
      publicKeyEncoding: {
        type: 'spki',
        format: 'pem'
      },
      privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem',
        cipher: 'aes-256-cbc',
        passphrase: 'top secret'
      }
    }, (err, publicKey, privateKey) => {
      try {
          pk=publicKey;
          sk=privateKey;

      } catch (error) {
          console.log(err)

      }
    });

now we have secret key and public key....so we can implement HMAC authentication...ref..go for hmac authentication doc


var hmac = crypto.createHmac('sha384', sk).update(string).digest('hex');

request.post({uri:..., json: { hmac, pk, string }, function(err, response, body) {
   console.log(body);
});