Skip to main content

Getting Started

warning

Tesla has updated their VCSEC for the new Model S and X, among other things. I'm currently in the process of extracting that. The current stuff should continue to work, but I can't predict for how long it'll continue working. Also, since the car uses 4 byte nonces many libraries will deem this as insecure and won't allow you to do that, and after checking out the new Tesla app, sadly the problem still exists and you must modify these libraries to work with the car :/

Once I extract the protobuf, you'll be able to choose between the new and old version of the documentation!

Edit: I have extracted the new protobuf (and created a personal script to do so easily in the future), the new protobuf file can be found here if you want it :). Will document it when I have time... if you want to contribute, you can document it using Docusaurus's version system, and then create a PR!

tip

I really recommend reading this over to grasp an understanding of how this stuff works. If you just want to see what the car can do, skip down to more info.

Whitelisting your key

To begin working with the vehicle's BLE API, you'll need to generate and whitelist your public key with the vehicle.

To do this, you'll need two things:

  • Generate an EC private key with the NISTP256 curve (aka secp256r1, or prime256v1)
    • Keep this safe! This key is used to sign all your messages.
  • Using your private key, you'll need to generate a public key serialized to bytes in the ANSI X9.62/X9.63 Uncompressed Point format
    • If done correctly, the first byte should always be 0x04
    • I never tried it, but Compressed Point might work too, that is where the first byte is 0x02 or 0x03. If it does work, please tell me so that I can update the documentation! If you're feeling wild and try out any other public key encodings that work, then please notify me too!

We'll call these privateKey, and publicKey respectively.

Next serialize, an unsigned protobuf message from the VCSEC protobuf in the following layout:

UnsignedMessage {
WhitelistOperation: WhitelistOperation {
addKeyToWhitelistAndAddPermissions: PermissionChange {
key: PublicKey {
PublicKeyRaw: <publicKey>
}
permission: WHITELISTKEYPERMISSION_LOCAL_DRIVE
permission: WHITELISTKEYPERMISSION_LOCAL_UNLOCK
permission: WHITELISTKEYPERMISSION_REMOTE_DRIVE
permission: WHITELISTKEYPERMISSION_REMOTE_UNLOCK
}
metadataForKey: KeyMetadata {
keyFormFactor: KEY_FORM_FACTOR_ANDROID_DEVICE
}
}
}

You may add any other permissions as needed but these are the default ones (the ones that let you unlock, and start the vehicle, among other things). We'll call the previously serialized message the protoMsg.

Once you have it serialized to bytes, you need to make a ToVCSECMessage message of the following format:

ToVCSECMessage {
signedMessage: SignedMessage {
protobufMessageAsBytes: <protoMsg>
signatureType: SIGNATURE_TYPE_PRESENT_KEY
}
}

Now, serialize this message to bytes. We'll call this authMsg.

We need to prepare this message by defining a function prependLength:

  • Extends the length of the byte array by two
  • Shifts all bytes to the right by two
  • Sets the first two bytes to the length of the message
    • Note: In most/all cases the byte array will be the final serialized message
someMsg = b'\x01\x02'
prependedMsg = prependLength(someMsg)
print(prependedMsg) # b'\x00\x02\x01\x02'

Once you have serialized protoMsg, and prepended the length, send the message to the vehicle over a normal BluetoothLE connection on the following write characteristic:

Serivce: 00000211-b2d1-43f0-9b88-960cebf8b91e
UUID: 00000212-b2d1-43f0-9b88-960cebf8b91e
Descriptor: 0x2901
Python Example
# Import VCSEC and crypto libraries
import VCSEC
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import serialization

# Function to prepend message length
def prepend_length(message):
return (len(message).to_bytes(2, 'big') + message)

try:
# Try to open and import private key
privKeyFile = open('private_key.pem', 'rb')
privateKey = serialization.load_pem_private_key(privKeyFile.read(), None)
privKeyFile.close()
except FileNotFoundError:
# If private key file not found, generate private keys
privateKey = ec.generate_private_key(ec.SECP256R1())

# Derive public key in X9.62 Uncompressed Point Encoding
publicKey = privateKey.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)

# Print the public key for information purposes
print("Public Key:")
print(publicKey.hex(" "))

# Create a "public key" variable and set its public key to the one we generated
key = VCSEC.PublicKey()
key.PublicKeyRaw = publicKey

