另辟蹊径的特斯拉卡片形式的数字车钥匙


文章目录

  • 另辟蹊径的特斯拉卡片形式的数字车钥匙
  • 前言
  • 一、特斯拉数字车钥匙卡的核心是什么?
  • 二、特斯拉卡片数字车钥匙的核心认证APDU
    • 1.关键TeslaAuth_APDU
    • 2.认证内部原理分析
    • 3.设计理念
  • 三、Java编写的模拟车端与真实特斯拉卡片车钥匙的Demo程序
  • 四、程序实际运行的特斯拉车端与卡端交互数据
  • 五、Python版本的模拟车端参考代码
  • 六、特斯拉手机车钥匙未来展望
  • 总结

前言

第一篇文章我们大概评价了数字车钥匙CCC3.0,里面经典的PKI体系的数字证书体系让人眼花缭乱,然后好奇当下火爆到极致的特斯拉数字车钥匙是怎么玩的呢,虽然没有实测,但通过网上两个核心的代码和中继攻击数据Log的互相印证,基本应该可以理解为是真实的情况。


一、特斯拉数字车钥匙卡的核心是什么?

卡的核心安全理论依然是椭圆曲线密码ECC,但特斯拉卡区别与CCC3.0的最大特点,直接干掉CA,自成一派,不考虑其他车钥匙,车中苹果特斯拉确实有这个实力,如果干掉CA那么车钥匙的实现就简单太多了,直接注册卡的公钥,干脆简洁,好比PGP直接给公钥模式,只要保障注册卡公钥是安全就可以了,还有车里面系统安全的,其实这样也是合理的,如果黑客可以修改车里面的公钥,即使用CA体系,黑客也可以修改CA公钥。

二、特斯拉卡片数字车钥匙的核心认证APDU

1.关键TeslaAuth_APDU

80 11 00 51 04 64字节车随机ECDH挑战 16字节挑战
16字节应答 90 00

2.认证内部原理分析

因为没有开源协议,只能根据代码分析,
准备阶段:卡内部生成随机密钥对d,dG是公钥,注册公钥dG到车内。
认证流程:

  1. 车NFC读头连接卡片选择特斯拉应用,然后获取卡片公钥dG
  2. 车生成随机数K,计算KG做为挑战ECDH,同时产生16字节随机数Random一起发送给卡片
  3. 卡片用自己的私钥d和挑战KG一起计算dKG得64字节共享密钥,取SHA1(dKG)前16字节做AES密钥
  4. 卡片用AESKey加密Random得16字节Response
  5. 车可以并行用随机数K和卡片公钥dG计算会话密钥KdG,然后同样取SHA1(KdG)前16字节做AES密钥加密Random得到CarCipher
  6. 比对CarChipher?=Response 完成认证流程
    核心认证交互就3条APDU就完成了。

3.设计理念

极致的简洁和安全,相比于CCC3.0蓝牙独立签名开锁模式,卡用了AES转化证明模式,安全性一样,但空中NFC的传输字节丛64到16字节会大大提高NFC通讯的稳定性,签名是一种证明和不可否认性,开锁只要证明有私钥就行,所以特斯拉采用对称转化在NFC场景是非常合理和高效的,而且如果车端充分利用多线程,可以并行计算会话密钥KdG,这样认证时间就是1ms内可以完成的aes解密对比后12字节,如果采用签名验证机制,必须等到签名才能做ECC运算,运算时间要做到1ms还是蛮难的,但这样巧妙一转化就轻松完成1ms验证任务,根据京东购买的实际特斯拉卡片数据验证发现卡面内部没有加随机数,这种情况下车端可以提前计算Response,这种情况下车端验证时间几乎为0。

三、Java编写的模拟车端与真实特斯拉卡片车钥匙的Demo程序

