Skip to content

Instantly share code, notes, and snippets.

@salrashid123
Last active January 5, 2026 11:15
Show Gist options
  • Select an option

  • Save salrashid123/761101aa94e9b26b114390fd966b1358 to your computer and use it in GitHub Desktop.

Select an option

Save salrashid123/761101aa94e9b26b114390fd966b1358 to your computer and use it in GitHub Desktop.
Generate MLKEM key using Trusted Platfrom Module as random number generator

Generate MLKEM key using Trusted Platfrom Module as random number generator

the following snippet generates an MLKEM key using a variety of sources and then writes the keys to file as PEM format

  • A) generate key internally in code
  • B) generate key externally using default crypto/rand source
  • C) generate a key externally using a TPM as the rand source ("github.com/salrashid123/tpmrand")
  • D) generate key externally using a given hex string statically

also see

an application of (D) maybe to derive a shared key for file encryption trusted computing (https://github.com/salrashid123/mcbn)

package main

import (
	"crypto/mlkem"
	"crypto/x509/pkix"
	"encoding/asn1"
	"encoding/hex"
	"encoding/pem"
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"slices"

	"github.com/google/go-tpm-tools/simulator"
	"github.com/google/go-tpm/tpmutil"
	tpmrand "github.com/salrashid123/tpmrand"
)

const ()

var (
	private = flag.String("private", "/tmp/private.pem", "PrivateKey")
	public  = flag.String("public", "/tmp/public.pem", "PublicKey")
	keyType = flag.String("keyType", "mlkem768", "KeyType must be mlkem768 or mlkem1024")
)

var (
	mlkem512_OID  = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 4, 1}
	mlkem768_OID  = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 4, 2}
	mlkem1024_OID = asn1.ObjectIdentifier{2, 16, 840, 1, 101, 3, 4, 4, 3}
)

type pkixPrivKey struct {
	Version    int `asn1:"version:0"`
	Algorithm  pkix.AlgorithmIdentifier
	PrivateKey asn1.RawContent
}

type pkixPubKey struct {
	Raw       asn1.RawContent
	Algorithm pkix.AlgorithmIdentifier
	PublicKey asn1.BitString
}

/*

### start swtpm
rm -rf /tmp/myvtpm && mkdir /tmp/myvtpm && swtpm_setup --tpmstate /tmp/myvtpm --tpm2 --create-ek-cert && swtpm socket --tpmstate dir=/tmp/myvtpm --tpm2 --server type=tcp,port=2341 --ctrl type=tcp,port=2342 --flags not-need-init,startup-clear --log level=2
export TPM2TOOLS_TCTI="swtpm:port=2341"
*/

var (
	tpmPath = flag.String("tpm-path", "127.0.0.1:2341", "Path to the TPM device (character device or a Unix socket).")
)

var TPMDEVICES = []string{"/dev/tpm0", "/dev/tpmrm0"}

func OpenTPM(path string) (io.ReadWriteCloser, error) {
	if slices.Contains(TPMDEVICES, path) {
		return tpmutil.OpenTPM(path)
	} else if path == "simulator" {
		return simulator.Get()
	} else {
		return net.Dial("tcp", path)
	}
}

