Frank DENIS random thoughts.

A simple tweak to make static shared keys suck less

Public-key cryptography is a powerful tool, but for performance and simplicity, static shared symmetric keys are still widely used.

For example, an API token can be a simple random byte sequence stored in a database to authenticate clients. Similarly, if two servers are operated by the same company, one server can encrypt data for the other using a shared symmetric key present on both hosts—this is a common practice.

Additionally, applications often use data concatenated with a signature to prevent tampering. A common example is JWT tokens. When these signatures are generated and verified by the same organization or between trusted parties, public-key cryptography may not feel necessary. Instead, a HMAC construction (as used in the HS* JWT algorithms) is simpler and faster.

Since TLS is widely used, this approach may not seem overly risky. However, a major issue remains: if a server is compromised, if a secret key appears in a leaked SQL dump, or if it is hardcoded in a repository that becomes public, attackers can forge valid tokens. Even without an explicit leak, more employees than necessary may have access to the secret, leading to security risks.

Public-key cryptography or alternative mechanisms like hash chains would provide stronger security, but existing schemes using static shared secrets can still be improved with a trivial tweak.

Improving Static API Tokens

The simplest implementation of static shared secrets works as follows:

  • A random, static secret x is generated and stored on both the client and server.
  • To authenticate, the client sends x to the server.
  • The server verifies that the received value matches x.

This protocol can be improved with a small change:

  • A random, static secret x is created.
  • x is stored only on the client.
  • The server stores z = HASH(x), not x itself.
  • To authenticate, the client sends x.
  • The server computes HASH(x) and verifies that it matches the stored value z.

At first glance, this may seem as weak as the original method, but there is a key difference: the server no longer stores the actual secret. Assuming HASH is a secure hash function, leaking z does not allow an attacker to authenticate. In fact, z can be hardcoded into applications and remain publicly visible, while the actual secret x stays only on the client.

Of course, x can still be leaked if the client is compromised, if the connection is intercepted, or if the server logs received values maliciously. However, this approach is comparable to hashing passwords instead of storing them in plain text. It significantly improves security with minimal effort and negligible performance overhead. Since secrets are not subject to dictionary attacks, a single round of a fast, standard hash function is sufficient.

Improving Authentication Tokens

Authentication tokens use a secret key to sign and verify arbitrary data—this is how HS256 JWT tokens work, for example. A typical signing and verification process using a static shared secret follows this pattern:

  1. A random, static secret k is created and shared between the signer and verifier.
  2. To ensure data cannot be modified, the signer computes t = HMAC(k, data).
  3. The signer sends data and t to the verifier.
  4. The verifier recomputes HMAC(k, data) and checks that it matches t. Since generating a valid t requires knowing k, unauthorized modifications are prevented.

This process can be enhanced as follows:

  1. A random, static secret k is created and shared between the signer and verifier.
  2. Another secret u is generated, stored only on the signer. The verifier stores z = HASH(u) instead.
  3. To sign data, the signer computes t = u || HMAC(k || u, data).
  4. The signer sends data and t to the verifier.
  5. The verifier extracts u and the MAC from t. It then verifies that HASH(u) matches z and that HMAC(k || u, data) matches the received MAC.

This tweak does not improve security on the signer’s side, but it does enhance security for the verifier. Since the verifier no longer stores the full key, leaking z alone is insufficient for forging valid tokens—an attacker would also need to obtain u.

The same process can be applied to encryption using static, shared keys.

A Simple Yet Effective Trick

Splitting keys has numerous security applications, and the examples above are just some of the simplest implementations. However, despite being easy to implement and deploy in virtually any environment, this technique remains underutilized.

While this trick does not turn basic authentication mechanisms into a silver bullet, it provides a meaningful security improvement with minimal effort. Any opportunity to enhance security with such a low cost is worth considering!