import javax.crypto.*;
import javax.crypto.spec.SecretKeySpec;
import javax.smartcardio.*;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.*;
import java.util.Arrays;
import java.util.List;import apdu4j.pcsc.TerminalManager;
import apdu4j.pcsc.terminals.LoggingCardTerminal;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.util.encoders.Hex;public class PCSCTesla {static TerminalFactory terminalFactory = null;static List<CardTerminal> terminals = null;static CardTerminal cardTerminal = null;static LoggingCardTerminal loggingCardTerminal = null;static Card card = null;static CardChannel cardChannel = null;static KeyPairGenerator keyGen = null;static ECFieldFp ecFp = null;static EllipticCurve Curve = null;static ECPoint G = null;static ECParameterSpec ecSpec = null;static KeyFactory keyFactory = null;static KeyAgreement carKeyAgree = null;public static byte[] SendAPDU(byte[] sendAPDU) throws CardException {CommandAPDU commandAPDU = new CommandAPDU(sendAPDU);ResponseAPDU responseAPDU = cardChannel.transmit(commandAPDU);return responseAPDU.getBytes();}public static void ConnectCard() throws CardException {terminalFactory = TerminalFactory.getDefault();terminals = terminalFactory.terminals().list();System.out.println("Terminals: " + terminals);// get the 0 or 1 or 2 or 3 or 4 or 5? terminalcardTerminal = terminals.get(4);loggingCardTerminal = LoggingCardTerminal.getInstance(cardTerminal);try {card = loggingCardTerminal.connect("T=1");System.out.println("Terminal connected");cardChannel = card.getBasicChannel();} catch (Exception e) {System.out.println("Terminal NOT connected: ");}}public static void CarECCInit() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException {Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());BigInteger p = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF", 16);BigInteger a = new BigInteger("FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC", 16);BigInteger b = new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);BigInteger Gx = new BigInteger("6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296", 16);BigInteger Gy = new BigInteger("4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5", 16);BigInteger n = new BigInteger("ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);keyGen = KeyPairGenerator.getInstance("ECDH", "BC");ecFp = new ECFieldFp(p);Curve = new EllipticCurve(ecFp, a, b);G = new ECPoint(Gx, Gy);ecSpec = new ECParameterSpec(Curve, G, n, 1);keyGen.initialize(ecSpec, new SecureRandom());keyFactory = java.security.KeyFactory.getInstance("EC");carKeyAgree = KeyAgreement.getInstance("ECDH");}public static byte[] CarGenECDHKey() throws InvalidKeyException {KeyPair CarPair = keyGen.generateKeyPair();ECPrivateKey privateKey = (ECPrivateKey) CarPair.getPrivate();carKeyAgree.init(CarPair.getPrivate());byte[] privateKeyS = privateKey.getS().toByteArray();System.out.println("Car Private Key is");System.out.println(new String(Hex.encode(privateKeyS)));ECPublicKey CarPubKey = (ECPublicKey) CarPair.getPublic();BCECPublicKey BCCarPubKey = (BCECPublicKey) CarPubKey;return BCCarPubKey.getQ().getEncoded(false);}public static byte[] CarGetCipherUsingCardPubKey(byte[] carChallenge, byte[] cardPubKey) throws InvalidKeySpecException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {byte[] CardPubKeyX = new byte[32];byte[] CardPubKeyY = new byte[32];System.arraycopy(cardPubKey, 1, CardPubKeyX, 0, 32);System.arraycopy(cardPubKey, 1 + 32, CardPubKeyY, 0, 32);BigInteger bigIntegerCardPubKeyX = new BigInteger(1, CardPubKeyX);BigInteger bigIntegerCardPubKeyY = new BigInteger(1, CardPubKeyY);ECPoint publicPoint = new ECPoint(bigIntegerCardPubKeyX, bigIntegerCardPubKeyY);ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(publicPoint, ecSpec);ECPublicKey Card_PublicKey = (ECPublicKey) keyFactory.generatePublic(pubKeySpec);carKeyAgree.doPhase(Card_PublicKey, true);byte[] sharedSecret = carKeyAgree.generateSecret();System.out.println("Shared secret is");System.out.println(new String(Hex.encode(sharedSecret)));// Derive aes key from the shared secret and both public keysMessageDigest hash = MessageDigest.getInstance("SHA1");byte[] hashValue = hash.digest(sharedSecret);System.out.println("aKeyAgree digest is");System.out.println(new String(Hex.encode(hashValue)));KeyGenerator keyGenHandle = KeyGenerator.getInstance("AES");int keySize = 128;keyGenHandle.init(keySize);byte[] aesKey = new byte[16];System.arraycopy(hashValue, 0, aesKey, 0, 16);System.out.println("AES Key is");System.out.println(new String(Hex.encode(aesKey)));SecretKeySpec aesKeySpec = new SecretKeySpec(aesKey, "AES");Cipher aesCipher = Cipher.getInstance("AES/ECB/NoPadding");aesCipher.init(Cipher.ENCRYPT_MODE, aesKeySpec);return aesCipher.doFinal(carChallenge);}public static boolean CarAuthCard() throws CardException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, InvalidKeySpecException, NoSuchAlgorithmException, BadPaddingException {byte[] challenge = new byte[16];SecureRandom secRand = new SecureRandom();secRand.nextBytes(challenge);String strChallenge = new String(Hex.encode(challenge));System.out.println("challenge is");System.out.println(strChallenge);byte[] apduSelectAID = Hex.decode("00a404000a7465736c614c6f67696301");//byte[] apduSelectAID = Hex.decode("00a4040006010203040501");SendAPDU(apduSelectAID);byte[] apduGetPubKey = Hex.decode("8004000000");byte[] responseGetPubKey = SendAPDU(apduGetPubKey);System.out.println("Card PubKey is");System.out.println(new String(Hex.encode(responseGetPubKey)));byte[] carECDHKey = CarGenECDHKey();byte[] carCipher = CarGetCipherUsingCardPubKey(challenge, responseGetPubKey);System.out.println(" Car Compute Cipher is");System.out.println(new String(Hex.encode(carCipher)));String strCarECDHKey = new String(Hex.encode(carECDHKey));String strApduAuth = "8011000051" + strCarECDHKey + strChallenge;byte[] apduAuth = Hex.decode(strApduAuth);byte[] responseAuth = SendAPDU(apduAuth);byte[] responseData = new byte[16];System.arraycopy(responseAuth, 0, responseData, 0, 16);System.out.println(" Card Response is");System.out.println(new String(Hex.encode(responseData)));return Arrays.equals(carCipher, responseData);}public static void main(String[] args) throws NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException, InvalidKeyException, InvalidKeySpecException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, CardException {System.out.println("Hello Paul!");TerminalManager.fixPlatformPaths();ConnectCard();CarECCInit();while (true) {if (CarAuthCard()) {System.out.println(" Now you can open the Car");} else {System.out.println(" Auth Failed");break;}}}
}

四、程序实际运行的特斯拉车端与卡端交互数据

challenge is
a9e1dfc8f594a6ae80e693021bef5fcc
A>> T=1 (4+0010) 00A40400 0A 7465736C614C6F676963 01
A<< (0000+2) (17ms) 9000
A>> T=1 (4+0000) 80040000 00
A<< (0065+2) (16ms) 043CE0B8EB5A2195100A4AE7D716DC57530F4F6CCD51217CB116AD887A0E028F8233411EAD26EAFCEA9B038A7E801EBFCE7F5E0F9437A5D01BF5B18451FEA15D25 9000
Card PubKey is
043ce0b8eb5a2195100a4ae7d716dc57530f4f6ccd51217cb116ad887a0e028f8233411ead26eafcea9b038a7e801ebfce7f5e0f9437a5d01bf5b18451fea15d259000
Car Private Key is
22e5c21a0c84190521b0106ba418b89dabc1e537a1708591e8db6211ed2fbb8b
Shared secret is
ad935496b0d1d7484c27b47708eb13a20e0ca411602689f61adfe4b0cb3afe11
aKeyAgree digest is
9da293a7c4273899110c44d1f12fd1588e8f6a68
AES Key is
9da293a7c4273899110c44d1f12fd158
Car Compute Cipher is
6f21d8e62406aa92608fecb25e1aefc3
A>> T=1 (4+0081) 80110000 51 04F99FB6870833B4AF5D19E23A48D6C091A65D94C82D4B524D5B9AACF4F0C9D9B08930151421A4E8F00BAECB417707F6AB63F44B740787E203AEB7070AAA57761FA9E1DFC8F594A6AE80E693021BEF5FCC
A<< (0016+2) (45ms) 6F21D8E62406AA92608FECB25E1AEFC3 9000
Card Response is
6f21d8e62406aa92608fecb25e1aefc3
Now you can open the Car

五、Python版本的模拟车端参考代码

要安装pyscard和mbedtls包才能运行

from smartcard.Exceptions import NoCardException
from smartcard.pcsc.PCSCReader import PCSCReader
from smartcard.util import *
from mbedtls import hashlib
from mbedtls import cipher
from mbedtls import pk
import binasciireader_list = PCSCReader.readers()
for reader in reader_list:try:print(reader.name)connection = reader.createConnection()connection.connect()print(toHexString(connection.getATR()))apdu_select = toBytes("00a404000a7465736c614c6f676963")data, sw1, sw2 = connection.transmit(apdu_select)print("{:02X} {:02X}".format(sw1, sw2))while True:apduGetKey = toBytes("80 04 00 00 00")data, sw1, sw2 = connection.transmit(apduGetKey)cardPubkey = toHexString(data, PACK)print("{:02X} {:02X}".format(sw1, sw2))print(cardPubkey)ecdh_key = pk.ECC(pk.Curve.SECP256R1)ecdh_key.generate()ecdh_srv = pk.ECDHServer(ecdh_key)ske = ecdh_srv.generate()carECDH = ske.hex()[8:]cke = "41" + cardPubkeyecdh_srv.import_CKE(binascii.unhexlify(cke.encode(encoding='utf_8')))secretA = ecdh_srv.generate_secret()shareKey = secretA.to_bytes(32, byteorder="big")hashEngine = hashlib.sha1()hashEngine.update(shareKey)digest = hashEngine.digest()aesKey = digest[:-4]aesEngine = cipher.AES.new(aesKey, cipher.MODE_ECB, b"ECB needs an IV.")strRand = "adfd9e9d09a7575968dc59a071363779"print("the Challenge is " + strRand)plain = binascii.unhexlify(strRand.encode(encoding='utf_8'))outBuf = aesEngine.encrypt(plain)strOut = binascii.hexlify(outBuf).decode()print("Server Compute Cipher is " + strOut.upper())apduAuth = toBytes("80 11 00 00 51 " + carECDH + strRand)data, sw1, sw2 = connection.transmit(apduAuth)response = toHexString(data, PACK)print("Card Compute Response is " + response)print("{:02X} {:02X}".format(sw1, sw2))if strOut.upper() == response:print("you can open the car!\n")except NoCardException:print('no card in reader')

六、特斯拉手机车钥匙未来展望

谷歌大力推广的Strongbox的愿景是手机做车钥匙,数字货币,身份证,Strongbox确实设计前沿,颠覆了安全芯片通常的思想,一直以来密钥一定要安全芯片存储而且不出安全芯片,这样很多应用都会被SE应用空间所局限,Strongbox开创性的将密钥加密出安全芯片,然后需要用再进来,由安全芯片内部对称密钥保障导出密钥只能回安全芯片使用,这样一下子就把安全芯片的空间无限扩大,而且不损害安全性,而且避免TSM的应用下载问题,避免应用兼容问题,安全芯片就提供安全功能,其他TEE或者REE实现,这个思想确实超前和实用,期待Strongbox未来大放异彩,回头来聊Strongbox如何实现特斯拉数字车钥匙呢,只要手机支持NFC,支持Strongbox,我们可以先生成公私钥对,然后能启用HCE模式的APK,用户点击模拟卡,将密钥Blob导入Strongbox,HCE接收的NFC公钥命令和Select命令正常会,关键认证命令过来,按照Strongbox实现ECDH密钥协商,然后SHA1,然后AES,全部调用Strongbox实现,然后NFC返回信息,这样不用下载特斯拉应用就可以实现特斯拉车卡,而且可以装海量特斯拉卡,等回头有空了,分享APK源代码。

总结

感谢国外大佬的
https://github.com/darconeous/gauss-key-card/blob/master/src/io/github/darconeous/gausskeycard/GaussKeyCard.java的分享
感谢国内NFC中继攻击提供的佐证数据
https://www.anquanke.com/post/id/213885
国内中继帖子标出的车公钥是错误的,这是ECDH挑战,每次都变的。
笔者购买的是正版199元的京东特斯拉卡,Log数据是实测数据,实际数据证明认证逻辑符合文章分析。
笔者同时用NXP的P71卡片测试Applet的AuthAPDU实际性能45ms,用正品卡实际测试性能是113ms,加上TagInfo信息推断,特斯拉卡片采用的应该是NXP的P6系列产品,以前大家谈性能总提PBOC200ms,其实PBOC被微信支付宝打的无还手之力,以后大家讨论性能就比拼TeslaAuth性能,这个指令包含了非对称,摘要,对称,衡量算法的黄金APDU指令,如果有一天大家比拼密码性能用TeslaAuth这个指标,这个帖子就是发源地。

特斯拉数字车钥匙卡评价相关推荐

  1. 未来鸿蒙能搭载oppo手机吗,【喂你播】华为下周发布鸿蒙手机系统;OPPO联合蔚来完成CCC2.0标准数字车钥匙开发...

    BGM:TIT FOR TAT-MYTH & ROID 华为下周发布鸿蒙手机系统 华为对外公布,确认将于6月2日正式发布鸿蒙手机系统.目前,华为已将 EMUI 官方微博账号正式更名为 @Har ...

  2. android手机开发安卓版本,OPPO首个完成CCC 2.0数字车钥匙开发的安卓厂商

    中关村在线消息:近日,OPPO正式宣布,已经与汽车品牌蔚来携手完成了基于CCC 2.0版本的数字车钥匙联合开发与测试工作.据了解,未来OPPO手机和手表内置的钱包APP内将会上线相关车钥匙功能. 通过 ...

  3. LG Innotek开发最高位置识别准确度“数字车钥匙模块”

    LG Innotek(代表郑哲东)宣布,已成功开发出提高了位置识别准确度与安全性的"数字车钥匙 (Digital Car Key) 模块". LG Innotek 开发的" ...

  4. 特斯拉被曝低级漏洞:用树莓派DIY车钥匙,开锁仅需90秒

    贾浩楠 木易 发自 凹非寺  量子位 报道 | 公众号 QbitAI 一辆售价80-90万的特斯拉Model X,只用2000块就能开走??? 这不是特斯拉在搞什么购车金融方案,而是比利时鲁汶大学的研 ...

  5. 用树莓派DIY车钥匙,开锁仅需90秒

    贾浩楠 木易 发自 凹非寺  量子位 报道 | 公众号 QbitAI 一辆售价80-90万的特斯拉Model X,只用2000块就能开走??? 这不是特斯拉在搞什么购车金融方案,而是比利时鲁汶大学的研 ...

  6. 中国智能电动车“登陆”韩国;特斯拉车主将车钥匙芯片植入手臂;日本研究用卫星“太空放牛” | 每日大事件...

    数据智能产业创新服务媒体 --聚焦数智 · 改变商业 01.中国智能电动车"登陆"韩国 据<亚洲日报>8 月 21 日报道,韩国贸易协会近日公布的进出口数据显示,今年上 ...

  7. 为什么特斯拉车钥匙电池,使用不到1年就需更换一次,而蒙迪欧致胜2.0T时尚版车钥匙电池可以使用10年。前期文章对特斯拉车钥匙功耗情况做了专题评测,用低功耗分析仪再来看看这款福特车钥匙的低功耗控制情况。

    为什么特斯拉车钥匙电池,使用不到1年就需更换一次,我们从前期文章功耗分析结果可以看到,由于车钥匙和车处于热连接态的时候,待机电流差不多0.5mA,平时待机电流也有4微安左右.所以,时刻都处于耗电状态. ...

  8. 特斯拉车钥匙低功耗评测过程中,发现一个奇怪问题,路过的大神帮忙看看!

    首先来说说测试过程和数据: #特斯拉车钥匙功耗测评#特斯拉车钥匙功能较多,除了常用功能外,还有自动感应,召唤等功能,所以比较耗电.想知道钥匙大概多长时间会换一次电池吗? 我对 特斯拉 model S ...

  9. 苹果钥匙试玩_苹果和宝马如何改变您的车钥匙

    苹果钥匙试玩 The coming of a standardized authentication protocol for phones and cars, making various cars ...

最新文章

  1. MyBatis动态SQL之 set 和 trim标记的使用示例
  2. mysql+keepalived搭建高可用环境
  3. 动手---sbt(2)
  4. androidfiletransfer_mac手机助手(Android File Transfer)下载_mac手机助手(Android File Transfer)官方下载...
  5. android 8.0 行为变更--day03
  6. 桃李春风一杯酒,江湖夜雨十年灯
  7. iOS 证书, provision profile作用
  8. 域策略(4)——设置统一锁屏壁纸(此策略仅适用于企业版、教育版和 Server SKU版)
  9. 通俗易懂的讲解 网关是什么
  10. 蓝牙Beacon广播数据包格式以及解析
  11. 使用MOno Cecil 的相关开源项目
  12. flutter拨打电话url_launcher
  13. 2022年Work-Life Balance能实现吗?
  14. Deepin Linux系统安装及显示器配置教程(可竖屏)
  15. centos9 intel集显直通方法
  16. 欧拉角和四元数之间转换公式推导
  17. 从零开始手写VIO第三章作业(含关键点细节及思维过程)
  18. 遭遇“windows已经阻止此软件因为无法验证发行者
  19. 冯巩的155句经典语
  20. 2017淮北计算机会考,2017年安徽淮北高中会考报名网站:淮北教育局

热门文章

  1. uniapp height高度 100% 无效的问题
  2. go语言-空结构体/ chan struct{}
  3. Nuit du hack 2017 webcrypto Writeup
  4. windows10系统下mfc100u.dll加载失败的解决方法
  5. android相册管理系统下载安装,云象相册管理
  6. ie开始屏蔽旧的java插件_IE将开始屏蔽旧版ActiveX控件
  7. [Linux Audio Driver] SM6350平台音频bring up ( 一 )
  8. pku 1013 解题报告
  9. 如何用Qt绘制一颗好看的二叉树
  10. 瑞士轮赛制模拟器_Major瑞士轮赛制ELO系统解析 公认的权威规则