# Add perissions to a "permission change" variable and add the key variable to it
permissionChange = VCSEC.PermissionChange()
permissionChange.permission.append(VCSEC.WhitelistKeyPermission_E.WHITELISTKEYPERMISSION_LOCAL_DRIVE)
permissionChange.permission.append(VCSEC.WhitelistKeyPermission_E.WHITELISTKEYPERMISSION_LOCAL_UNLOCK)
permissionChange.permission.append(VCSEC.WhitelistKeyPermission_E.WHITELISTKEYPERMISSION_REMOTE_DRIVE)
permissionChange.permission.append(VCSEC.WhitelistKeyPermission_E.WHITELISTKEYPERMISSION_REMOTE_UNLOCK)
permissionChange.key.CopyFrom(key)

# Create a "key metadata" variable and set its form factor to android (can technically be anything in the enum)
metadataForKey = VCSEC.KeyMetadata()
metadataForKey.keyFormFactor = VCSEC.KeyFormFactor.KEY_FORM_FACTOR_ANDROID_DEVICE

# Create a "whitelist operation" variable and set its permission change variable to the one we created, and also set its key metadata variable to our's
whitelistOperation = VCSEC.WhitelistOperation()
whitelistOperation.addKeyToWhitelistAndAddPermissions.CopyFrom(permissionChange)
whitelistOperation.metadataForKey.CopyFrom(metadataForKey)

# Create an "unsigned message" variable and set its whitelist operation to our's
unsignedMessage = VCSEC.UnsignedMessage()
unsignedMessage.WhitelistOperation.CopyFrom(whitelistOperation)

# Print the unsigned message layout for information purposes
print("\nUnsigned Message Layout:")
print(unsignedMessage)

# Serialize our unsigned message variable
protoMsg = unsignedMessage.SerializeToString()

# Create a "signed message variable" and set its protobuf message to the one we created, and set the signature type to ask the user to tap the key card
signedMessage = VCSEC.SignedMessage()
signedMessage.protobufMessageAsBytes = protoMsg
signedMessage.signatureType = VCSEC.SignatureType.SIGNATURE_TYPE_PRESENT_KEY

# Create a "to vcsec message" and assign its signed message to the one we created
toVCSECMessage = VCSEC.ToVCSECMessage()
toVCSECMessage.signedMessage.CopyFrom(signedMessage)

# Print the to vcsec message layout for information purposes
print("\nTo VCSEC Message Layout:")
print(toVCSECMessage)

# Serialize the to vcsec message
authMsg = toVCSECMessage.SerializeToString()

# Prepend the length to our message
prependedMsg = prepend_length(authMsg)

# Print the final message needed to send to car
print("\nFinal Message To Send To Car:")
print(prependedMsg.hex(" "))

Vehicle BLE Name

The vehicle's BLE name is fairly easy to figure out. You need to do the following to get the whole name except the last character:

  • Get the vehicle's VIN, we'll call this vin
  • Get a SHA1 hash of it, we'll call this vinSHA
  • Get the vinSHA as a hex string, and keep only the first 16 characters, we'll call this middleSection
  • Prepend "S" to middleSection and that is it. We're not sure how to find the last character yet, but you don't have to pay attention to that
  • All that is currently known about the last letter is that it must be one of the following:
    • C
    • R
    • D
    • P
Python Example
# Import Crypto Library
from cryptography.hazmat.primitives import hashes

# Example VIN As Bytestring
vin = bytes("5YJ3E1EA1KF000000", "UTF8")

# Create A SHA1 Hasher
digest = hashes.Hash(hashes.SHA1())

# Put VIN Into The Hasher
digest.update(vin)

# Set vinSHA To The Hex String Of The VIN Hash
vinSHA = digest.finalize().hex()

# Get The First 16 Characters
middleSection = vinSHA[0:16]

# Prepend S And Append ? As We Don't Know The Last Character
bleName = "S" + middleSection + "?"

# Print The Final BLE Name
print(bleName) # Sa6bab0d54ffaecf1?

Response

The vehicle will always respond with a FromVCSECMessage message.

The first two bytes of this message represent the length of the message.

The rest of the message can then be decoded. Below is an example response to a whilelist request:

FromVCSECMessage {
commandStatus: CommandStatus {
operationStatus: OPERATIONSTATUS_WAIT
}
}

