I'd like for two machines on a network to be able to prove to each other that they both have knowledge of a pre-shared secret, without revealing the secret to each other. Let's assume that all traffic over the connection between the parties, A and B, is encrypted.

Here are the steps I'm currently imagining:

1. A->B: nonce_A, hash(nonce_A || secret_key)

  • B checks that they can produce the same hash using nonce_A
  • even if B can produce the hash, it doesn't yet prove A knows the key (e.g.could be a replay attack)

2. B->A: nonce_B, hash(nonce_B || nonce_A || secret_key)

  • A checks that they can produce the same hash using the nonces and key
  • if nonce_B == nonce_A, the handshake fails
  • A now knows B knows the secret key

3. A->B: hash(nonce_A || nonce_B || secret_key)

  • B checks that they can produce the same hash using the nonces and key
  • B now knows A knows the secret key

4. Both sides know the key!

My understanding is that this protects against replay attacks (since a fresh nonce is used each time) and also against reflection attacks (since the hash must include BOTH parties' nonces).

Are there any security issues in this approach?

(I'm also curious if there are any existing RFC or similar for doing this.)

  • 2
    $\begingroup$ This question is off-topic here and on-topic at Cryptography. "I'm also curious if there are any existing RFC or similar for doing this." - sure, just search for pre shared key authentication rfc and you'll find standards for PSK authentication in IPSec/IKE, EAP-PSK, PSK in TLS ... $\endgroup$ May 5, 2023 at 19:17
  • 1
    $\begingroup$ One nitpick with this protocol is that it doesn't really authenticate the parties. Maybe that is desired? If it is not desired, then the issue is that an attacker can force party A into a session with itself while A thinks it completed the session with B. Depending on the application, this might not be so good. The protocol described is very close to the MAP1 protocol from Bellare and Rogaway (check it out). Another thing, using a Pseudo Random Function might be a better choice here. Though currently it's not too bad because the key is prepended. $\endgroup$ May 10, 2023 at 21:43
  • $\begingroup$ Thanks Marc, that's a great point. I think if each side had even a short-lived "session identifier" that was used in conjunction with the nonce, it would allow A to detect a replay attempt. And thanks for the references. $\endgroup$
    – Kira
    May 17, 2023 at 22:56
  • $\begingroup$ Marc, what is the danger in not using a PRF instead of (inside of?) the hash function? $\endgroup$
    – Kira
    May 17, 2023 at 23:09

1 Answer 1


First off, if you can, just use something standard like TLS-PSK which is designed to do exactly this and is hard to mess up.

If you do want to roll your own crypto, symmetric stuff is definitely the most foolproof place to start.

simplifying the protocol

Simplest protocol is:

secret key sk, A and B pick nonces N_a,N_b

  • A-->B N_a
  • B-->A N_b,Hash(N_a || N_b || sk || bytes("b-->a"))
  • A-->B Hash(N_a || N_b || sk || bytes("a-->b"))

When deriving keys, pseudorandom values or challenge responses, just add a string to the hash that's unlikely to be reused elsewhere.

If you need to derive encryption keys, just do K_a2b=Hash(N_a || N_b || sk || bytes("K_a2b")) or similar. Adding strings to the value to be hashed is pretty standard for adding context to get different values out of a hash function or key derivation function.

A few things to keep in mind

Are you are using /dev/urandom or your platform's equivalent secure random byte source? If not, nonce values could be predictable.

If the random number generator in A or B breaks and nonces start repeating, hash comparisons can be vulnerable to a timing attack.

If you use your language's = operator to compare the hash value you receive from the peer to a value calculated locally your code will take longer to reject an answer if more bytes of the hash match. With enough timing measurements an attacker can determine the entire value. Always use something like https://docs.python.org/3/library/hmac.html#hmac.compare_digest when doing byte string comparisons in crypto code.

Alternatively, if you can't find a constant time comparison somewhere in your standard library, you can do this:

def masked_compare(a,b):
    return hash(rand+a)==hash(rand+b)

It's not constant time but it doesn't allow for timing based extraction of the correct answer.

  • $\begingroup$ Thank you for your notes Richard! Also, TLS-PSK does a LOT more than just verifying each party knows a pre-shared key. I'm trying to see what exists that does only this piece. $\endgroup$
    – Kira
    May 10, 2023 at 18:10
  • $\begingroup$ I think this is vulnerable to the same replay attack Marc outlined in the comment to my question post. If bytes("a-->b") were actual session/permanent identities rather than a static string literal, I think it would be avoided. $\endgroup$
    – Kira
    May 17, 2023 at 23:08
  • 1
    $\begingroup$ Relay attack, not Replay, but yes. There's no session establishment happening here, at least I'd hope not. All you get is a liveness guarantee. Mallory needs to relay messages between two people who know the secrets. If you trust the underlying network (bad idea unless you're dealing with a secure VPN like Wireguard or similar.) then you could bind (EG: is Alice and is Bob) as you suggest. If this is a prelude to sensitive communication then STOP IMMIDIATELY and use TLS or similar to encrypt that communication. $\endgroup$ May 19, 2023 at 3:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.