Oblivious Pseudorandom Functions

Nick Sullivan from Cloudflare always has his finger on the pulse of cybersecurity, and announced today that the RFC 9497 has just been…

Oblivious Pseudorandom Functions

Nick Sullivan from Cloudflare always has his finger on the pulse of cybersecurity, and announced today that the RFC 9497 has just been published [here]:

Introduction

In our digital world, we give away too many of our secrets, especially when we just have to prove the knowledge of something rather than actually revealing it. In many of the systems we use, we could just prove things in an oblivious way, and where we could pass a secret but in a blinded form.

With this, a server does not discover our password and does not discover the identifier that a server holds on us. But, can we also provide proof back that the right password has been used? 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, and the blinds it. This blind value is then sent to Alice, and then who uses her private key 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 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 [here]:

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 [here]:

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:

The code is:

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)
}

A sample run:

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

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.

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/