How to Create an Apple Wallet NFC encryption keys


Richard Grundy

Updated July 09, 2024 22:26

Overview

This guide is designed to help developers quickly generate an encryption keys for NFC enabled Apple Wallet passes. We'll review the original docs from the Apple Developer Portal, parse through them, and generate a key pair using OpenSSL via command line as well as Ruby.

Once generated, we will extract the value so you can store the private key in your NFC / VAS reader and put the public key into the pass.json for your pass.

The requirements per the docs

The Apple Developer Portal documentation on NFC passes says the following:

(Required) The public encryption key the Value Added Services protocol uses. Use a Base64-encoded X.509 SubjectPublicKeyInfo structure that contains an ECDH public key for group P256.

If you're developing an NFC enabled pass, you probably already know about VAS. But you may not know about how to generate a Base64-encoded X.509 SubjectPublicKeyInfo structure that contains an ECDH public key for group P256.

We will break the statement up, first let's look at "Base64-encoded X.509 SubjectPublicKeyInfo structure...". According to Wikipedia, X.509 is:

X.509 is an International Telecommunication Union (ITU) standard defining the format of public key certificates

The same article talks about when Base64-encoding is appropriate:

.pem – (Privacy-enhanced Electronic Mail) Base64 encoded DER certificate, enclosed between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----.

So this basically means we need to get a public key in pem format. Now the next part of the docs that was hard to parse was "...that contains an ECDH public key for group P256". For this we need to know what ECDH and P256 are. Looks like it's Elliptic-curve Diffie Hellman and a particular standard curve:

Also known as: secp256r1 or prime256v1

Now, there is one thing the Apple documentation doesn't mention, which you can only see if you know exactly how to debug passes:

ERROR: Error Domain=PKPassKitErrorDomain Code=1 "The pass cannot be read because it isn’t valid." UserInfo={NSLocalizedDescription=The pass cannot be read because it isn’t valid., NSUnderlyingError=0x600003b9dc50 {Error Domain=PKPassKitErrorDomain Code=1 "nfc encryptionPublicKey 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaS+TuIUibzeU5OJA+cJu5dGWqKWLFCXjfv37dnFngUVPHqnXkLFJmnHqz4Qc5Kts1mMDqXt6ZarozzMHlHJfGg==' isn't a valid EC compact public key. Valid curves are secp256r1." UserInfo={NSLocalizedDescription=nfc encryptionPublicKey 'MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEaS+TuIUibzeU5OJA+cJu5dGWqKWLFCXjfv37dnFngUVPHqnXkLFJmnHqz4Qc5Kts1mMDqXt6ZarozzMHlHJfGg==' isn't a valid EC compact public key. Valid curves are secp256r1.}}}

The important part is this: isn't a valid EC compact public key

So putting it all together we need a public key in compact pem format from the secp256r1 elliptic curve. Alright, so let's do that.

Generating a key pair via OpenSSL

So we need to generate a key pair, we'll need the private key for later and the public key in compact pem format. We'll create the private key in a file called private.pem and the public key in a file called nfcPubkey.pem. We can do that rather easily via bash:

openssl ecparam -name prime256v1 -genkey -noout -out nfcKey.pem openssl ec -in private.pem -pubout -out nfcPubkey.pem -conv_form compressed cat nfcPubkey.pem

But what if we wanted to do this in a programming language, like ruby?

Generating a key pair programatically

key = OpenSSL::PKey::EC.new('prime256v1').generate_key group = key.public_key.group point = key.public_key asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Sequence([ OpenSSL::ASN1::ObjectId('id-ecPublicKey'), OpenSSL::ASN1::ObjectId(group.curve_name) ]), OpenSSL::ASN1::BitString(point.to_octet_string(:compressed)) ] ) pkey = OpenSSL::PKey::EC.new(asn1.to_der) pub_key_pem = pkey.to_pem puts pub_key_pem

This little ruby script uses OpenSSL bindings and tells OpenSSL to use the prime256v1 curve to generate a private key stored in key and then the compact (aka :compressed) public key in pem format in the pubkeypem variable. When you run it you should see something like this:

-----BEGIN PUBLIC KEY----- MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADkGZpDGxw4yTToVFCVkCa7whytzCc UqkGZh9uBC5LbBw= -----END PUBLIC KEY-----

Extracting the public key for pass.json

So Apple doesn't say this explicitly, but we need to put the public key in a certain format in the pass.json file. We will need to remove the declarations, as well as newlines.

To remove the declarations, just take out the first and last lines that say -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY-----. Then remove the newlines.

We'll need to put the public key in the encryptionPublicKey field, which is nested under the nfc key like so:

{ "logoText": "Your Company Name", "labelColor": "rgb(0, 0, 0)", "nfc": { "message": "[whatever-64-char-message-you-want]", "encryptionPublicKey": "MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgADkGZpDGxw4yTToVFCVkCa7whytzCcUqkGZh9uBC5LbBw=" } }

The easier method to managing public keys

If you'd like to avoid all of these complicated steps and generate passes with ease. We built the platform to do just that, it's called PassNinja. It's free to get started, just click the "Get Started for Free" button in the top right of this website and fill out the form - we'll give you $10 in free credits!

Conclusion

This guide walked you through the steps to create a encryption public key that is VAS compatible using OpenSSL or ruby and where that key lives in the context of a pass.json. It's an important step in creating NFC enabled passes for Apple Wallet.

If you have any feedback on this guide, please do not hesitate to reach out!

Was this article helpful?
Yes No