HDMI或者DP接口中HDCP2.x流程分析
一、read version
i2c_read(0x37, 0x50, 1)
通过i2c读取1字节数据,注意dev_addr=0x37和offset_addr=0x50
value = 0x4,表示支持HDCP2.2或者更高版本
二、AKE(Authentication and Key Exchange)
用私钥加密信息的过程,我们称之为:数字签名 (常见数字签名算法:RSA/DSA/ECDSA)
用私钥加密的信息,必须用对应的公钥才能解开,这个过程称之为:验证签名(数字验签)
权威机构:CA(Certificate Authority)证书授权中心,在HDCP协议里面是DCP LLC

所有的HDCP Tx都会有一个DCP LLC发布的3072-bit RSA公钥,用于对HDCP Rx数字签名进行解密(公钥解密),即:
1、HDCP Rx使用DCP LLC的3072-bit RSA私钥,对HDCP Rx公钥进行加密,生成数字签名
2、HDCP Tx使用DCP LLC的3072-bit RSA公钥,对数字签名进行解密,得到HDCP Rx公钥
所有的HDCP Rx都会有一个1024-bit的公钥和私钥,公钥通过DCP LLC发布的私钥进行加密生成数字签名,然后数字签名+公钥保存到HDCP Rx的公钥证书(Public Key Certificate)里面
因此:
当HDCP Tx读取到公钥证书后,可以进行如下操作来确认HDCP Rx的公钥是否可信
1、使用HDCP Tx的3072-bit RSA公钥,对公钥证书里面的数字签名进行解密(数字验签),得到HDCP Rx公钥
2、将解密后得到的HDCP Rx公钥,与公钥证书里面的HDCP Rx公钥进行比对,若比对一致则验证通过,就可以使用HDCP Rx进行下一步的操作
当然我们也可以直接相信这就是HDCP Rx公钥,直接从公钥证书里面获取HDCP Rx公钥后进行使用即可,跳过数字验签的过程
4176/8=522Byte HDCP Rx公钥证书大小为522Byte
注意:
公钥证书里面的4176-bit是以大端模式存储的

所以我们驱动程序里面读取回来的数据,不需要转换就可以直接使用
2.1 AKE_Init

说明:
rtx是自己定义的,任意8个字节数据均可
TxCaps是按照spce里面的定义实现的
然后通过i2c将这13个字节发送出去即可,注意offset_addr为0x60
发送AKE_Init msg后,100ms之内必须发送AKE_Send_Cert msg,用于获取Rx公钥证书,具体来讲是TxCaps这三个字节写入后的100ms内
注意:
在发送AKE_Init msg之前,需要先判断Rx是否准备好数据
代码实现:
如果从0x70读取回来的数据为0(rx_status=0),那就说明Rx端没有准备好数据,也就不需要进行下一步读取操作了,即使读取了那么读取回来的值也全是0
Rx端最大可以准备1023个字节供HDCP Tx读取
然后通过i2c从0x80读取指定字节的数据即可
2.2 AKE_Send_Cert

1+522+8+3=534,AKE_Send_Cert是一个i2c_read操作,正常会从0x80处读取534Byte数据回来
代码实现:

HDCP Rx公钥证书以大端模式存储,我们读取回来的数据rx_cert可以直接使用,共522Byte
Receiver ID: 0~4 5Byte
Receiver Public Key: 5~135 131Byte
Reserved: 136~137 2Byte
DCP LLC Signature: 138~521 384Byte
有了kpub和sign,正常情况下我们要做的是:使用从DCP LLC购买的3072-bit公钥对签名进行解密,然后与kpub进行比对,比对通过则认为kpub可信,然后进行下一步操作
2.3 AKE_No_Stored_km
代码实现:
正常做法:
km是16个字节,我们可以任意定义,然后使用kpub通过RSA加密算法(纯软件实现),对km进行加密生成Ekpub_km(大小为128个字节)
HDCP Rx收到Ekpub_km后,会使用私钥对其进行解密,从而得到HDCP Tx的km
发送AKE_No_Stored_km msg后,1s之内必须发送AKE_Send_H_prime msg,用于获取Rx端的H值,具体来讲是Ekpub_km的最后一个字节写入后的1s内
注意:
在发送AKE_Send_H_prime msg之前,需要先判断Rx是否准备好数据
代码实现:
2.4 AKE_Stored_km
我们默认接入的显示器,都是第一次连接,不走AKE_Stored_km流程,即代码里面可以选择不使用该方式,后面内容也会讲解AKE_Stored_km流程
2.5 AKE_Send_H_prime
代码实现:
这里关注一下H值是怎么产生的:
派生机制:
基于km(主秘钥)和随机数rtx、rrx,通过AES加密算法(纯软件实现)生成动态秘钥dkeyi

