blob: fab0cafd0605b4fe09e2993c38633cef8b65f700 [file] [log] [blame]
// Copyright 2016 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file defines an implementation of the IBE interfaces using the
// Boneh-Boyen scheme (BB2). The Fujisaki-Okamoto transformation is applied to
// obtain CCA2-security. The paper defining this algorithm (see comments for
// SetupBB2) uses multiplicative groups while the bn256 package used in the
// implementation here defines an additive group. The comments follow the
// notation in the paper while the code uses the bn256 library. For example,
// g^i corresponds to G1.ScalarBaseMult(i).
// The ciphertexts in the resulting CCA2-secure IBE scheme will consist of
// two parts: an IBE encryption of a symmetric key (called the 'kem' - key
// encapsulation mechanism) and a symmetric encryption of the payload
// (called the 'dem' - data encapsulation mechanism).
package ibe
import (
"bytes"
"crypto/rand"
"fmt"
"math/big"
"golang.org/x/crypto/bn256"
"golang.org/x/crypto/nacl/secretbox"
)
// Setup creates an ibe.Master based on the BB2 scheme described in "Efficient
// Selective Identity-Based Encryption Without Random Oracles" by Dan Boneh and
// Xavier Boyen (http://crypto.stanford.edu/~dabo/papers/bbibe.pdf).
//
// Specifically, a variant of Section 5.1 (from the section labeled Hash-BDHI
// construction in Section 5.1) of the paper is implemented.
//
// In addition, we apply the Fujisaki-Okamoto transformation to the BB-IBE
// scheme (http://link.springer.com/chapter/10.1007%2F3-540-48405-1_34)
// in order to obtain CCA2-security (in the random oracle model). The resulting
// scheme is a CCA2-secure hybrid encryption scheme where BB-IBE is used to
// encrypt a nonce used to derive a symmetric key, and NaCl/secretbox is used
// to encrypt the data under the symmetric key.
//
// The BB2 scheme uses approximately 30% less CPU for the Decrypt operation
// compared to the BB1 scheme, and public keys are more compact (3 group
// elements vs. 5 group elements). The main disadvantage is that it relies on
// a more complicated assumption on bilinear groups (the hash bilinear Diffie-
// Hellman inversion assumption). This assumption holds in the generic group
// model, and to date, there are no known attacks on the assumption other than
// the generic ones.
func SetupBB2() (Master, error) {
var (
m = &bb2master{
params: newbb2params(),
hHat: new(bn256.G2),
}
g = new(bn256.G1).ScalarBaseMult(big.NewInt(1)) // generator for G1
pk = m.params // shorthand
hHat = m.hHat // shorthand
)
// Pick a random exponent r and set hHat = gHat^r
r, err := random()
if err != nil {
return nil, err
}
hHat.ScalarBaseMult(r)
// Pick exponent x for master secret key and compute g^x
m.x, err = random()
if err != nil {
return nil, err
}
pk.X.ScalarBaseMult(m.x)
// Pick exponent y for master secret key and compute g^y
m.y, err = random()
if err != nil {
return nil, err
}
pk.Y.ScalarBaseMult(m.y)
pk.v = bn256.Pair(g, hHat)
return m, nil
}
type bb2master struct {
params *bb2params // Public params
x *big.Int // Master key component
y *big.Int // Master key component
hHat *bn256.G2 // Master key component
}
func (m *bb2master) Extract(id string) (PrivateKey, error) {
var (
ret = &bb2PrivateKey{
params: m.params,
r: nil,
K: new(bn256.G2),
}
// some shorthands
hHat = m.hHat
z = new(big.Int)
sum = new(big.Int)
invSum = new(big.Int)
zero = big.NewInt(0)
)
// z = x + H(id) (mod p)
z.Add(m.x, val2bignum(idPrefix, []byte(id)))
z.Mod(z, bn256.Order)
for {
r, err := random()
if err != nil {
return nil, err
}
// sum = x + r y + H(id) = z + r y (mod p)
sum.Mul(r, m.y)
sum.Add(z, sum)
sum.Mod(sum, bn256.Order)
if sum.Cmp(zero) != 0 {
ret.r = r
break
}
}
invSum.ModInverse(sum, bn256.Order)
ret.K.ScalarMult(hHat, invSum)
return ret, nil
}
func (m *bb2master) Params() Params { return m.params }
type bb2params struct {
X, Y *bn256.G1
v *bn256.GT
}
func newbb2params() *bb2params {
return &bb2params{
X: new(bn256.G1),
Y: new(bn256.G1),
v: new(bn256.GT),
}
}
func (e *bb2params) encapsulateKeyStart(sigma *[encKeySize]byte, s *big.Int, C []byte) error {
if len(C) != encKeySize+marshaledG1Size {
return fmt.Errorf("provided buffer has size %d, must be %d", len(C), encKeySize+marshaledG1Size)
}
var (
vs = new(bn256.GT)
tmpG1 = new(bn256.G1)
// Ciphertext C = (A, B, C1) - this method computes the first two components
A = C[0:encKeySize]
B = C[encKeySize : encKeySize+marshaledG1Size]
)
vs.ScalarMult(e.v, s)
pad := hashval(ibePrefix, vs.Marshal())
// A = sigma ⊕ H(v^s)
for i := range sigma {
A[i] = sigma[i] ^ pad[i]
}
// B = Y^s
if err := marshalG1(B, tmpG1.ScalarMult(e.Y, s)); err != nil {
return err
}
return nil
}
func (e *bb2params) Encrypt(id string, m, C []byte) error {
if err := checkSizes(m, C, e); err != nil {
return err
}
// Choose a random nonce for the Fujisaki-Okamoto transform
var sigma [encKeySize]byte
if _, err := rand.Read(sigma[:]); err != nil {
return err
}
var (
s = computeKemRandomness(&sigma, m) // H_1(sigma, m)
symKey = computeDemKey(&sigma) // H_2(sigma)
tmpG1 = new(bn256.G1)
// Ciphertext C = (kem, dem)
kem = C[0:kemSize]
dem = C[kemSize:]
)
// kem = (A, B, C1). Invoke encasulateKeyStart to compute (A, B)
if err := e.encapsulateKeyStart(&sigma, s, kem[0:encKeySize+marshaledG1Size]); err != nil {
return err
}
C1 := kem[encKeySize+marshaledG1Size:]
// C1 = (X g^H(id))^s
tmpG1.ScalarBaseMult(val2bignum(idPrefix, []byte(id)))
tmpG1.Add(tmpG1, e.X)
tmpG1.ScalarMult(tmpG1, s)
if err := marshalG1(C1, tmpG1); err != nil {
return err
}
// Nonce for symmetric encryption can be all-zeroes string, because
// we only require one-time semantic security of the underlying symmetric
// scheme in the Fujisaki-Okamoto transformation.
var nonce [nonceSize]byte
if tmp := secretbox.Seal(dem[0:0], m, &nonce, symKey); &tmp[0] != &dem[0] {
return fmt.Errorf("output of Seal has unexpected length: expected %d, received %d", len(dem), len(tmp))
}
return nil
}
func (e *bb2params) CiphertextOverhead() int {
return kemSize + secretbox.Overhead
}
type bb2PrivateKey struct {
params *bb2params // public params
r *big.Int
K *bn256.G2
}
func (k *bb2PrivateKey) Decrypt(C, m []byte) error {
if err := checkSizes(m, C, k.params); err != nil {
return err
}
var (
A = C[0:encKeySize]
B = new(bn256.G1)
C1 = new(bn256.G1)
D = C[kemSize:]
)
if _, ok := B.Unmarshal(C[encKeySize : encKeySize+marshaledG1Size]); !ok {
return errBadCiphertext
}
if _, ok := C1.Unmarshal(C[encKeySize+marshaledG1Size : encKeySize+2*marshaledG1Size]); !ok {
return errBadCiphertext
}
// sigma = A ⊕ H(e(B^r C, K)
var tmpG1 = new(bn256.G1)
tmpG1.ScalarMult(B, k.r)
tmpG1.Add(tmpG1, C1)
var hash = hashval(ibePrefix, bn256.Pair(tmpG1, k.K).Marshal())
var sigma [encKeySize]byte
for i := range sigma {
sigma[i] = A[i] ^ hash[i]
}
symKey := computeDemKey(&sigma)
var nonce [nonceSize]byte
if tmp, success := secretbox.Open(m[0:0], D, &nonce, symKey); !success || (&tmp[0] != &m[0]) {
return errBadCiphertext
}
// Check that consistent randomness was used to encrypt the symmetric key. It suffices
// to check that the first two components of the KEM portion matches, since given the
// message and the secret identity key, the first two components uniquely determine the third.
// First, derive the randomness used for the KEM.
s := computeKemRandomness(&sigma, m)
// Check the first two components
var kemChkBuf [encKeySize + marshaledG1Size]byte
k.params.encapsulateKeyStart(&sigma, s, kemChkBuf[:])
if !bytes.Equal(kemChkBuf[:], C[0:encKeySize+marshaledG1Size]) {
return errBadCiphertext
}
return nil
}
func (k *bb2PrivateKey) Params() Params { return k.params }