What's the best approach for generating a new API key?
Asked Answered
D

11

148

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.

Dropline answered 19/1, 2013 at 7:26 Comment(1)
I have answered in more details here.. generating keys and using it as hmac authTwocycle
V
89

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);
Viscera answered 11/9, 2013 at 0:30 Comment(12)
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.Calica
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.Viscera
Nice solution, but you need var keyword before apiKey probably :) var apiKey = Convert.ToBase64String(key);Ucayali
@JohnM2 Right. I had been leaving it open-ended, since apiKey could be declared elsewhere. I added the type for clarity.Viscera
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.Runin
@Runin Do I need to hash or encrypt API keys before storing them in a database?Viscera
@JamesWierzba if the attacked is already in your database, then them having unsecured access to your API is probably the least of your concerns...Montoya
@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.Galway
@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.Viscera
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.Galway
@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.Viscera
@EdwardBrey even if (when) they're stored together, I think it's still a massive advantage over an attacker.Galway
S
38

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.

Sangfroid answered 23/6, 2014 at 14:2 Comment(5)
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.Caul
@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.Sangfroid
@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?Asis
@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.Sangfroid
"Hashing doesn't prevent forgery." So, why exactly am I hashing all these passwords? Oh, and there is the answer. Passwords are supposed to be difficult to guess. This answer uses easily guessable tokens. Well, yes, if they are easily guessable then signing is essential. Otherwise, and assuming you are not trusting additional data embedded in the token, signing is a waste of CPU.Boorer
B
17

2023 Note: In Chrome, the default new tab page does not allow the use of the cryptography module in the console, so please use a different page.

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 on any site (see 2023 note at the top of this answer), 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...

Britishism answered 2/2, 2022 at 20:13 Comment(0)
O
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.

Osiris answered 20/1, 2013 at 8:55 Comment(3)
Do not assume that UUIDs are hard to guess; they should not be used as security capabilities (UUID spec RFC4122 section 6). An API key needs a secure random number, but UUIDs are not securely unguessable.Viscera
@EdwardBrey what about UUID uuid = UUID.randomUUID(); in Java? Are you saying that random is not good enough?Counterrevolution
@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, 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.Viscera
V
8

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();
}
Viscera answered 20/6, 2018 at 20:35 Comment(0)
B
5

In the terminal, you can use openssl like so:

openssl rand -hex 32

Example output:

1e846f3fcf103f64ca10fa4eac73bfae32ef10750bf4eae29132dc099526c561
Buddybuderus answered 12/4, 2023 at 12:50 Comment(0)
P
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.

Phototypy answered 20/1, 2013 at 9:26 Comment(1)
Windows 2000 switched to GUIDs using random numbers. 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.Viscera
D
1

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
Duplessismornay answered 7/5, 2023 at 14:5 Comment(0)
T
1

I'd like to add an implementation in Java, that uses class KeyPairGenerator to gen a public key using RSA, then convert it to base64. Lastly you can also pretty your API key by eliminating all the slash char and extract only the last part of the generated public key.

import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;

public static String generateMyAPIKey() throws NoSuchAlgorithmException {
   KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
   keyGen.initialize(1024);
   byte[] publicKey = keyGen.genKeyPair().getPublic().getEncoded();
   String base64Binary = DatatypeConverter.printBase64Binary(publicKey).replaceAll("/", "");

   return base64Binary.substring(base64Binary.length() - 32);
}
Toting answered 1/4 at 9:15 Comment(0)
B
0

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())
Boarfish answered 22/7, 2023 at 7:3 Comment(0)
B
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
Bruges answered 26/7, 2023 at 11:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.