在AKE阶段,也就是产生dkey0和dkey1的期间,rn被初始化为0并且整个阶段都为0
在LC阶段,rn被初始化为一个16-bytes的随机值,然后与Km的低8-bytes数据进行异或操作后送入AES-CTR后产生dkey2,所以:
rn的高8-bytes数据相当于没有什么作用,在代码中就可以将rn高8-bytes数据全部定义为0,然后将rn低8-bytes数据定义为不为0的任意值
Kd = dkey0 || dkey1
H = HMAC-SHA256(rtx || RxCaps || TxCaps, kd)
变量 km rn kd dkey0 dkey1 H
大小 16Byte 16Byte 32Byte 16Byte 16Byte 32Byte
代码实现:
我们从Rx端读取rx_H值回来后,Tx端也需要计算得到一个tx_H值,二者进行比较,若值相等则认证通过继续进行下一步,否则认证失败直接退出
看一下Tx端如何计算tx_H值:
再来进行tx_H与rx_H的比较:
发送AKE_Send_H_prime msg后,200ms之内必须发送AKE_Send_Pairing_Info msg,用于获取Rx端的H值,具体来讲是rx_H值最后一个字节读取后的200ms内
注意:
在发送AKE_Send_Pairing_Info msg之前,需要先判断Rx是否准备好数据
代码实现:
2.6 AKE_Send_Pairing_Info

代码实现:
我们来看一下spec关于这个部分是怎么说的:
为了加快AKE的过程,在HDCP Tx和Rx之间必须执行pair,具体的过程如下:
1、HDCP Rx生成一个Ekh(km),发送给HDCP Tx
2、HDCP Tx收到Ekh(km)后,与m值、km值和rx_id值保存在一起,这样当同一款显示器再次插入时,我们通过AKE_Send_Cert获取到rx_id后与本地保存的pair_info数据进行比较,找到后就可以直接走AKE_Strored_km流程,从而节省时间
3、HDCP Rx收到Ekh(km)后就可以解密得到km


注意1:Ekh(km)是如何产生的

1.HDCP Rx会通过SHA-256算法,使用私钥对[127:0]的数据进行加密得到128-bit kh
这个[127:0]的数据是什么?我们不关心,这个是HDCP Rx特有的,它愿意用什么数据就用什么数据
2.kh与m值送入AES后输出128-bit数据,其与km进行异或操作,从而得到Ekh(km)
3.在AKE_Stored_km过程中,HDCP Rx收到Ekh(km)后会使用私钥进行解密(这个过程应该会使用到步骤1提到的[127:0]的数据)得到km,所以说[127:0]的数据自始至终都是HDCP Rx自己使用,HDCP Tx根本不会用到,所以HDCP Tx不必关心它具体是什么数据
综上:Ekh(km)我们可以称之为使用kh对km进行加密后产生的值
注意2:
我们可以不使用AKE_Stored_km流程,即每一次都使用AKE_No_Stored_km流程,即使说某一个显示器已经接入过一次,那么下一次接入的时候我依然走AKE_No_Stored_km流程,这里就有一个问题:
AKE_Send_Pairing_Info流程可以不执行嘛?
必须执行,走AKE_No_Stored_km流程就必须同时走AKE_Send_Pairing_Info流程,这是spec规定的,不这样做可能会出问题,但是:
通过AKE_Send_Pairing_Info流程获取到的Ekh(km)值我们可以置之不理,就当没有这个东西就好了,也不需要与m值、km值和rx_id值保存在一起
三、Locality Check
Locality Check分为两个阶段:
3.1 LC_Init
用于将8-bytes大小的随机值rn发送给HDCP Rx,rn值软件代码中定义不是16-bytes嘛?怎么这里就发送8-bytes大小呢?
代码实现:
通过代码我们可以看到:发送的不仅仅是8-bytes大小的rn数据,而且还是rn的低8-bytes数据,为什么要这样做呢?在SKE_Send_Eks阶段我们会进行详细说明
在发送LC_Init msg后,20ms之内必须发送LC_Send_L_Prime msg,用于获取Rx端的L值,具体来讲是rn值最后一个字节写入后的20ms内
注意:
在发送LC_Send_L_Prime msg之前,需要先判断Rx是否准备好数据
代码实现:
3.2 LC_Send_L_prime
从HDCP Rx读取回来的rx_L值与我们计算出来的tx_L值进行比较,这里我们关注一下tx_L值的计算过程,以及rx_L值与tx_L值的比较过程:
若rx_L值与tx_L值比较通过,则进入下一阶段
若比较失败则可以再次发起LC_Init和LC_Send_L_prime过程,最大可以循环执行1023次,需要注意的是再次发起LC_Init的时候需要使用新的8-bytes rn值,也就是说8-bytes rn值不能与上一次一样
代码实现:
获取了rx_L值后,就需要与tx_L进行比对,判断是否认证通过
我们看一下Tx端是如何进行tx_L值的计算:
我们可以发现L_Prime值的计算时,用到的也是rn低8-bytes数据
再来进行tx_L与rx_L的比较:
四、SKE(Session Key Exchange)
ks:会话秘钥
riv:随机初始化向量