func main() {
	flag.Parse()

	var privteKeyBytes []byte
	var publicKeyBytes []byte

	// A) generate key in code
	// nk, err := mlkem.GenerateKey768()
	// if err != nil {
	// 	fmt.Printf("error creating encapsulation key %v", err)
	// 	os.Exit(1)
	// }

	// B) generate key using default rand reader for bytes
	externalSeed := make([]byte, mlkem.SeedSize) // mlkem.SeedSize is 64 bytes
	// _, err := rand.Read(externalSeed)
	// if err != nil {
	// 	log.Fatalf("failed to create get random seed: %v", err)
	// }

	// C) generate a key using a TPM as the rand source
	rwc, err := OpenTPM(*tpmPath)
	if err != nil {
		fmt.Printf("Unable to open TPM at %s", *tpmPath)
	}
	defer rwc.Close()

	r, err := tpmrand.NewTPMRand(&tpmrand.Reader{
		TpmDevice: rwc,
	})
	_, err = r.Read(externalSeed) // Fill it with random data
	if err != nil {
		log.Fatalf("failed to create get random seed: %v", err)
	}

	fmt.Printf("%s\n", hex.EncodeToString(externalSeed))

	// D) generate key using a given hex string statically
	// externalSeed, err = hex.DecodeString("e0c311ae778d5208fc799d1f50e278ba1b86762ab463620bf4d1affd415e75c9a520b688a19ebb7b997c1a03cb3e9e170ae8b13f3c09776e58fad1f23d08ec05")
	// if err != nil {
	// 	log.Fatalf("failed decoding hex: %v", err)
	// }

	// now create the key
	nk, err := mlkem.NewDecapsulationKey768(externalSeed)
	if err != nil {
		log.Fatalf("failed to create decapsulation key from seed: %v", err)
	}

	fmt.Println("ML-KEM key pair successfully derived from external seed.")
	fmt.Printf("Decapsulation Key (seed) size: %d bytes\n", len(nk.Bytes()))
	fmt.Printf("Encapsulation Key size: %d bytes\n", len(nk.EncapsulationKey().Bytes()))

	privateKey := pkixPrivKey{
		Version: 0,
		Algorithm: pkix.AlgorithmIdentifier{
			Algorithm: mlkem768_OID,
		},
		PrivateKey: nk.Bytes(),
	}
	pkb, err := asn1.Marshal(privateKey)
	if err != nil {
		fmt.Printf("error marshalling key %v", err)
		os.Exit(1)
	}
	privateKeyBlock := &pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: pkb,
	}
	privteKeyBytes = pem.EncodeToMemory(privateKeyBlock)

	// encode public key

	nk.EncapsulationKey().Bytes()
	publicKey := pkixPubKey{
		Algorithm: pkix.AlgorithmIdentifier{
			Algorithm: mlkem768_OID,
		},
		PublicKey: asn1.BitString{
			BitLength: len(nk.EncapsulationKey().Bytes()),
			Bytes:     nk.EncapsulationKey().Bytes(),
		},
	}
	ppkb, err := asn1.Marshal(publicKey)
	if err != nil {
		fmt.Printf("error marshalling key %v", err)
		os.Exit(1)
	}
	publicKeyBlock := &pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: ppkb,
	}
	publicKeyBytes = pem.EncodeToMemory(publicKeyBlock)

	fmt.Printf("raw private key \n%s\n", privteKeyBytes)
	fmt.Printf("raw public key \n%s\n", publicKeyBytes)

	err = os.WriteFile(*private, privteKeyBytes, 0666)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error writing private key to file %v\n", err)
		os.Exit(1)
	}

	err = os.WriteFile(*public, publicKeyBytes, 0666)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error writing public key to file %v\n", err)
		os.Exit(1)
	}

}
@salrashid123
Copy link
Author

$ go run main.go 

82d24948703301930ef2ce252ad1ff115c2e55e44ae82faf2e7474eff4858b82af19fc14240e61d823da63086f72ebb98c8a4eec8a63d01eb743d1b2b6ad9485
ML-KEM key pair successfully derived from external seed.
Decapsulation Key (seed) size: 64 bytes
Encapsulation Key size: 1184 bytes
raw private key 
-----BEGIN PRIVATE KEY-----
MFICAQAwCwYJYIZIAWUDBAQCBECC0klIcDMBkw7yziUq0f8RXC5V5EroL68udHTv
9IWLgq8Z/BQkDmHYI9pjCG9y67mMik7simPQHrdD0bK2rZSF
-----END PRIVATE KEY-----