It should be received on the following indication characteristic:

Serivce: 00000211-b2d1-43f0-9b88-960cebf8b91e
UUID: 00000213-b2d1-43f0-9b88-960cebf8b91e
Descriptor: 0x2901

Now tap an existing key card, and you should recieve the following message:

FromVCSECMessage {
commandStatus: CommandStatus {
whitelistOperationStatus: WhitelistOperation_status {
signerOfOperation: KeyIdentifier {
publicKeySHA1: 0x5f0d64b3
}
operationStatus: OPERATIONSTATUS_OK # usually won't get printed out along
# with the message as its tag is 0, and
# therefore it gets truncated from the
# message, but that is the default value
# of the enum when there is no value
}
}
}

Congrats! You whitelisted your first key!

Python Example
# Import VCSEC Library
import VCSEC

# Example Message That You Should Receive Upon Sending The Whitelist MEssage
exampleMsgRcvd = b'\x00\x04\x22\x02\x08\x01'
# Extracting The Expected Length Of The Message
expectedLength = int.from_bytes(exampleMsgRcvd[0:2], "big")

# Make Sure Message Of Expected Length
if len(exampleMsgRcvd[2:]) == expectedLength:
print("Message Length Correct!")
else:
print("Message Not Expected Length, Exiting...")
exit()

# Decode The Received Message
decodedMsg = VCSEC.FromVCSECMessage()
decodedMsg.ParseFromString(exampleMsgRcvd[2:])

# Check If The Car Is Telling Us To Wait For Something To Happen (the keycard getting tapped in this case)
if decodedMsg.commandStatus.operationStatus == VCSEC.OperationStatus_E.OPERATIONSTATUS_WAIT:
print("Car Asking To Tap Key Card!")

# Key Card Tapped Example Message
exampleKCTappedMsgRcvd = b'\x00\x0c\x22\x0a\x1a\x08\x12\x06\x0a\x04\x5f\x0d\x64\xb3'
# Extract The Length
expectedKCTappedLength = int.from_bytes(exampleKCTappedMsgRcvd[0:2], "big")

# Check If The Message Length Is Correct
if len(exampleKCTappedMsgRcvd[2:]) == expectedKCTappedLength:
print("\nKey Card Tapped Message Length Correct!")
else:
print("\nKey Card Tapped Message Not Expected Length, Exiting...")
exit()

# Decode The Message
decodedMsg = VCSEC.FromVCSECMessage()
decodedMsg.ParseFromString(exampleKCTappedMsgRcvd[2:])

# Check If Everything Is Ok
if decodedMsg.commandStatus.whitelistOperationStatus.operationStatus == VCSEC.OperationStatus_E.OPERATIONSTATUS_OK:
print("Key Card Tapped!")

Getting the ephemeral key

To sign messages to send to the vehicle, you'll need to get the vehicle's ephemeral key.

By definition of its name, this key should change every so often, but I never experienced it change in the many months of testing that I've done.

First, generate the keyId. This can be done by taking the first 4 bytes of a SHA1 digest of publicKey.

Now that you have your keyId, you'll need to request the vehicle's ephemeral key. You can do this by making a ToVCSECMessage message with the following layout:

ToVCSECMessage {
unsignedMessage: UnsignedMessage {
InformationRequest: InformationRequest {
informationRequestType: INFORMATION_REQUEST_TYPE_GET_EPHEMERAL_PUBLIC_KEY
keyId: KeyIdentifier {
publicKeySHA1: <keyId>
}
}
}
}

Now, serialize this message to bytes as getEphemeralBytes. Prepend the message with prependLength(getEphemeralBytes), and send the value to the vehicle.

The vehicle should respond with a message in the ANSI X9.62/X9.63 Uncompressed Point format, with the NISTP256 format. When decoded, it should looks something like this:

FromVCSECMessage {
sessionInfo: SessionInfo {
publicKey: <vehicle's ephemeral key>
}
}

Now that you have the ephemeralKey, generate an AES secret from the ephemeralKey and your secret key. Next, make a SHA1 of it, and put take the first 16 bytes to form your sharedKey.

Python Example
# Import the VCSEC and the crypto libraries
import VCSEC
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization

# Function to prepend message length
def prependLength(message):
return (len(message).to_bytes(2, 'big') + message)