注意:
在完成SKE_Send_Eks后,至少需要200ms时间后HDCP Tx才能发送加密数据,所以我们驱动代码中需要进行延时处理
代码实现:
1、Eks的产生过程
综上就是:
eks高8-bytes数据 = (dkey2高8-bytes数据 异或 ks高8-bytes数据)
eks低8-bytes数据 = (dkey2低8-bytes数据 异或 ks低8-bytes数据 异或 rrx的8-bytes数据)
我们再来回顾一下dkey2的产生过程:
在spec中规定LC阶段,rn被初始化为一个16-bytes的随机值,然后与Km的低8-bytes数据进行异或操作后送入AES-CTR后产生dkey2,所以:
rn的高8-bytes数据相当于没有什么作用,在代码中就可以将rn高8-bytes数据全部定义为0,然后将rn低8-bytes数据定义为不为0的任意值
也就是说dkey2的高8-bytes数据与rn高8-bytes数据没有什么关系,那么eks的高8-bytes数据也就与rn高8-bytes数据没有什么关系,那么HDCP Rx在解密ks时就用不到rn高8-bytes数据,再加上L_Prime值的计算用的也是rn低8-bytes数据,所以:
在LC阶段只需要发送rn的低8-bytes数据即可
2、SKE_Send_Eks
至此HDCP2.x认证过程已全部完成,该过程是纯软件行为,没有任何HDCP Tx硬件参与,接下来就是HDCP Tx发送加密数据了
看一下HDCP Tx是如何使能HDCP硬件的:
所有的HDCP设备,不论是Tx还是Rx,都共享秘密常数lc128,
秘密常数由DCP LLC提供,用于生成最终加密秘钥k_final,k_final = ks ^ lc128,目的是为了防止ks直接泄露导致内容被解密


最终的数据加密方式如上图所示,重点关注一下inputCtr
inputCtr = FrameNumber || DataNumber
FrameNumbe:帧号,顾名思义每发送一帧数据后FrameNumber +1
DataNumber: 数据号,与key stream相关,每生成一次key stream则DataNumber +1
(ks^lc128, riv, inputCtr)经过AES-CTR后产生128-bits key stream,其会与每5个24-bit像素数据进行异或操作,这就是数据加密操作,然后发送给HDCP Rx
代码实现:

最后我们再来说明一下HDCP2.x中用到的伪随机值(pseudo-random number generator)

在HDCP2.x认证过程中用到的8-bytes rrx/rtx/riv/rn,都是高质量伪随机数,正常流程应该是使用硬件PRNG(符合NIST SP 800-90标准)生成,但是实际测试过程中我们驱动代码是自己定义的随机数
最后我们也来说明一下SRM(system renewability message-系统可更新性消息):

也就是说如果HDCP Tx要支持SRM功能,就需要将DCP LLC发布的加密文件存储并且进行解析(要求以binary格式存储),里面包含了被revoke(撤销)掉的Device IDs,所以:
当在认证过程中获取到HDCP Rx公钥证书后,与Receiver IDs进行比对,若比对通过则终止认证过程,实际上我们很少会使用SRM机制
更多推荐


所有评论(0)