raw public key 
-----BEGIN PUBLIC KEY-----
MIIEsjALBglghkgBZQMEBAIDggShAIopbNugqnthf02mYc7bLSebqnYBIYAbPyUH
QqwHkcLrZYlTHWiiiwbAXlNVWg+Gv605vR7iluSnKrBXA+dyXcrQFHHwN0EmDP3z
P2o7C2/xAuVESYxCaOZwyrlMuyhJj11Sf72VMt+BtlUWLRqVNhngemWrkv1RfLAW
U71bRYQYTlQ2kiF0rKfBLr7bPg2BZ57Ma5W4beDYevT4zE/gvRRcVe17jLgDvm3M
UHJCG0HIwq+2hd3mINbZCsLJtGFouwIok7BZn+CBiNFwe9wDwNZrKohKPIZJTy1F
EinYewJVXNRZIVuQLBtpcteoadFizVnRiVjVepNJVgALRH31vTUcJNOxt75Xj51K
bAnJnESQkgwJFODxjGRqacyga+sWroeEHIu1NcQMyqI7apuLLDkzOOLhEGnsLcVi
HmmxIw4FsuSAhIMpX2ExmXJSqS1UbNiHBgBLfTogCnD7nulUNjCnRoOBq224VThL
pB77cIl8tnBmzJenDsKYdh9YA57Tjp+HJorEdx+RMq9ZgWaoc48YpCSMOL2KWn41
d3F2NzAzR50MChSho5Kobk6Zsj0xVE+3y/2buWjFo3MrGVsEJu6iJZrRG/r3ysrQ
lIWsQymiFznbflmjns3ZAdG5Q2XqzYfEN3lWC0eXh8qbZ1jQhuVDGqpcaYnpg4pU
izDqrv1XOmxik41cCSexBNqCcl1ooJdwlvflg7t0XzN8WAm2WguBjJ5CMhfFoNYW
m0nrTXtDyLYqpjnYLgCELCDoRohxmv2ozc6yrQh0AxqauCpKWRW6vy+pTvr2PvEl
LSbzs8jwe6vAdxqYufoIwWAJhRk8JKAwLt36sUT0ISdhInLVw117p4EQruAzmSSF
YHqamesaHXiBRVZ6FSSoXxpEY+DMzND2wvxjIbIVHLmVKwMCLl3pSWBYulrplj+B
NsyiKlGVtf5SwZnsWRn4XXx5xco6s+yAtxkVG7BxN+XAcH6kfRGom8I1fsjHMqxF
tgknOHLEkBKwquGoKaUIE2NhmrgBSjTMlD1ley5WNSwUJhqkZVvzxochlcUov1wQ
CToGXDTDOqDUECFxvjv8IpvCdXf1O/IXKxUxg1+kSxrYTDC5V4SHk3w1XOqihwE1
TGosRlylPJVRdfmmOTXhrC+cybKyluZbiSGjRCRLD1wFguqgGwj5LdNkZr+ptNJn
Hg3HoWeWk1WhOjZpNk51pC/1OBVVjpHZSNBVfc9izjaYHOXkYJRLH8LKWL4oF2yF
LGBAubqGwt5wuPaCbvImikw5VSmUyVzBHvOZXrCBB6S3vBE1QWLZNCpMyahxAPx1
hIkHs3zWhqOnGYy7W4s8ZweKVl4cWVMVc8BrDy5qJ1VHzKuzOQN4ON9jSqwJIHV1
iItVopzbnKYAuZ1lseemYhFQz44STntkDFiVvSH4b2NLTwklO7e8dGkynPTwQTOS
ar1AwCxwx11REMq7SofcwmC8XxyJJC9xzJWJmzq3mATSPbtbC++hKvkkwBaTv7Y2
cGrXkaGzT9+0ainJJTILtMGIxETVWPDntNXqqfGQTbU+vjRkRXVe0zl7OxXulida
FStQWuQ+
-----END PUBLIC KEY-----

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