两种RTMP的握手协议
RTMP握手协议
根据RTMP的文档,RTMP的握手过程为:
1 2 3 4 |
|client| |server| | -----(C0, C1)----> | | <----(S0, S1)----- | | <----( S2 )----- | | -----( C2 )----> | |
下面是握手的数据格式:
1 2 3 4 5 6 7 |
C0:(协议版本)0x03; C1:4B(时间1) + 4B(全零) + 1528B(随机数1)。 ------------------------------------------> S0:(协议版本)0x03; S1:4B(时间2) + 4B(全零) + 1528B(随机数2)。 S2:4B(时间1) + 4B(时间3) + 1528B(随机数1)。 |
其中:
时间1:C发送C1时,C上的时间
时间2:S发送S1时,S上的时间
时间3:S接到C1时,S上的时间
时间4:C接到S1时,S上的时间
(时间2和时间3可能相等)
所以上面的过程写成代码应该是:
(客户端代码)client.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
int send_c0c1() { char c0[1537],*c1; int32_t epoch1; c1 = c0 + 1; epoch = time(0); /*NOTE: must be 32-bit time*/ memcpy(c1,&epoch1,4); /*NOTE: must be network endian*/ memset(c1+4,0,4); /*zero*/ fill_random(c1+8,1528); return send(c0,1537); } int send_c2() { char s0[1537],*s1,c2[1536],*s2; int32_t epoch4; recv(s0,1537); /*read s0,s1*/ s1 = s0 + 1; epoch4 = time(0); memcpy(c2,s1,4); /*copy epoch2*/ memcpy(c2+4,&epoch4,4); memcpy(c2+8,s1+8,1528); send(c2,1528); /*send c2*/ /*discard c2,get s2*/ s2 = c2; recv(s2,1536); /*handshake done !!!*/ } |
(服务器代码)server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
int send_s0s1s2() { char c0[1537],*c1,*c2,s0[1537],*s1,*s2; int32_t epoch2,epoch3; /*recv c0c1*/ recv(c0,1537); c1 = c0 + 1; s1 = s0 + 1; epoch3 = epoch2 = time(0); *s0 = 0x03; memcpy(s1,&epoch2,4); /*NOTE: must be network endian*/ memset(s1+4,0,4); /*zero*/ fill_random(s1+8,1528); /*send s0s1*/ send(s0,1537); /*send s2*/ s2 = s1; mempcy(s2,c1,4); mempcy(s2+4,&epoch3,4); mempcy(s2+8,c1+8,1528); send(s2,1536); /*recv c2*/ c2 = c1; recv(c2,1536); /*handshake done !!!*/ } |
上面的代码是RTMP协议明文的握手协议,但是adobe公司又将上面的协议给扩展成加密的方式,但是握手的顺序没有变,改变仅仅在随机字段的意义。
我们知道上面的C1与S1都存在一个零字段,在修改后的加密协议中这个字段不为零。所以根据该字段的值,服务器可以了解客户端握手协议的版本,以确定继续握手还是中断连接。
握手的明文格式:
1 2 3 4 5 6 7 |
C0:(协议版本)0x03; C1:4B(时间1) + 4B(版本1) + 1528B(密文1)。 ------------------------------------------> S0:(协议版本)0x03; S1:4B(时间2) + 4B(版本2) + 1528B(密文2)。 S2:4B(时间1) + 4B(时间3) + 1528B(密文3)。 |
其中
版本1:非零。
版本2:非零。
密文1:由C1的明文生成。
密文2:由S1的明文生成。
密文3:根据密文1生成。
密文4:根据密文2生成。
下面是C1的明文格式:
C1:4B(时间1) + 4B(版本1) + 764B(随机数1) + 764B(X1)。
X1:4B(偏移量) + xB(随机数1) + 32B(密文1) + (728-x)B(随机数2) (0 <= x < 728)
下面我来划一幅图
1 2 3 4 |
0 776 776+x 808+x 1536 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | 4 | 764 | 4 | x | 32 | 728-x | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
密文1将整个C1/S1划分成了三个部分[0,776+x),[776+x,808+x),[808+x,1536)。这也是整个加密的关键所在。
特殊情形,当x=728时,整个C1/S1分成了两部分:[0,1054),[1054,1536)
加密步骤:
1.构造C1(填充版本)
与明文握手相比,多了一个版本填充,对于C1填充(0x0C, 0x00, 0x0D, 0x0E),对于S1填充(0x0D, 0x0E, 0x0A, 0x0D)。
2.计算x的值。
根据X1的最前面计算x值。计算方法:
/*x: [0 ,728),这里限制x不能取728*/
1 |
int get_x(char *X1) { return (X1[0] + X1[1] + X1[2] + X1[3]) % 728; } |
3.计算并填充密文。
密文长为32,
对于C1来说密钥为”Genuine Adobe Flash Player 001″(30B),
对于S1来说密钥为”Genuine Adobe Flash Media Server 001″(36B).
整个加密函数encrypt_c1s1(char *c1s1,char *key);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
int encrypt_c1s1(char *c1s1,char *key) { static HMAC_CTX hmac; static int init = 0; uint32_t dlen; char *X1; int x; X1 = 776 + c1s1; x = get_x(X1); /*static init encrypt*/ if (init == 0) { HMAC_CTX_init(&hmac); init = 1; } HMAC_Init_ex(&hmac,key,strlen(key),EVP_sha256(), NULL); /*[0,776+x)*/ HMAC_Update(&hmac,c1,776+x); /*[808+x,1536)*/ HMAC_Update(&hmac,c1+808+x,728-x); /*set digest*/ HMAC_Final(&hmac,X1+x,&dlen); return 0; } |
C1整个加密过程可以写成下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
static const client_version[4] = { 0x0C, 0x00, 0x0D, 0x0E } static const char server_version[4] = { 0x0D, 0x0E, 0x0A, 0x0D }; static char c1_public_key[] = "Genuine Adobe Flash Player 001"; static char s1_public_key[] = "Genuine Adobe Flash Media Server 001"; static char s2_public_key[] = { 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ', 'S', 'e', 'r', 'v', 'e', 'r', ' ', '0', '0', '1', 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE }; static char c2_public_key[] = { 'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ', 'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ', '0', '0', '1', 0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1, 0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB, 0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE }; int send_c0c1() { char c0[1537],*c1,*X1,*digest; int32_t epoch1; int x; *c0 = 0x03; c1 = c0 + 1; epoch = time(0); /*NOTE: must be 32-bit time*/ memcpy(c1,&epoch1,4); /*NOTE: must be network endian*/ memcpy(c1+4,client_version,4); /*fill version*/ fill_random(c1+8,1528); encrypt_c1s1(c1,c1_public_key); send(c0,1537); } |
客户端将C0C1发送出去后,将接收服务器发回来的S0S1S2,这时候客户端要对S0S1进行检查,然后根据S2来发送C2。
校验S1的方法很简单,只需要将S1的digest提取来,然后重新对S1进行加密得到密文后与接收到的密文对比,如果一致校验通过,否则关闭连接。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
static int verify_c1s1(char *c1s1,char *public_key,char *digest) { char *X1; int x; /*verify c1s1*/ X1 = 776 + c1s1; x = get_x(X1); /*save c1s1'digest */ memcpy(digest,X1+x,32); /* re-calculate c1s1'digest */ encrypt_c1s1(c1s1,public_key); /*compare*/ if (memcmp(digest,X1+x,32) == 0) { return 1; /*true*/ } return 0; /*false*/ } |
C2/S2主要是对S1/C1的验证,C2/S2的包跟之前不样的地方在于,它的格式在于C1/S1的特例也就是x=728时的C1/S1格式。(严格来讲x是无意义的。)
C2/S2格式:
1 2 3 4 |
0 1504 1536 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | 4 | 4 | 764 | 732 | 32 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
产生S2/C2的难点在于对S2/C2的key的选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
int encrypt_c2s2(char *c2s2,char *key) { static HMAC_CTX hmac; static int init = 0; uint32_t dlen; /*static init encrypt*/ if (init == 0) { HMAC_CTX_init(&hmac); init = 1; } HMAC_Init_ex(&hmac,key,32,EVP_sha256(), NULL); /*[0,1504)*/ HMAC_Update(&hmac,c1,1504); /*set digest*/ HMAC_Final(&hmac,c1+1504,&dlen); return 0; } |
关键是c2/s2的密钥的生成。它是由s1/c1的digest二次加密而来,也就是这c2/s2的密钥长度为32。
从C1/S1得到digest后,用各自的public key进行加密后产生的digest做c2s2的key。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
int get_c2s2_key(char *digest,char *public_key,int klen,char *digest_key) { static HMAC_CTX hmac; static int init = 0; uint32_t dlen; /*static init encrypt*/ if (init == 0) { HMAC_CTX_init(&hmac); init = 1; } HMAC_Init_ex(&hmac,public_key,klen,EVP_sha256(), NULL); /*[0,1504)*/ HMAC_Update(&hmac,digest,32); /*set digest key*/ HMAC_Final(&hmac,digest_key,&dlen); return 0; } int send_c2() { char s0[1537],*s1,c2[1536],*s2,digest[32],digest_key[32]; int32_t epoch4,klen; recv(s0,1537); /*read s0,s1*/ s1 = s0 + 1; /*verify s1*/ if (verify_c1s1(s1,s1_public_key,digest) == 0) { return -1; } epoch4 = time(0); memcpy(c2,s1,4); /*copy epoch2*/ memcpy(c2+4,&epoch4,4); memcpy(c2+8,s1+8,1528); get_c2s2_key(digest,c2_public_key,sizeof(c2_public_key),digest_key); encrypt_c2s2(c2,digest_key); send(c2,1528); /*send c2*/ /*discard c2,get s2*/ s2 = c2; recv(s2,1536); /*handshake done !!!*/ } |
(服务器代码)server.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
int send_s0s1s2() { char c0[1537],*c1,*c2,s0[1537],*s1,*s2; int32_t epoch2,epoch3; /*recv c0c1*/ recv(c0,1537); c1 = c0 + 1; s1 = s0 + 1; epoch3 = epoch2 = time(0); *s0 = 0x03; memcpy(s1,&epoch2,4); /*NOTE: must be network endian*/ memcpy(s1+4,server_version,4); /*fill version*/ fill_random(s1+8,1528); /*send s0s1*/ send(s0,1537); /*verify c1*/ if (verify_c1s1(c1,c1_public_key,digest) == 0) { return -1; } /*send s2*/ s2 = s1; mempcy(s2,c1,4); mempcy(s2+4,&epoch3,4); mempcy(s2+8,c1+8,1528); get_c2s2_key(digest,s2_public_key,sizeof(s2_public_key),digest_key); encrypt_c2s2(s2,digest_key); send(s2,1536); /*recv c2*/ c2 = c1; recv(c2,1536); /*handshake done !!!*/ } |