137

So with lots of different services around now, Google APIs, Twitter API, Facebook API, etc etc.

Each service has an API key, like:

AIzaSyClzfrOzB818x55FASHvX4JuGQciR9lv7q

All the keys vary in length and the characters they contain, I'm wondering what the best approach is for generating an API key?

I'm not asking for a specific language, just the general approach to creating keys, should they be an encryption of details of the users app, or a hash, or a hash of a random string, etc. Should we worry about hash algorithm (MSD, SHA1, bcrypt) etc?

Edit: I've spoke to a few friends (email/twitter) and they recommended just using a GUID with the dashes stripped.

This seems a little hacky to me though, hoping to get some more ideas.

Piper
  • 1,266
  • 3
  • 15
  • 26
Phill
  • 18,398
  • 7
  • 62
  • 102
  • I have answered in more details here.. [generating keys and using it as hmac auth](https://stackoverflow.com/questions/55009503/how-services-generate-and-use-public-and-secret-api-keys/61301438#61301438) – Anshu Kumar Apr 19 '20 at 08:33

10 Answers10

84

Use a random number generator designed for cryptography. Then base-64 encode the number.

This is a C# example:

var key = new byte[32];
using (var generator = RandomNumberGenerator.Create())
    generator.GetBytes(key);
string apiKey = Convert.ToBase64String(key);
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
  • 4
    This is not very secure, an attacker that gains access to your database could obtain the key. It would be better to generate the key as a hash of something unique to the user (like a salt), combined with a server secret. – James Wierzba Feb 24 '17 at 16:47
  • 23
    Storing a randomly generated API key has the same security characteristics as storing a hashed password. In most cases, it's fine. As you suggest, it is possible to consider the randomly generated number to be a salt and hashing it with a server secret; however, by doing so, you incur the hash overhead on every validation. There is also no way to invalidate the server secret without invalidating all API keys. – Edward Brey Feb 24 '17 at 16:56
  • Nice solution, but you need var keyword before apiKey probably :) var apiKey = Convert.ToBase64String(key); – Dawid Ohia Mar 01 '18 at 15:52
  • @JohnM2 Right. I had been leaving it open-ended, since `apiKey` could be declared elsewhere. I added the type for clarity. – Edward Brey Mar 01 '18 at 16:21
  • I hope this is a sensible question: do you recommend storing a one-way hash of a generated apiKey in the database, just as is done for user-supplied passwords? Then an adversary that reads the database still cannot easily impersonate the actual user AFAIK. – chrisinmtown Jul 18 '18 at 12:32
  • @chrisinmtown [Do I need to hash or encrypt API keys before storing them in a database?](https://security.stackexchange.com/q/180345/39887) – Edward Brey Jul 18 '18 at 12:59
  • 30
    @JamesWierzba if the attacked is already in your database, then them having unsecured access to your API is probably the least of your concerns... – Jon Story Apr 27 '20 at 18:07
  • 6
    @EdwardBrey not quite the same characteristics. Someone who reads the database with the API key in it now has a valid API key. Someone who reads a hashed password cannot use that hash as a password. – Rob Grant May 19 '20 at 14:19
  • @RobGrant You can use a hashed password by writing a fake app that pretends to be the real app. Whereas the real app would hash the user's password, the fake app just uses the pre-hashed password. – Edward Brey May 19 '20 at 22:33
  • Point being that a database leak, where an attacker got hold of your database data, can not now start hitting your API with valid credentials. – Rob Grant Aug 04 '23 at 13:04
  • 1
    @RobGrant Good point. The server can give the API key to the application but store a hash of it in the database. When authenticating, the server can hash the presented API key and verify that the hash matches the hash in the database. So long as the salt used for hashing is stored separately from the hash, such that an attacker is unlikely to obtain both the hash and salt, a leak of a hash does not grant an attacker API access. – Edward Brey Aug 06 '23 at 02:57
  • @EdwardBrey even if (when) they're stored together, I think it's still a massive advantage over an attacker. – Rob Grant Aug 07 '23 at 12:07
35

API keys need to have the properties that they:

  • uniquely identify an authorized API user -- the "key" part of "API key"
  • authenticate that user -- cannot be guessed/forged
  • can be revoked if a user misbehaves -- typically they key into a database that can have a record deleted.

Typically you will have thousands or millions of API keys not billions, so they do not need to:

  • Reliably store information about the API user because that can be stored in your database.

As such, one way to generate an API key is to take two pieces of information:

  1. a serial number to guarantee uniqueness
  2. enough random bits to pad out the key

and sign them using a private secret.

The counter guarantees that they uniquely identify the user, and the signing prevents forgery. Revocability requires checking that the key is still valid in the database before doing anything that requires API-key authorization.

A good GUID generator is a pretty good approximation of an incremented counter if you need to generate keys from multiple data centers or don't have otherwise a good distributed way to assign serial numbers.


or a hash of a random string

Hashing doesn't prevent forgery. Signing is what guarantees that the key came from you.

Mike Samuel
  • 118,113
  • 30
  • 216
  • 245
  • 6
    Is the signing step of your algorithm necessary if the API key presented by a client is checked against a database of already registered API keys on the server providing the API? Seems like signing would be redundant here if the server is the one providing keys. – sappenin Feb 27 '15 at 17:13
  • 5
    @sappenin, Yes. If you store an unguessable key on the server, then you don't need to prevent forgery. Often API requests are handled by any one of a farm of machines -- **the** server is one of many servers. Signature checking can be done on any machine without a round-trip to a database which can avoid race conditions in some cases. – Mike Samuel Feb 27 '15 at 21:04
  • 3
    @MikeSamuel if API key is signed and you don't do a round trip to Database then what happens when the key is revoked but still used to access the API? – Abhyudit Jain Feb 01 '17 at 07:56
  • @AbhyuditJain, In any distributed system, you need a consistent message order (revocations *happen-before* subsequent uses of revoked credentials) or other ways to bound ambiguity. Some systems don't round-trip on every request -- if a node caches the fact that a key was in the database for 10 minutes, there's only a 10 min. window in which an attacker can abuse a revoked credential. Possible confusion can result though: user revokes a credential, then tests that it's revoked, and is surprised because non-sticky sessions cause the two requests to go to different nodes. – Mike Samuel Feb 01 '17 at 20:25
14

Update, in Chrome's console and Node.js, you can issue:

crypto.randomUUID()

Example output:

'4f9d5fe0-a964-4f11-af99-6c40de98af77'

Original answer (stronger):

You could try your web browser console by opening a new tab, hitting CTRL + SHIFT + i on Chrome, and then entering the following immediately invoked function expression (IIFE):

(async function (){
  let k = await window.crypto.subtle.generateKey(
    {name: "AES-GCM", length: 256}, true, ["encrypt", "decrypt"]);
  const jwk = await crypto.subtle.exportKey("jwk", k)
  console.log(jwk.k)
})()

Example output:

gv4Gp1OeZhF5eBNU7vDjDL-yqZ6vrCfdCzF7HGVMiCs

References:

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/generateKey

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/exportKey

I'll confess that I mainly wrote this for myself for future reference...

Chris Chiasson
  • 547
  • 8
  • 17
8

I use UUIDs, formatted in lower case without dashes.

Generation is easy since most languages have it built in.

API keys can be compromised, in which case a user may want to cancel their API key and generate a new one, so your key generation method must be able to satisfy this requirement.

Adam Ralph
  • 29,453
  • 4
  • 60
  • 67
  • 22
    Do not assume that UUIDs are hard to guess; they should not be used as security capabilities (UUID spec [RFC4122 section 6](http://tools.ietf.org/html/rfc4122#section-6)). An API key needs a secure random number, but [UUIDs are not securely unguessable](http://stackoverflow.com/q/3652944/145173). – Edward Brey Jun 23 '14 at 13:24
  • 4
    @EdwardBrey what about `UUID uuid = UUID.randomUUID();` in Java? Are you saying that random is not good enough? – Micro Feb 07 '16 at 14:44
  • 10
    @MicroR A random UUID is secure only if the random number generator used to make it is cryptographically secure and 128 bits are sufficient. Although the UUID RFC does not require a secure random number generator, a given implementation is free to use one. In the case of [randomUUID](https://docs.oracle.com/javase/6/docs/api/java/util/UUID.html#randomUUID()), the API docs specifically state that it uses a "cryptographically strong pseudo random number generator". So that particular implementation is secure for a 128-bit API key. – Edward Brey Feb 07 '16 at 21:27
7

If you want an API key with only alphanumeric characters, you can use a variant of the base64-random approach, only using a base-62 encoding instead. The base-62 encoder is based on this.

public static string CreateApiKey()
{
    var bytes = new byte[256 / 8];
    using (var random = RandomNumberGenerator.Create())
        random.GetBytes(bytes);
    return ToBase62String(bytes);
}

static string ToBase62String(byte[] toConvert)
{
    const string alphabet = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
    BigInteger dividend = new BigInteger(toConvert);
    var builder = new StringBuilder();
    while (dividend != 0) {
        dividend = BigInteger.DivRem(dividend, alphabet.Length, out BigInteger remainder);
        builder.Insert(0, alphabet[Math.Abs(((int)remainder))]);
    }
    return builder.ToString();
}
Edward Brey
  • 40,302
  • 20
  • 199
  • 253
1

An API key should be some random value. Random enough that it can't be predicted. It should not contain any details of the user or account that it's for. Using UUIDs is a good idea, if you're certain that the IDs created are random.

Earlier versions of Windows produced predictable GUIDs, for example, but this is an old story.

Dave Van den Eynde
  • 17,020
  • 7
  • 59
  • 90
  • 5
    Windows 2000 switched to GUIDs using [random numbers](http://en.wikipedia.org/wiki/UUID#Version_4_.28random.29). However, there is no guarantee that the random numbers can't be predicted. For example, if an attacker creates several API keys for himself, it may be possible to determine a future random number used to generate another user's API key. In general, [do not consider UUIDs to be securely unguessable](http://stackoverflow.com/q/3652944/145173). – Edward Brey Jun 23 '14 at 13:51
1

In the terminal, you can use openssl like so:

openssl rand -hex 32

Example output:

1e846f3fcf103f64ca10fa4eac73bfae32ef10750bf4eae29132dc099526c561
Victor
  • 3,081
  • 2
  • 18
  • 20
0

I liked Chris Chiasson's use of crypto.subtle in a browser, but wanted a command-line version. If you have NodeJS installed, this can be saved in a file (e.g., mkapikey.js):

#!/usr/bin/env node
crypto.subtle.generateKey({name:"AES-GCM", length:256},true,
["encrypt","decrypt"]).then(key => {crypto.subtle.exportKey("jwk",key).
then(jwk => {console.log(jwk.k)})});

Either chmod +x to an executable or invoke with node mkapikey.js.

chmod +x mkapikey.js
./mkapikey.js
4SvnAK87DD0t3WMS878UCY-obmADtPeBn6X4gFOtUig
Joe
  • 2,352
  • 20
  • 38
0

Yet another more updated version of previous answers - but more compact and new-javascripty than before!

#!/usr/bin/env node
const { subtle } = require('crypto').webcrypto

subtle
  .generateKey({ name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'])
  .then(key => subtle.exportKey('jwk', key))
  .then(jwk => console.log(jwk.k))

Run:

$ node /tmp/genkey.js
Mtra_qEFS7F76HrpgDAP2rBsb4pJ4w2hTL8UUyxalRA
Andrew E
  • 7,697
  • 3
  • 42
  • 38
-1

One popular way is to generate a random string using a cryptographically secure pseudo-random number generator (CSPRNG) and then encode this string with base64 encoding. This can provide a high level of security, as the keys are difficult to guess, and can be of virtually any length.

The other approach, as your friends suggested, is to use a GUID/UUID. It's true that some might find this to be a little "hacky", but in practice, it works well.

As for the hashing algorithms, if you choose to go the hashing route, it's generally recommended to use a strong algorithm like SHA256 or SHA3. Algorithms like MD5 and SHA1 are considered to be broken and should not be used for new systems.

Here is the Python code:

import secrets
import base64

def generate_api_key():
    # Generate 32 random bytes
    random_bytes = secrets.token_bytes(32)

    # Convert those bytes into a URL-safe base64 string
    api_key = base64.urlsafe_b64encode(random_bytes).decode("utf-8")

    return api_key

print(generate_api_key())
Eric Aya
  • 69,473
  • 35
  • 181
  • 253