try:
# Try to open and import private key
privKeyFile = open('private_key.pem', 'rb')
privateKey = serialization.load_pem_private_key(privKeyFile.read(), None)
privKeyFile.close()
except FileNotFoundError:
# If private key file not found, generate private keys
privateKey = ec.generate_private_key(ec.SECP256R1())

# Derive public key in X9.62 Uncompressed Point Encoding
publicKey = privateKey.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)

# Hash our public key to get our key id and extract the first 4 bytes
digest = hashes.Hash(hashes.SHA1())
digest.update(publicKey)
keyId = digest.finalize()[:4]

# Create a "key identifier" message and assign our key id to it
keyIdentifier = VCSEC.KeyIdentifier()
keyIdentifier.publicKeySHA1 = keyId

# Create an "information request" message and set its request type and our key id
informationRequest = VCSEC.InformationRequest()
informationRequest.informationRequestType = VCSEC.InformationRequestType.INFORMATION_REQUEST_TYPE_GET_EPHEMERAL_PUBLIC_KEY
informationRequest.keyId.CopyFrom(keyIdentifier)

# Put the information request message on an unsigned message
unsignedMessage = VCSEC.UnsignedMessage()
unsignedMessage.InformationRequest.CopyFrom(informationRequest)

# Put all of that onto a to vcsec message
toVCSEC = VCSEC.ToVCSECMessage()
toVCSEC.unsignedMessage.CopyFrom(unsignedMessage)

# Print the layout for information purposes
print("To VCSEC Message Layout:")
print(toVCSEC)

# Serialize it, prepend the length, and print it out for sending
getEphemeralBytes = toVCSEC.SerializeToString()
prependedMessage = prependLength(getEphemeralBytes)
print("Ephemeral Key Request Message:")
print(prependedMessage.hex(" "))

# Example of a message you get in response
exampleMsgResponse = b'\x00\x45\x12\x43\x1a\x41\x04\x79\xc0\x50\x4a\x21\x6f\xfc\x26\x46\xb7\x57\x80\x39\x9f\x1c\xe1\x23\xf4\x01\x56\x1b\x68\x5c\x31\x83\x64\xfa\x96\xcc\x3f\xe6\x7a\x5a\xc5\x04\x8c\x44\x7a\xf8\x8d\x91\x52\x86\x5a\x1e\xfc\x15\xbb\xd5\x68\x98\xdd\x2c\x46\xf7\xa1\x9b\xad\x4f\xb2\x80\x52\xc4\x60'
# Extract the length
exampleMsgResponseLen = int.from_bytes(exampleMsgResponse[0:2], "big")

# Make sure the length is correct
if len(exampleMsgResponse[2:]) == exampleMsgResponseLen:
print("\nMessage Length Correct")
else:
print("\nMessage Not Of Expected Length, Exiting...")
exit()

# Parse the message into a variable that we can work with
fromVCSEC = VCSEC.FromVCSECMessage()
fromVCSEC.ParseFromString(exampleMsgResponse[2:])

# Print out its layout for information purposes
print("Example Response Message Layout:")
print(fromVCSEC)

# Extract the ephemeral key
ephemeralKey = fromVCSEC.sessionInfo.publicKey
print("Extracted Ephemeral Key:")
print(ephemeralKey.hex(" "))

# Put the known curve of the key into a variable
curve = ec.SECP256R1()
# Use the curve to put the ephemeral key into a workable format
ephemeralKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, ephemeralKey)
# Prepare a hasher
hasher = hashes.Hash(hashes.SHA1())
# Derive an AES secret from our private key and the car's public key
aesSecret = privateKey.exchange(ec.ECDH(), ephemeralKey)
# Put the AES secret into the hasher
hasher.update(aesSecret)
# Put the first 16 bytes of the hash into a shared key variable
sharedKey = hasher.finalize()[:16]

# Print the shared key for information purposes
print("\nShared Key:")
print(sharedKey.hex(' '))

Authenticating

warning

Due to Tesla's implementation of message signing, you are required to use a 4 byte long nonce, which is a problem as it might be deemed insecure by some libraries (such as the one in the example of this section), and they must be modified to remove the limit, or the encryption must be done without a library if that such a modified library is not available.

For the vehicle to know that you are connected and to be able to send/receive messages, you need to generate an authentication message in the following format:

