Skip to content

Instantly share code, notes, and snippets.

@Sc00bz
Last active August 5, 2019 14:41
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sc00bz/ef0951ab98e8e1bac4810f65a42eab1a to your computer and use it in GitHub Desktop.
Save Sc00bz/ef0951ab98e8e1bac4810f65a42eab1a to your computer and use it in GitHub Desktop.
Description of BSPAKE with all optional features and implementation ways explicitly pointed out
BSPAKE
For an easier read that glosses over a few details see:
https://gist.github.com/Sc00bz/09b5836923ad986921b905723b0d0c02
Both have:
G = generator
idS = server identity
H() is a KDF or hash that serializes the inputs and outputs enough bits.
hashToPoint() is either Elligator or SWU depending on curve.
pwKdf() is a password KDF like Argon2, scrypt, etc.
The modifiers are for generating different keys from a base key. These
could be implemented as info strings for HKDF or used as an offset
from large output like "a || b || c || d = H(baseKey)" or
"a || b || c || d = baseKey".
For authModifier and optional encryptModifier, the output from pwKdf()
should be passed through a KDF because of PBKDF2's footgun. Each hash
size block of output runs through the full amount of work. An
attacker, that dumps the database, would generate the block that
overlaps the most with k3 or block(s) for the authKey. This gives the
attacker an advantage of doing much less work to check a password.
For clientModifier, serverModifier, keyModifier, and verifyModifier,
another way to implement this is like:
v = H(authKey, verifyModifier)
k3 = H(authKey, keyModifier)
clientBlind = H(k3, clientModifier)
serverBlind = H(k3, serverModifier)
Thus you can give the server k3 and v*G. Then the server can choose
to expand k3 on the fly or expand and save BlindC and BlindS.
extraKeyData is any already established or co-established session key.
Such as a TLS session key.
Client has:
idC = client identity
secretSalt = an optional secret salt stored on the client's device
Server has these for "idC":
salt
settings
BlindC = hashToPoint(H(authKey, clientModifier))
BlindS = hashToPoint(H(authKey, serverModifier))
k3 = H(authKey, keyModifier)
V = H(authKey, verifyModifier) * G
C: r = random()
C: R = r * hashToPoint(H(password, idC, idS))
C->S: idC, R
S: b = random()
S: B = b * G + BlindS
S: R' = H(salt) * R
C<-S: B, R', settings
C: BlindSalt = (1/r) * R'
C: pwKey = pwKdf(password, BlindSalt, 1[secretSalt], idC, idS, settings)
C: authKey = H(pwKey, authModifier)
C: a = random()
C: A = a * G + hashToPoint(H(authKey, clientModifier))
C: B' = B - hashToPoint(H(authKey, serverModifier))
C: k3 = H(authKey, keyModifier)
C: v = H(authKey, verifyModifier)
C: K_c = H(idC, idS, A, B, a * B', k3, v * B')
C: verifierC_c = H(K_c, verifyCModifier)
C: 2[verifierS_c = H(K_c, verifySModifier)]
C: 3[keyC = H(K_c, extraKeyData, encryptCModifier)]
C: 4[keyS = H(K_c, extraKeyData, encryptSModifier)]
C: 3[encryptedDataC = encrypt(dataC, keyC)]
C->S: A, verifierC_c, 3[encryptedDataC]
S: A' = A - BlindC
S: K_s = H(idC, idS, A, B, b * A', k3, b * V)
S: verifierC_s = H(K_s, verifyCModifier)
S: Checks verifierC_c == verifierC_s
S: 2[verifierS_s = H(K_s, verifySModifier)]
S: 3[keyC = H(K_s, extraKeyData, encryptCModifier)]
S: 4[keyS = H(K_s, extraKeyData, encryptSModifier)]
S: 4[encryptedDataS = encrypt(dataS, keyS)]
C<-S: 2[verifierS_s], 4[encryptedDataS]
C: 2[Checks verifierS_c == verifierS_s]
C: 5[secretKey = H(pwKey, encryptModifier)]
#[This denotes optional steps that are needed for the numbered "#" feature]. If
you don't need encryption keys don't do 3 and 4. Don't need server auth skip 2.
Not authenticating to an encrypted service skip 5. Don't have a secretSalt skip
1.
On success K_c == K_s, thus derived verifiers and encryption keys are the same.
When receiving a point, you must check it is valid and not a low order point.
After blinding and unblinding, check the point is not the point at infinity.
When using H() or random() to generate a scalar, you should generate a larger
value and modulo by one less than the order then add 1. This makes sure it is
uniformly distributed and not zero. Similar should be done for H() when
generating fields to avoid bad values.
Note "H(salt) * R" vs "salt * R", this is so you don't need to store a large
salt. A salt of just 128 to 256 bits is fine once expanded to the required
length.
Note client can do "secretKey = H(pwKey, encryptModifier)" to generate an
encryption key that only the client knows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment