Last active
March 13, 2022 15:42
-
-
Save Zenithar/d742a97330b67c6e9b8db65652811936 to your computer and use it in GitHub Desktop.
Sample Identity Based Encryption (IBE) in Go using NaCL (No Warranty)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:[email protected]] | |
PKG> Validate '[email protected]' 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 ! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 = "[email protected]" | |
bobID = "[email protected]" | |
) | |
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 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Wow