UnsignedMessage {
authenticationResponse: AuthenticationResponse {
authenticationLevel: AUTHENTICATION_LEVEL_NONE
}
}

Set a variable called counter to 1 (can be any number that hasn't been used as the counter for this key and must match the rule counter >= 1 and counter <= 65535), which you should increment (or change to an unused one) each time after using.

Now, you need to encrypt this message using the sharedKey with AES encryption in GCM mode, with a nonce of the counter, split into 4 bytes in big-endian, where the least significant (smaller in value) byte is at the end.

So, if counter is 23 (0x17), the nonce will be b'\x00\x00\x00\x17'.

We'll call this encryptedMsgWithTag as it has a signature tag that we'll need to separate.

You should separate the encrypted/signed message into 2 variables, encryptedMsg (from byte 0 to encryptedMsgWithTag.length - 16), and msgSignature (from byte encryptedMsgWithTag.length - 16 to encryptedMsgWithTag.length).

Now you can send the message to the vehicle! Use the following format:

ToVCSECMessage {
signedMessage: SignedMessage {
protobufMessageAsBytes: <encryptedMsg>
signature: <msgSignature>
counter: <counter>
keyId: <keyId>
}
}

Now, prepare the message by serializing and prepending the length of the message. It is now ready to be sent to the vehicle!

Once complete, as long as you stay connected to the vehicle's BLE, your key will stay marked in the vehicle as an active key.

Python Example
import VCSEC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization

# Function to prepend message length
def prependLength(message):
return (len(message).to_bytes(2, 'big') + message)

try:
# Try to open and import private key
privKeyFile = open('private_key.pem', 'rb')
privateKey = serialization.load_pem_private_key(privKeyFile.read(), None)
privKeyFile.close()
except FileNotFoundError:
# If private key file not found, generate private keys
privateKey = ec.generate_private_key(ec.SECP256R1())

# Derive public key in X9.62 Uncompressed Point Encoding
publicKey = privateKey.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)

# Hash our public key to get our key id and extract the first 4 bytes
digest = hashes.Hash(hashes.SHA1())
digest.update(publicKey)
keyId = digest.finalize()[:4]

# Example Ephemeral Key
ephemeralKey = b'\x04\x79\xc0\x50\x4a\x21\x6f\xfc\x26\x46\xb7\x57\x80\x39\x9f\x1c\xe1\x23\xf4\x01\x56\x1b\x68\x5c\x31\x83\x64\xfa\x96\xcc\x3f\xe6\x7a\x5a\xc5\x04\x8c\x44\x7a\xf8\x8d\x91\x52\x86\x5a\x1e\xfc\x15\xbb\xd5\x68\x98\xdd\x2c\x46\xf7\xa1\x9b\xad\x4f\xb2\x80\x52\xc4\x60'

# Put the known curve of the key into a variable
curve = ec.SECP256R1()
# Use the curve to put the ephemeral key into a workable format
ephemeralKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, ephemeralKey)
# Prepare a hasher
hasher = hashes.Hash(hashes.SHA1())
# Derive an AES secret from our private key and the car's public key
aesSecret = privateKey.exchange(ec.ECDH(), ephemeralKey)
# Put the AES secret into the hasher
hasher.update(aesSecret)
# Put the first 16 bytes of the hash into a shared key variable
sharedKey = hasher.finalize()[:16]

# Create an authentication response message with a level of none
authenticationResponse = VCSEC.AuthenticationResponse()
authenticationResponse.authenticationLevel = VCSEC.AuthenticationLevel_E.AUTHENTICATION_LEVEL_NONE

# Put that message on an unsigned message and serialize it
unsignedMessage = VCSEC.UnsignedMessage()
unsignedMessage.authenticationResponse.CopyFrom(authenticationResponse)
unsignedMessageS = unsignedMessage.SerializeToString()

# Print out the unsigned message layout for information purposes
print("Unsigned Message Layout:")
print(unsignedMessage)

# Set counter to 1 and create a nonce from it
counter = 1
nonce = int.to_bytes(counter, 4, "big")

# Initialize an AES encryptor in GCM mode and encrypt the message using it
encryptor = AESGCM(sharedKey)
# This will error out if you're using the latest version of the cryptorgraphy.io library as I'm using a 4 byte long nonce
try:
encryptedMsgWithTag = encryptor.encrypt(nonce, unsignedMessageS, None)
except ValueError:
print("Error: The cryptography.io library doesn't allow nonces as small as 4 bytes anymore. Please modify the if statement in the _check_params(nonce, data, associated_date) function in the cryptography.hazmat.primitives.ciphers.aead.AESGCM class to require the minimum length to be 1")
exit()

# Put all of this onto a "signed message" variable
signedMessage = VCSEC.SignedMessage()
signedMessage.protobufMessageAsBytes = encryptedMsgWithTag[:-16]
signedMessage.counter = counter
signedMessage.signature = encryptedMsgWithTag[-16:]
signedMessage.keyId = keyId

# Put all of this onto a "to vcsec" message
toVCSECMessage = VCSEC.ToVCSECMessage()
toVCSECMessage.signedMessage.CopyFrom(signedMessage)

# Print it out for information purposes
print("\nTo VCSEC Message Layout:")
print(toVCSECMessage)

# Serialize the message and prepend the length
msg = toVCSECMessage.SerializeToString()
msg = prependLength(msg)

# Print the message to be sent to the car
print("\nAuth Message To Send To Car:")
print(msg.hex(" "))

Authenticating for other things

If you want to let the vehicle do things automatically without sending messages to do things (i.e. like the Tesla App does when you're next to/inside the vehicle), you can send it an authentication response of a higher level to give permission to do everything under and including that level, so say level = 'UNLOCK', you are only letting the vehicle unlock, but say you do level = 'DRIVE', you can unlock or drive. Also, everytime the vehicle does something automatically, the vehicle resets level to NONE.

All that you have to do is serialize and sign an auth message where the distance is optional, but if not sent to the vehicle, it will just automatically assume that you're next to/inside the vehicle:

UnsignedMessage {
AuthenticationResponse: AuthenticationResponse {
authenticationLevel: AUTHENTICATION_LEVEL_<LEVEL>
estimatedDistance: <distance> # optional
}
}

Once you sign that message, I'll call it signedAuthMsg, and its signature signedAuthSign, and turn it into a ToVCSECMessage message like this, which you then serialize, prepend the length, and send to the vehicle:

ToVCSECMessage {
signedMessage: SignedMessage {
protobufMessageAsBytes: <signedAuthMsg>
signature: <signedAuthSign>
counter: <counter>
keyId: <keyId>
}
}
note

I recommend only changing this to other auth levels when the car requests it. Here's an example of a message you may get from the car, requesting to unlock it (yes I know this says drive, but that's just how it is):

FromVCSECMessage {
authenticationRequest: AuthenticationRequest {
sessionInfo: SessionInfo {
token: "some random token that i don't know the use of"
}
requestedLevel: AUTHENTICATION_LEVEL_DRIVE
}
}

In this case you'll just send the car back the following message:

UnsignedMessage {
AuthenticationResponse: AuthenticationResponse {
authenticationLevel: AUTHENTICATION_LEVEL_DRIVE
}
}

Don't worry about the walk-away car lock, when you walk away the car will automatically lock

Python Example
import VCSEC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization

# Function to prepend message length
def prependLength(message):
return (len(message).to_bytes(2, 'big') + message)

try:
# Try to open and import private key
privKeyFile = open('private_key.pem', 'rb')
privateKey = serialization.load_pem_private_key(privKeyFile.read(), None)
privKeyFile.close()
except FileNotFoundError:
# If private key file not found, generate private keys
privateKey = ec.generate_private_key(ec.SECP256R1())

# Derive public key in X9.62 Uncompressed Point Encoding
publicKey = privateKey.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)

# Hash our public key to get our key id and extract the first 4 bytes
digest = hashes.Hash(hashes.SHA1())
digest.update(publicKey)
keyId = digest.finalize()[:4]

# Example Ephemeral Key
ephemeralKey = b'\x04\x79\xc0\x50\x4a\x21\x6f\xfc\x26\x46\xb7\x57\x80\x39\x9f\x1c\xe1\x23\xf4\x01\x56\x1b\x68\x5c\x31\x83\x64\xfa\x96\xcc\x3f\xe6\x7a\x5a\xc5\x04\x8c\x44\x7a\xf8\x8d\x91\x52\x86\x5a\x1e\xfc\x15\xbb\xd5\x68\x98\xdd\x2c\x46\xf7\xa1\x9b\xad\x4f\xb2\x80\x52\xc4\x60'

