Can We Generate a Secret Based on Bob and Alice’s Secret — And Prove It?

It has been a tough time for Bob and Alice. The demands of cybersecurity has meant that they just don’t trust anyone or anything anymore —…

Instagram [here]

Can We Generate a Secret Based on Bob and Alice’s Secret — And Prove It?

It has been a tough time for Bob and Alice. The demands of cybersecurity has meant that they just don’t trust anyone or anything anymore — even their must trusted of acquaintances: Trent. So, how can Bob have a secret, and Alice has a secret, and where they can pass these in a secure way, but for the other side to not know what their secret is, and for them both to end up with a shared secret?

Let’s say Bob has a password, and Alice has an identifier on Bob. Neither want to reveal their secret to the other, but both want proof that their secrets have been used to compute a shared secret. Well, with Verifiable Oblivious Pseudorandom Functions (VOPRF), we can generate a random secret based on a key generated on the server (Alice), and which is based on Bob’s secret:

Initially, Bob generates his secret (x), and the blinds it. This blind value is then sent to Alice, and who then uses her private key (k) to produce proof values to go back to Bob (r). Bob then finalises the PRF, by taking his secret value, and the proof. Overall, Alice does not learn anything about Bob’s secret, and Bob does not learn about Alice’s key.

We can set up Bob and Alice with:

key, _ := oprf.GenerateKey(id, rand.Reader)
alice, _ := oprf.NewVerifiableServer(id, key)bob, _ :=
oprf.NewVerifiableClient(id, key.Public())

and next Bob can generate the blinded value for his secret (pass) to send to Alice:

cl, _ := bob.Request([][]byte{[]byte(pass)})EV, _ := 
alice.Evaluate(cl.BlindedElements(), nil)

The value of EV is then sent back to Bob, who then generates the PRF:

Bob_token, _ := bob.Finalize(cl, EV, nil)

If Alice needs to prove the PRF (pseudorandom function), she can take the actual inputs, and prove with:

rtn := alice.VerifyFinalize([]byte(pass), nil, Bob_token)
token, _ := alice.FullEvaluate([]byte(pass), nil)

A core advantage of VOPRF is that the client (Bob) can check that the server (Alice) has used a committed secret key — and where Alice’s public key verifies that private key that has been used — within the Evaluation() method (on Alice’s side), and which is checked in the Finalize() method on Bob’s side. The full code using Cloudflare CIRCL is here:

package main

import (
"crypto/rand"
"fmt"
"os"
"github.com/cloudflare/circl/oprf"
)

func main() {

id := oprf.OPRFP256

// alice -> server, bob -> client
pass := "password"
idtype := "OPRFP256"


argCount := len(os.Args[1:])


if argCount > 0 {
idtype = os.Args[1]
}
if argCount > 1 {
pass = os.Args[2]
}

switch idtype {
case "OPRFP256":
id = oprf.OPRFP256
case "OPRFP384":
id = oprf.OPRFP384
case "OPRFP521":
id = oprf.OPRFP521

}

key, _ := oprf.GenerateKey(id, rand.Reader)
alice, _ := oprf.NewVerifiableServer(id, key)

bob, _ := oprf.NewVerifiableClient(id, key.Public())

cl, _ := bob.Request([][]byte{[]byte(pass)})

EV, _ := alice.Evaluate(cl.BlindedElements(), nil)

Bob_token, _ := bob.Finalize(cl, EV, nil)

fmt.Printf("Bob's secret: %s\n", pass)
fmt.Printf("Method: %s\n", idtype)

fmt.Printf("\nBob sends blinded requests:\n%x\n", cl.BlindedElements())

fmt.Printf("Alice generates proof (C): %x\n", EV.Proof.C)
fmt.Printf("Alice generates proof (S): %x\n", EV.Proof.S)

fmt.Printf("Bob gets PRF secret key: %x\n", Bob_token)

fmt.Printf("\n\n=== If the server holds inputs and proofs, let's verify ===\n")

rtn := alice.VerifyFinalize([]byte(pass), nil, Bob_token[0])
rtn2, _ := alice.FullEvaluate([]byte(pass), nil) // This does a full server side evaluation

fmt.Printf("Verified: %v\n", rtn)
fmt.Printf("Derived PRF secret key: %x\n", rtn2)

}

The method we have outlined provides an excellent way of creating a trust infrastructure, such as for IoT devices, and where we can generate encryption keys for these devices, using a secret stored on the device, and a trust server’s secret key. For the keys used, we can then prove they usage, if required. A sample run is:

Bob sends blinded requests:
[02174467e4f99b32c42012aa25aa4d88936a16b5eb63ee88898267669c89ca9551]
Alice generates proof (C): 5253e4710ad32d805ffed2a56f503358809e5e91aa8bbee85f73224c4432284f
Alice generates proof (S): c09e020d232ca9c415ba0d174f2cd2eecd3f9f3a5fc8c2a26c67ffbc85147aeb
Bob gets PRF secret key: [dcffd0b47c7bd5975005585e4c282faf6ad7673f35761b5c990ecdc8bf572aa7]


=== If the server holds inputs and proofs, let's verify ===
Verified: true
Derived PRF secret key: dcffd0b47c7bd5975005585e4c282faf6ad7673f35761b5c990ecdc8bf572aa7

Conclusions

Oblivious transfers (OT) and the blinding of data are just two ways of implementing zero-knowledge proofs. If you want to learn more about these, try here:

https://asecuritysite.com/zero/

and for Cloudflare CIRCL:

https://asecuritysite.com/circl/