The NHSX Contract Tracing App uses Elliptic Curve Integrated Encryption Scheme (ECIES) with AES. This is a demo page for the setup of this. Initially the Health Authority (HA) creates a unique InstallationID for Bob, and sends their public key (PubHA). Bob then creates a new key pair every day (PubBobD, PrivBobD). Bob then creates a secret \(Z\) using the HA's public key (PubHA) and Bob's daily private key (PrivBobD). This is then used to create an AES encryption key in order to pass Bob's InstallationID. When received by the HA, the HA will take Bob's daily public key (PubBobD), and its private key (PrivHA), and creates the same secret (\(Z\)). This is then used to create the encryption key used by Bob.
NHSX Contract Tracing Method |
Method
An overview of the system is shown in the figure below. Initially, the user (Bob) installs the App, and the Infrastructure Provider (and which runs in the public cloud) sends back: the Health Authority public key (PubHA); an InstallationID (which is unique for Bob), and a symmetric key (that will be used for signing Bluetooth broadcasts). These are stored on Bob’s phone, and the details registered with the Infrastructure Provider, along with half of Bob’s postcode.
When Bob comes in contact with Carol, the contact is stored in a log on Bob’s phone with signal strength and a risk score. When the risk score is high enough, it the log is then sent through Firefox Firebase to the Infrastructure Provide (stored in the public cloud). The NHS then has direct access to the information stored.
The values broadcasted by Bob (BroadcastValues) uses the public key sent within the registration process. Each day the device creates a new ephemeral private key on an elliptic curve (P256):
\(PrivKeyD (daily) = r\)
\(PubKeyD (daily) = rG\)
and where G is the base point on the P-256 elliptic curve. The secret is then:
\(Z = ECDH (PubServer, PrivKeyD)\)
This is the elliptic curve Diffie Hellman and creates a key exchange. Next, a key is generated using X9.63 KDF and SHA-256 to give two 128-bit values (and where we split the result into two parts (Key and IV):
\(Key, IV = KDF(Z,rG)\)
The payload for the message is then:
\(m = (Start Date) || (End Date) || (InstallationID) || (Country Code)\)
and where InstallationID is a 128-bit unique identifier for the person. It is then encrypted with AES (GCM) to give:
\(Cipher, IntCheck = AES(m,IV)\)
where IV is the initialisation vector (salt) used, and IntCheck is the integrity check. The broadcast value to the device is then:
\(BV=(Country Code||PubKeyD||C|| ICV)\)
and where || is a concatenation. This gives a 856-bit broadcast value. This broadcast value will change every day, and where the daily secret is stored on the server:
Only the server has the private key for the public key (PublickeyS), and only it (and Bob) will be able to determine Z.
When the BV is received by Alice and is based onto the central server. The central server will take the public key (PublicKeyD) and then derive Z, and then generate the same encryption key that Bob used. We thus generate a BV every day and a new PublicKeyD. When there is a connection, Bob sends:\(P = (BV || TxPower || TxTime || Auth)\)
and where TxPower is the power of the sender in dBm, TxTime is the transmission time stamp, and Auth is the HMAC relates to he other contents in the payload and keyed using the sending device’s symmetric authentication key. When received, the server can then extract the daily public key (PubKeyD) and then use this with the ECDH method to derive the shared secret (Z). Once we have this, we can then determine the key used to encrypt the message:
Coding
Here is the code to generate this:
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/cipher" "crypto/aes" "io" "time" "fmt" "os" "crypto/hmac" "golang.org/x/crypto/pbkdf2" "encoding/hex" ) func main() { // Bob's public and private key privBOB, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) pubBOB := privBOB.PublicKey InstallationID:="40444321-Bob" CountryCode:="UK" t:=time.Now().String() argCount := len(os.Args[1:]) if (argCount>0) { InstallationID= (os.Args[1])} // HA public and private key privHA, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) pubHA := privHA.PublicKey fmt.Printf("--- Bob sending -----") b, _ := pubBOB.Curve.ScalarMult(pubHA.X, pubHA.Y, privBOB.D.Bytes()) Z := sha256.Sum256((b.Bytes())) block, _ := aes.NewCipher(Z[:]) plaintext:=[]byte(InstallationID+"-"+CountryCode+"-"+t) nonce := make([]byte, 12) // 96 bits for nonce/IV if _, err := io.ReadFull(rand.Reader, nonce); err != nil { panic(err.Error()) } aesgcm,_:= cipher.NewGCM(block) ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) fmt.Printf("\nBluetooth send: [Cipher: %x] [Bob's Public Key: %x, %x]\n", ciphertext,pubBOB.X, pubBOB.Y) fmt.Printf("\nShared key (Bob) %x\n", Z) fmt.Printf("\n\n--- HA receiving -----") // Health Provider side HA, _ := pubHA.Curve.ScalarMult(pubBOB.X, pubBOB.Y, privHA.D.Bytes()) Z = sha256.Sum256((HA.Bytes())) plain, _ := aesgcm.Open(nil, nonce, ciphertext, nil) fmt.Printf("\nShared key (HA) %x\n", Z) fmt.Printf("\nMessage received (HA) %s\n", plain) fmt.Printf("\n\n--- Signing of Bluetooth -----") // Bob's signing key for Bluetooth // Just generate the key sent from HA with a password passwd:="test" salt:=[]byte("000000000000") sign_key := pbkdf2.Key([]byte(passwd), salt, 10000, 32, sha256.New) h := hmac.New(sha256.New, []byte(sign_key)) h.Write([]byte(plaintext)) fmt.Printf("\nAuth sign (HMAC) %s\n", hex.EncodeToString(h.Sum(nil))) // Other details fmt.Printf("\n\n---Details--") fmt.Printf("\nDaily Private key (Bob) %x", privBOB.D) fmt.Printf("\nDaily Public key (Bob) (%x,%x)", pubBOB.X,pubBOB.Y) fmt.Printf("\nPublic key (HA) (%x %x)\n", pubHA.X,pubHA.Y) fmt.Printf("\nPrivate key (HA) %x\n", privHA.D) fmt.Printf("\n\n--ECC Parameters--\n") fmt.Printf(" Name: %s\n",elliptic.P256().Params().Name) fmt.Printf(" N: %x\n",elliptic.P256().Params().N) fmt.Printf(" P: %x\n",elliptic.P256().Params().P) fmt.Printf(" Gx: %x\n",elliptic.P256().Params().Gx) fmt.Printf(" Gy: %x\n",elliptic.P256().Params().Gy) fmt.Printf(" Bitsize: %x\n\n",elliptic.P256().Params().BitSize) }
Sample run
A sample run is:
--- Bob sending -----Bluetooth send: [Cipher: 7cfe6593d6e550e0dee61961294d72b432209331996898a345f64f1a40aa9f035e016bba2c68669ee4a9e0cb39aa4c733a3af81ffd5d02421df92f19bc29b76715c29f785553d17d732151911792f5f27731d3c3f70f] [Bob's Public Key: 7f886d6cea7cbed70115ecf49377fb9db01529de76cbbb59248fd001e8cfbee3, deeecff24515 452b686bd19c03b46c21336a4728fcd86c174bd380e1cdbef85e] Shared key (Bob) 1cd877f4a9351051171704a1f8a27e272e3c91f845a6d8fedd8ceb6c3 4643515 --- HA receiving -----Shared key (HA) 1cd877f4a9351051171704a1f8a27e272e3c91f845a6d8fedd8ceb6c34 643515 Message received (HA) 40444321-Bob-UK-2020-05-06 11:52:02.628966284 +0000 UTC m=+0.022672051 --- Signing of Bluetooth -----Auth sign (HMAC) 0ba52c87b85300c933cf9d4ff9941beace90b0501b3523441cf018b88 5b79c88 ---Details--Daily Private key (Bob) 3df4bea18cca20daf65ec4359311a3234de90dbf3307542836 064cc85a3fc804Daily Public key (Bob) (7f886d6cea7cbed70115ecf49377fb9db01529de76cbbb59248fd001e8cfbee3,deeecff24515452b686bd19c03b46c21336a4728fcd86c174bd380e1cdb ef85e)Public key (HA) (1328c93a89790b3540bc79c4d0e1ff9078c3de2430f88b7b46e957d17 1644db0 60e0abc69a6fbb84bb15d3aa8ca0b5ef407d667db1a0ff826fc8cc42180d4df4) Private key (HA) c4300f2e3f4c6f31af01e1caa27eec02c56922a36b0941fdf52c1da46 6c7373c --ECC Parameters-- Name: P-256 N: ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551 P: ffffffff00000001000000000000000000000000ffffffffffffffffffffffff Gx: 6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 Gy: 4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 Bitsize: 100