# Put the known curve of the key into a variable
curve = ec.SECP256R1()
# Use the curve to put the ephemeral key into a workable format
ephemeralKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, ephemeralKey)
# Prepare a hasher
hasher = hashes.Hash(hashes.SHA1())
# Derive an AES secret from our private key and the car's public key
aesSecret = privateKey.exchange(ec.ECDH(), ephemeralKey)
# Put the AES secret into the hasher
hasher.update(aesSecret)
# Put the first 16 bytes of the hash into a shared key variable
sharedKey = hasher.finalize()[:16]

# Example of an auth request message
exampleAuthMsgRcvd = b'\x00\x0c\x1a\x0a\x12\x06\x0a\x04\x00\x01\x0f\x2c\x18\x02'
# Extract the length
exampleAuthMsgRcvdLen = int.from_bytes(exampleAuthMsgRcvd[0:2], "big")

# Make sure the length is correct
if len(exampleAuthMsgRcvd[2:]) == exampleAuthMsgRcvdLen:
print("Message Length Correct")
else:
print("Message Not Of Expected Length, Exiting...")
exit()

fromVCSEC = VCSEC.FromVCSECMessage()
fromVCSEC.ParseFromString(exampleAuthMsgRcvd[2:])

print("Auth Request Message Layout:")
print(fromVCSEC)

# Create an authentication response message with the level requested
authenticationResponse = VCSEC.AuthenticationResponse()
authenticationResponse.authenticationLevel = fromVCSEC.authenticationRequest.requestedLevel

# Put that message on an unsigned message and serialize it
unsignedMessage = VCSEC.UnsignedMessage()
unsignedMessage.authenticationResponse.CopyFrom(authenticationResponse)
unsignedMessageS = unsignedMessage.SerializeToString()

# Print out the unsigned message response layout for information purposes
print("\nUnsigned Message Response Layout:")
print(unsignedMessage)

# Set counter to 2 and create a nonce from it
counter = 2
nonce = int.to_bytes(counter, 4, "big")

# Initialize an AES encryptor in GCM mode and encrypt the message using it
encryptor = AESGCM(sharedKey)
# This will error out if you're using the latest version of the cryptorgraphy.io library as I'm using a 4 byte long nonce
try:
encryptedMsgWithTag = encryptor.encrypt(nonce, unsignedMessageS, None)
except ValueError:
print("Error: The cryptography.io library doesn't allow nonces as small as 4 bytes anymore. Please modify the if statement in the _check_params(nonce, data, associated_date) function in the cryptography.hazmat.primitives.ciphers.aead.AESGCM class to require the minimum length to be 1")
exit()

# Put all of this onto a "signed message" variable
signedMessage = VCSEC.SignedMessage()
signedMessage.protobufMessageAsBytes = encryptedMsgWithTag[:-16]
signedMessage.counter = counter
signedMessage.signature = encryptedMsgWithTag[-16:]
signedMessage.keyId = keyId

# Put all of this onto a "to vcsec" message
toVCSECMessage = VCSEC.ToVCSECMessage()
toVCSECMessage.signedMessage.CopyFrom(signedMessage)

# Print it out for information purposes
print("\nTo VCSEC Message Layout:")
print(toVCSECMessage)

# Serialize the message and prepend the length
msg = toVCSECMessage.SerializeToString()
msg = prependLength(msg)

# Print the message to be responded to the car with
print("\nAuth Message To Send To Car:")
print(msg.hex(" "))

Sending manual actions

Say a user interacts with an app or needs to do something that can't be done automatically. In that case you need to send an RKE action. You can send those by making a message in the following format, signing it, prepending length, and sending it to the vehicle like with any other signed message:

UnsignedMessage {
RKEAction: <any rke action>
}
Python Example
import VCSEC
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives import hashes, serialization

# Function to prepend message length
def prependLength(message):
return (len(message).to_bytes(2, 'big') + message)

try:
# Try to open and import private key
privKeyFile = open('private_key.pem', 'rb')
privateKey = serialization.load_pem_private_key(privKeyFile.read(), None)
privKeyFile.close()
except FileNotFoundError:
# If private key file not found, generate private keys
privateKey = ec.generate_private_key(ec.SECP256R1())

# Derive public key in X9.62 Uncompressed Point Encoding
publicKey = privateKey.public_key().public_bytes(serialization.Encoding.X962, serialization.PublicFormat.UncompressedPoint)

# Hash our public key to get our key id and extract the first 4 bytes
digest = hashes.Hash(hashes.SHA1())
digest.update(publicKey)
keyId = digest.finalize()[:4]

# Example Ephemeral Key
ephemeralKey = b'\x04\x79\xc0\x50\x4a\x21\x6f\xfc\x26\x46\xb7\x57\x80\x39\x9f\x1c\xe1\x23\xf4\x01\x56\x1b\x68\x5c\x31\x83\x64\xfa\x96\xcc\x3f\xe6\x7a\x5a\xc5\x04\x8c\x44\x7a\xf8\x8d\x91\x52\x86\x5a\x1e\xfc\x15\xbb\xd5\x68\x98\xdd\x2c\x46\xf7\xa1\x9b\xad\x4f\xb2\x80\x52\xc4\x60'

# Put the known curve of the key into a variable
curve = ec.SECP256R1()
# Use the curve to put the ephemeral key into a workable format
ephemeralKey = ec.EllipticCurvePublicKey.from_encoded_point(curve, ephemeralKey)
# Prepare a hasher
hasher = hashes.Hash(hashes.SHA1())
# Derive an AES secret from our private key and the car's public key
aesSecret = privateKey.exchange(ec.ECDH(), ephemeralKey)
# Put the AES secret into the hasher
hasher.update(aesSecret)
# Put the first 16 bytes of the hash into a shared key variable
sharedKey = hasher.finalize()[:16]

# Put an rke action on an unsigned message and serialize it
# It will technically be empty, but the default action is to unlock the car as there is nothing else on the unsigned message
unsignedMessage = VCSEC.UnsignedMessage()
unsignedMessage.RKEAction = VCSEC.RKEAction_E.RKE_ACTION_UNLOCK
unsignedMessageS = unsignedMessage.SerializeToString()

# Print out the unsigned message layout for information purposes
print("Unsigned Message Layout:")
print(unsignedMessage)

# Set counter to 3 and create a nonce from it
counter = 3
nonce = int.to_bytes(counter, 4, "big")

# Initialize an AES encryptor in GCM mode and encrypt the message using it
encryptor = AESGCM(sharedKey)
# This will error out if you're using the latest version of the cryptorgraphy.io library as I'm using a 4 byte long nonce
try:
encryptedMsgWithTag = encryptor.encrypt(nonce, unsignedMessageS, None)
except ValueError:
print("Error: The cryptography.io library doesn't allow nonces as small as 4 bytes anymore. Please modify the if statement in the _check_params(nonce, data, associated_date) function in the cryptography.hazmat.primitives.ciphers.aead.AESGCM class to require the minimum length to be 1")
exit()

# Put all of this onto a "signed message" variable
signedMessage = VCSEC.SignedMessage()
signedMessage.protobufMessageAsBytes = encryptedMsgWithTag[:-16]
signedMessage.counter = counter
signedMessage.signature = encryptedMsgWithTag[-16:]
signedMessage.keyId = keyId

# Put all of this onto a "to vcsec" message
toVCSECMessage = VCSEC.ToVCSECMessage()
toVCSECMessage.signedMessage.CopyFrom(signedMessage)

# Print it out for information purposes
print("\nTo VCSEC Message Layout:")
print(toVCSECMessage)

# Serialize the message and prepend the length
msg = toVCSECMessage.SerializeToString()
msg = prependLength(msg)

# Print the message to be sent to the car
print("\nRKE Action Message To Send To Car:")
print(msg.hex(" "))

More Info

For more info, you can begin looking at ToVCSECMessage (UnsignedMessage in particular) to see things you can send to the car, and FromVCSECMessage to see things you receive to the car.

Don't forget, VCSEC is the vehicle's secondary security system, so you send ToVCSECMessage messages, and the vehicle sends you back FromVCSECMessage messages.

note

There is only one exception to this rule: when you added your key as keyfob. In this case you would use FromKeyfobMessage instead of ToVCSECMessage, and ToKeyfobMessage instead of FromVCSECMessage.

There is also an exception for tire pressure sensors, but I have absolutely no idea of how they work, and don't have time to research.