Skip to content

Instantly share code, notes, and snippets.

@Zenithar
Last active March 13, 2022 15:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Zenithar/d742a97330b67c6e9b8db65652811936 to your computer and use it in GitHub Desktop.
Save Zenithar/d742a97330b67c6e9b8db65652811936 to your computer and use it in GitHub Desktop.
Sample Identity Based Encryption (IBE) in Go using NaCL (No Warranty)
A> Generate ephemeral encryption keypair
A> Bob Identity: harp:v1:identity:nH01kx0xukWDuji3hwNNslj_2YKvp0TdnAB6OPzR1B4:1605982091
A> mPk[Alice => Bob] => KEK: 1TyRX0Azt-w3bsy6bvSJ26StzFvp92PC8FFV_YEQSVY
A> Generate DEK
A> Encrypt [Msg] with DEK
A> Encrypt DEK with KEK
A> Send to bob [MasterPub || ts || EphPub || enc(DEK, KEK) || enc(Msg, DEK)]
B> Extract MasterPub, ts and Ephemeral Encryption Public key
B> Authenticate to PKG
B> Send to PKG => [mPk: fPEhHP8YyjnOG6saN2pizNGRxi3zHwCMy94zuN9YfEE, ephPub:tRkj3C1T81D4lAYG_Ylr1SCoRXT2iStU_mIPFwdmvlY, ts:1605982091, id:bob@example.com]
PKG> Validate 'bob@example.com' identity authorization over `fPEhHP8YyjnOG6saN2pizNGRxi3zHwCMy94zuN9YfEE` scoped data
PKG> Bob Identity: harp:v1:identity:nH01kx0xukWDuji3hwNNslj_2YKvp0TdnAB6OPzR1B4:1605982091
PKG> Reply to Bob => KEK: 1TyRX0Azt-w3bsy6bvSJ26StzFvp92PC8FFV_YEQSVY
B> Decrypt DEK with KEK
B> Decrypt Msg with DEK
B> Message received !
package main
import (
"bytes"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/binary"
"fmt"
"io"
"os"
"time"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"golang.org/x/crypto/nacl/box"
)
const (
aliceID = "alice@example.com"
bobID = "bob@example.com"
)
func main() {
// -------------------------------------------------------------------------
// PKG : Private Key Generator (Setup) - One Time
// Generate master public and private key (Vault, KMS, etc.)
// mPk is pre-shared with all clients (Alice and Bob)
mPk, mSk, err := box.GenerateKey(bytes.NewReader([]byte("00001-deterministic-buffer-for-tests")))
if err != nil {
panic(err)
}
// -------------------------------------------------------------------------
// Alice (External not authenticated identity)
// Compute ephemeral identity based on recipient
// Set data expiration data (in 7 days)
dataExpiration := time.Now().Round(time.Second).AddDate(0, 0, 7)
// Generate ephemeral keypair
ephPub, ephPriv, err := box.GenerateKey(rand.Reader)
if err != nil {
panic(err)
}
fmt.Fprintf(os.Stdout, "A> Generate ephemeral encryption keypair\n")
// Create ephemeral bob identity with 7 days TTL
remoteBob, err := createIdentity(mPk[:], bobID, dataExpiration)
if err != nil {
panic(err)
}
fmt.Fprintf(os.Stdout, "A> Bob Identity: %s\n", remoteBob.String())
// Derive KEK (Master / Bob)
{
// Compute key agreement (ECDH) between ephemeral private key and master public key (PFS)
zEph, err := curve25519.X25519(ephPriv[:], mPk[:])
if err != nil {
panic(err)
}
// Compute key agreement (ECDH) between ephemeral private key and recipient public key (PFS)
zRecipient, err := curve25519.X25519(ephPriv[:], remoteBob.EncryptionPublicKey)
if err != nil {
panic(err)
}
// Concatenate agreement results.
zS := append(zEph, zRecipient...)
// Bind all public keys together
var salt bytes.Buffer
salt.Write(ephPub[:]) // Ephemeral Public Key
salt.Write(mPk[:]) // Master Public Key
salt.Write(remoteBob.EncryptionPublicKey) // Bob Public Key
// Derive HKDF (HMAC-SHA256)
h := hkdf.New(sha256.New, zS, salt.Bytes(), []byte("harp:v1:identity"))
wrappingKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, wrappingKey); err != nil {
panic(err)
}
fmt.Fprintf(os.Stdout, "A> mPk[Alice => Bob] => KEK: %s\n", base64.RawURLEncoding.EncodeToString(wrappingKey))
}
// Generate DEK
fmt.Fprintf(os.Stdout, "A> Generate DEK\n")
// Encrypt message with DEK
fmt.Fprintf(os.Stdout, "A> Encrypt [Msg] with DEK\n")
// Encrypt DEK with KEK
fmt.Fprintf(os.Stdout, "A> Encrypt DEK with KEK\n")
// Send (MasterPub || ts || EphPub || ENC(DEK, KEK) || ENC(Msg, DEK)) to Bob
fmt.Fprintf(os.Stdout, "A> Send to bob [MasterPub || ts || EphPub || enc(DEK, KEK) || enc(Msg, DEK)]\n")
// -------------------------------------------------------------------------
// Bob
fmt.Fprintln(os.Stdout)
// Read recipient and expiration from packet
fmt.Fprintf(os.Stdout, "B> Extract MasterPub, ts and Ephemeral Encryption Public key\n")
fmt.Fprintln(os.Stdout, "B> Authenticate to PKG")
fmt.Fprintf(os.Stdout, "B> Send to PKG => [mPk: %s, ephPub:%s, ts:%d, id:%s]\n", base64.RawURLEncoding.EncodeToString(mPk[:]), base64.RawURLEncoding.EncodeToString(ephPub[:]), dataExpiration.Unix(), bobID)
// -------------------------------------------------------------------------
// PKG
// Compute key agreement between private master key and generated identity
fmt.Fprintln(os.Stdout)
fmt.Fprintf(os.Stdout, "PKG> Validate '%s' identity authorization over `%s` scoped data\n", bobID, base64.RawURLEncoding.EncodeToString(mPk[:]))
// Create its ephemeral identity with received timestamp
bob, err := createIdentity(mPk[:], bobID, dataExpiration)
if err != nil {
panic(err)
}
fmt.Fprintf(os.Stdout, "PKG> Bob Identity: %s\n", bob.String())
// Derive encryption key
{
// Compute key agreement between master secret key and sender public key
zEph, err := curve25519.X25519(mSk[:], ephPub[:])
if err != nil {
panic(err)
}
zRecipient, err := curve25519.X25519(bob.EncryptionPrivateKey, ephPub[:])
if err != nil {
panic(err)
}
// Concatenate agreement results.
zS := append(zEph, zRecipient...)
// Bind public keys together
var salt bytes.Buffer
salt.Write(ephPub[:]) // Ephemeral Public Key
salt.Write(mPk[:]) // Master Public Key
salt.Write(bob.EncryptionPublicKey) // Bob Public Key
h := hkdf.New(sha256.New, zS, salt.Bytes(), []byte("harp:v1:identity"))
wrappingKey := make([]byte, chacha20poly1305.KeySize)
if _, err := io.ReadFull(h, wrappingKey); err != nil {
panic(err)
}
fmt.Fprintf(os.Stdout, "PKG> Reply to Bob => KEK: %s\n", base64.RawURLEncoding.EncodeToString(wrappingKey))
}
// -------------------------------------------------------------------------
// Bob
fmt.Fprintln(os.Stdout)
// Decrypt DEK with KEK
fmt.Fprintf(os.Stdout, "B> Decrypt DEK with KEK\n")
fmt.Fprintf(os.Stdout, "B> Decrypt Msg with DEK\n")
// Done !
fmt.Fprintf(os.Stdout, "B> Message received !\n")
}
// Identity wraps identity attributes.
type Identity struct {
ID string
ExpiresOn time.Time
EncryptionPrivateKey []byte
EncryptionPublicKey []byte
}
func (id *Identity) String() string {
return fmt.Sprintf("harp:v1:identity:%s:%d", base64.RawURLEncoding.EncodeToString(id.EncryptionPublicKey), id.ExpiresOn.Unix())
}
func createIdentity(masterPublicKey []byte, name string, expiresOn time.Time) (*Identity, error) {
// Prepare protected identity
ts := make([]byte, 8)
binary.BigEndian.PutUint64(ts, uint64(expiresOn.Unix()))
// Prepare buffer to derive
var buf bytes.Buffer
buf.WriteString(name) // Recipient string
buf.Write([]byte{0x00}) // Always add null byte after ascii string
buf.Write(ts) // Encoded expiration
// Initialize hash function to bind recipient and timestamp.
h, err := blake2b.New512([]byte("harp identity salt derivation"))
if err != nil {
return nil, fmt.Errorf("unable to initialize salt: %w", err)
}
h.Write(buf.Bytes())
salt := h.Sum(nil)
// Derive key from Master Public Key with hash as salt
dk := hkdf.New(sha256.New, masterPublicKey, salt, []byte("harp:v1:x25519"))
seed := make([]byte, curve25519.ScalarSize)
if _, err := io.ReadFull(dk, seed); err != nil {
return nil, fmt.Errorf("unable to generate seed: %w", err)
}
// Generate box keypair
encPub, encPriv, err := box.GenerateKey(bytes.NewReader(seed[:curve25519.ScalarSize]))
if err != nil {
return nil, fmt.Errorf("unable to generate encryption keypair: %w", err)
}
// Return identity
return &Identity{
ID: name,
ExpiresOn: expiresOn,
EncryptionPrivateKey: encPriv[:],
EncryptionPublicKey: encPub[:],
}, nil
}
@attia-ali-attia
Copy link

Wow

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment