As allways, the best way to protect a key is not to transmit it.
That said, we typically use a scheme, where every "API key" has two parts: A non-secret ID (e.g. 1234) and a secret key (e.g. byte[64]).
- If you give out an API key, store it (salted and hashed) in you
service's database.
- If you give out user accounts (protected by password), store the
passwords (salted and hashed) in your service's database
Now when a consumer first accesses your API, to connect, have him
- Send a "username" parameter ("john.doe" not secret)
- Send a "APIkeyID" parameter ("1234", not secret)
and give him back
- the salts from your database (In case one of the parameters is wrong,
just give back some repeatable salt - eg.
sha1(username+"notverysecret").
- The timestamp of the server
The consumer should store the salt for session duration to keep things fast and smooth, and he should calculate and keep the time offset between client and server.
The consumer should now calculate the salted hashes of API key and password. This way the consumer has the exact same hashes for password and API key, as what is stored in your database, but without anything seceret ever going over the wire.
Now when a consumer subseqently accesses your API, to do real work, have him
- Send a "username" parameter ("john.doe" not secret)
- Send a "APIkeyID" parameter ("1234", not secret)
- Send a "RequestSalt" parameter (byte[64], random, not secret)
- Send a "RequestTimestamp" parameter (calculated from client time and known offset)
- Send a "RequestToken" parameter (hash(passwordhash+request_salt+request_timestamp+apikeyhash))
The server should not accept timestamps more than say 2 seconds in the past, to make this safe against a replay attack.
The server can now calculate the same hash(passwordhash+request_salt+request_timestamp+apikeyhash) as the client, and be sure, that
- the client knows the API key,
- the client knows the correct password
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…