rtmpdump源代码分析------HandShake

HandShake的流程图:

1:握手以客户端发送 C0 和 C1 块开始。

2:客户端必须等待接收到 S1 才能发送 C2。

3:客户端必须等待接收到 S2 才能发送任何其他数据。

4:服务器端必须等待接收到 C0 才能发送 S0 和 S1,也可以等待接收到 C1 再发送 S0 和 S1。服务器端必须等待接收到 C1 才能发送 S2。服务器端必须等待接收到 C2 才能发送任何其他数据。

C0 和 S0 的格式

C0 和 S0 包都是一个单一的八位字节,以一个单独的八位整型域进行处理:

以下是 C0/S0 包中的字段:

版本号 (八位):在 C0 中,这一字段指示出客户端要求的 RTMP 版本号。在 S0 中,这一字段指示出服务器端选择的 RTMP 版本号。本文档中规范的版本号为 3。0、1、2 三个值是由早期其他产品使用的,是废弃值;4 - 31 被保留为 RTMP 协议的未来实现版本使用;32 - 255 不允许使用 (以区分开 RTMP 和其他常以一个可打印字符开始的文本协议)。无法识别客户端所请求版本号的服务器应该以版本
3 响应,(收到响应的) 客户端可以选择降低到版本 3,或者放弃握手。

C1 和 S1 的格式

C1 和 S1 数据包的长度都是 1536 字节,包含以下字段:

Time (四个字节):这个字段包含一个 timestamp,用于本终端发送的所有后续块的时间起点。这个值可以是 0,或者一些任意值。要同步多个块流,终端可以发送其他块流当前的 timestamp 的值。

Zero (四个字节):这个字段必须都是 0。

Random data (1528 个字节):这个字段可以包含任意值。终端需要区分出响应来自它发起的握手还是对端发起的握手,这个数据应该发送一些足够随机的数。这个不需要对随机数进行加密保护,也不需要动态值。

C2 和 S2 的格式

C2 和 S2 数据包长度都是 1536 字节,基本就是 S1 和 C1 的副本 (分别),包含有以下字段:

Time (四个字节):这个字段必须包含终端在 S1 (给 C2) 或者 C1 (给 S2) 发的 timestamp。

Time2 (四个字节):这个字段必须包含终端先前发出数据包 (s1 或者 c1) timestamp。

Random echo (1528 个字节):这个字段必须包含终端发的 S1 (给 C2) 或者 S2 (给 C1) 的随机数。两端都可以一起使用 time 和 time2 字段再加当前 timestamp 以快速估算带宽和/或者连接延迟,但这不太可能是有多大用处。

握手示意图

如果觉得上面的流程相对来说比较难理解的话:

代码的实现:

static int HandShake(RTMP * r, int FP9HandShake)
{
	int i, offalg = 0;
	int dhposClient = 0;
	int digestPosClient = 0;
	int encrypted = r->Link.protocol & RTMP_FEATURE_ENC;

	RC4_handle keyIn = 0;
	RC4_handle keyOut = 0;

	int32_t *ip;
	uint32_t uptime;

	uint8_t clientbuf[RTMP_SIG_SIZE + 4], *clientsig = clientbuf + 4;
	uint8_t serversig[RTMP_SIG_SIZE], client2[RTMP_SIG_SIZE], *reply;
	uint8_t type;
	getoff *getdh = NULL, *getdig = NULL;

	if (encrypted || r->Link.SWFSize)
		FP9HandShake = TRUE;//加密的
	else
		FP9HandShake = FALSE;//普通的

	r->Link.rc4keyIn = r->Link.rc4keyOut = 0;

	if (encrypted)
	{
		clientsig[-1] = 0x06; /* 0x08 is RTMPE as well */
		offalg = 1;
	}
	else
		clientsig[-1] = 0x03;//普通rtmp的Version

	uptime = htonl(RTMP_GetTime());//获取的时间戳
	memcpy(clientsig, &uptime, 4);

	if (FP9HandShake)
	{
		/* set version to at least 9.0.115.0 */
		if (encrypted)
		{
			clientsig[4] = 128;
			clientsig[6] = 3;
		}
		else
		{
			clientsig[4] = 10;
			clientsig[6] = 45;
		}
		clientsig[5] = 0;
		clientsig[7] = 2;

		RTMP_Log(RTMP_LOGDEBUG, "%s: Client type: %02X", __FUNCTION__, clientsig[-1]);
		getdig = digoff[offalg];
		getdh = dhoff[offalg];
	}
	else
	{
		memset(&clientsig[4], 0, 4);//zero,占据4 byte
	}

	/* generate random data */
#ifdef _DEBUG
	memset(clientsig+8, 0, RTMP_SIG_SIZE-8);
#else
	ip = (int32_t *)(clientsig + 8);//从第八位开始,随机数据,进行填充clientsig
	for (i = 2; i < RTMP_SIG_SIZE / 4; i++)
		*ip++ = rand();
#endif

	/* set handshake digest */
	if (FP9HandShake)
	{
		if (encrypted)
		{
			/* generate Diffie-Hellmann parameters */
			r->Link.dh = DHInit(1024);
			if (!r->Link.dh)
			{
				RTMP_Log(RTMP_LOGERROR, "%s: Couldn't initialize Diffie-Hellmann!", __FUNCTION__);
				return FALSE;
			}

			dhposClient = getdh(clientsig, RTMP_SIG_SIZE);
			RTMP_Log(RTMP_LOGDEBUG, "%s: DH pubkey position: %d", __FUNCTION__, dhposClient);

			if (!DHGenerateKey(r->Link.dh))
			{
				RTMP_Log(RTMP_LOGERROR, "%s: Couldn't generate Diffie-Hellmann public key!", __FUNCTION__);
				return FALSE;
			}

			if (!DHGetPublicKey(r->Link.dh, &clientsig[dhposClient], 128))
			{
				RTMP_Log(RTMP_LOGERROR, "%s: Couldn't write public key!", __FUNCTION__);
				return FALSE;
			}
		}

		digestPosClient = getdig(clientsig, RTMP_SIG_SIZE); /* reuse this value in verification */
		RTMP_Log(RTMP_LOGDEBUG, "%s: Client digest offset: %d", __FUNCTION__, digestPosClient);

		CalculateDigest(digestPosClient, clientsig, GenuineFPKey, 30, &clientsig[digestPosClient]);

		RTMP_Log(RTMP_LOGDEBUG, "%s: Initial client digest: ", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, clientsig + digestPosClient,
		SHA256_DIGEST_LENGTH);
	}

#ifdef _DEBUG
	RTMP_Log(RTMP_LOGDEBUG, "Clientsig: ");
	RTMP_LogHex(RTMP_LOGDEBUG, clientsig, RTMP_SIG_SIZE);
#endif

	//sed to rtmp server,主要发送的是c0+c1,其中c0为version,c1为1536 byte,包裹时间戳以及zero及填充的信息
	if (!WriteN(r, (char *)clientsig - 1, RTMP_SIG_SIZE + 1))
		return FALSE;

	//recv version,一般是0x03 or 0x06
	if (ReadN(r, (char *)&type, 1) != 1) /* 0x03 or 0x06 */
		return FALSE;

	RTMP_Log(RTMP_LOGDEBUG, "%s: Type Answer   : %02X", __FUNCTION__, type);
	//比较版本是否一致,也就是发送的c0 给服务器,服务器寻找所支持的版本后,返回给client.
	if (type != clientsig[-1])
		RTMP_Log(RTMP_LOGWARNING, "%s: Type mismatch: client sent %d, server answered %d", __FUNCTION__, clientsig[-1], type);
	//获取S1的数据,这部分的数据,用于C2发送的数据.
	if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
		return FALSE;

	/* decode server response */
	memcpy(&uptime, serversig, 4);
	uptime = ntohl(uptime);//大小端转换

	RTMP_Log(RTMP_LOGDEBUG, "%s: Server Uptime : %d", __FUNCTION__, uptime);
	RTMP_Log(RTMP_LOGDEBUG, "%s: FMS Version   : %d.%d.%d.%d", __FUNCTION__, serversig[4], serversig[5], serversig[6], serversig[7]);

	if (FP9HandShake && type == 3 && !serversig[4])
		FP9HandShake = FALSE;

#ifdef _DEBUG
	RTMP_Log(RTMP_LOGDEBUG, "Server signature:");
	RTMP_LogHex(RTMP_LOGDEBUG, serversig, RTMP_SIG_SIZE);
#endif

	if (FP9HandShake)
	{
		uint8_t digestResp[SHA256_DIGEST_LENGTH];
		uint8_t *signatureResp = NULL;

		/* we have to use this signature now to find the correct algorithms for getting the digest and DH positions */
		int digestPosServer = getdig(serversig, RTMP_SIG_SIZE);

		if (!VerifyDigest(digestPosServer, serversig, GenuineFMSKey, 36))
		{
			RTMP_Log(RTMP_LOGWARNING, "Trying different position for server digest!");
			offalg ^= 1;
			getdig = digoff[offalg];
			getdh = dhoff[offalg];
			digestPosServer = getdig(serversig, RTMP_SIG_SIZE);

			if (!VerifyDigest(digestPosServer, serversig, GenuineFMSKey, 36))
			{
				RTMP_Log(RTMP_LOGERROR, "Couldn't verify the server digest"); /* continuing anyway will probably fail */
				return FALSE;
			}
		}

		/* generate SWFVerification token (SHA256 HMAC hash of decompressed SWF, key are the last 32 bytes of the server handshake) */
		if (r->Link.SWFSize)
		{
			const char swfVerify[] = {0x01, 0x01};
			char *vend = r->Link.SWFVerificationResponse + sizeof(r->Link.SWFVerificationResponse);

			memcpy(r->Link.SWFVerificationResponse, swfVerify, 2);
			AMF_EncodeInt32(&r->Link.SWFVerificationResponse[2], vend, r->Link.SWFSize);
			AMF_EncodeInt32(&r->Link.SWFVerificationResponse[6], vend, r->Link.SWFSize);
			HMACsha256(r->Link.SWFHash, SHA256_DIGEST_LENGTH, &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
			SHA256_DIGEST_LENGTH, (uint8_t *)&r->Link.SWFVerificationResponse[10]);
		}

		/* do Diffie-Hellmann Key exchange for encrypted RTMP */
		if (encrypted)
		{
			/* compute secret key */
			uint8_t secretKey[128] = {0};
			int len, dhposServer;

			dhposServer = getdh(serversig, RTMP_SIG_SIZE);
			RTMP_Log(RTMP_LOGDEBUG, "%s: Server DH public key offset: %d", __FUNCTION__, dhposServer);
			len = DHComputeSharedSecretKey(r->Link.dh, &serversig[dhposServer], 128, secretKey);
			if (len < 0)
			{
				RTMP_Log(RTMP_LOGDEBUG, "%s: Wrong secret key position!", __FUNCTION__);
				return FALSE;
			}

			RTMP_Log(RTMP_LOGDEBUG, "%s: Secret key: ", __FUNCTION__);
			RTMP_LogHex(RTMP_LOGDEBUG, secretKey, 128);

			InitRC4Encryption(secretKey, (uint8_t *)&serversig[dhposServer], (uint8_t *)&clientsig[dhposClient], &keyIn, &keyOut);
		}

		reply = client2;
#ifdef _DEBUG
		memset(reply, 0xff, RTMP_SIG_SIZE);
#else
		ip = (int32_t *)reply;
		for (i = 0; i < RTMP_SIG_SIZE / 4; i++)
			*ip++ = rand();
#endif
		/* calculate response now */
		signatureResp = reply + RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH;

		HMACsha256(&serversig[digestPosServer], SHA256_DIGEST_LENGTH, GenuineFPKey, sizeof(GenuineFPKey), digestResp);
		HMACsha256(reply, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digestResp,
		SHA256_DIGEST_LENGTH, signatureResp);

		/* some info output */
		RTMP_Log(RTMP_LOGDEBUG, "%s: Calculated digest key from secure key and server digest: ", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, digestResp, SHA256_DIGEST_LENGTH);

#ifdef FP10
		if (type == 8)
		{
			uint8_t *dptr = digestResp;
			uint8_t *sig = signatureResp;
			/* encrypt signatureResp */
			for (i = 0; i < SHA256_DIGEST_LENGTH; i += 8)
				rtmpe8_sig(sig + i, sig + i, dptr[i] % 15);
		}
#if 0
		else if (type == 9))
		{
			uint8_t *dptr = digestResp;
			uint8_t *sig = signatureResp;
			/* encrypt signatureResp */
			for (i=0; i<SHA256_DIGEST_LENGTH; i+=8)
			rtmpe9_sig(sig+i, sig+i, dptr[i] % 15);
		}
#endif
#endif
		RTMP_Log(RTMP_LOGDEBUG, "%s: Client signature calculated:", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, signatureResp, SHA256_DIGEST_LENGTH);
	}
	else
	{
		reply = serversig;
#if 0
		uptime = htonl(RTMP_GetTime());
		memcpy(reply+4, &uptime, 4);
#endif
	}

#ifdef _DEBUG
	RTMP_Log(RTMP_LOGDEBUG, "%s: Sending handshake response: ",
			__FUNCTION__);
	RTMP_LogHex(RTMP_LOGDEBUG, reply, RTMP_SIG_SIZE);
#endif
	//send C2到服务器
	if (!WriteN(r, (char *)reply, RTMP_SIG_SIZE))
		return FALSE;

	/* 2nd part of handshake */
	//read到s2的内容
	if (ReadN(r, (char *)serversig, RTMP_SIG_SIZE) != RTMP_SIG_SIZE)
		return FALSE;

#ifdef _DEBUG
	RTMP_Log(RTMP_LOGDEBUG, "%s: 2nd handshake: ", __FUNCTION__);
	RTMP_LogHex(RTMP_LOGDEBUG, serversig, RTMP_SIG_SIZE);
#endif

	if (FP9HandShake)
	{
		uint8_t signature[SHA256_DIGEST_LENGTH];
		uint8_t digest[SHA256_DIGEST_LENGTH];

		if (serversig[4] == 0 && serversig[5] == 0 && serversig[6] == 0 && serversig[7] == 0)
		{
			RTMP_Log(RTMP_LOGDEBUG, "%s: Wait, did the server just refuse signed authentication?", __FUNCTION__);
		}
		RTMP_Log(RTMP_LOGDEBUG, "%s: Server sent signature:", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
		SHA256_DIGEST_LENGTH);

		/* verify server response */
		HMACsha256(&clientsig[digestPosClient], SHA256_DIGEST_LENGTH, GenuineFMSKey, sizeof(GenuineFMSKey), digest);
		HMACsha256(serversig, RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH, digest,
		SHA256_DIGEST_LENGTH, signature);

		/* show some information */
		RTMP_Log(RTMP_LOGDEBUG, "%s: Digest key: ", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, digest, SHA256_DIGEST_LENGTH);

#ifdef FP10
		if (type == 8)
		{
			uint8_t *dptr = digest;
			uint8_t *sig = signature;
			/* encrypt signature */
			for (i = 0; i < SHA256_DIGEST_LENGTH; i += 8)
				rtmpe8_sig(sig + i, sig + i, dptr[i] % 15);
		}
#if 0
		else if (type == 9)
		{
			uint8_t *dptr = digest;
			uint8_t *sig = signature;
			/* encrypt signatureResp */
			for (i=0; i<SHA256_DIGEST_LENGTH; i+=8)
			rtmpe9_sig(sig+i, sig+i, dptr[i] % 15);
		}
#endif
#endif
		RTMP_Log(RTMP_LOGDEBUG, "%s: Signature calculated:", __FUNCTION__);
		RTMP_LogHex(RTMP_LOGDEBUG, signature, SHA256_DIGEST_LENGTH);
		if (memcmp(signature, &serversig[RTMP_SIG_SIZE - SHA256_DIGEST_LENGTH],
		SHA256_DIGEST_LENGTH) != 0)
		{
			RTMP_Log(RTMP_LOGWARNING, "%s: Server not genuine Adobe!", __FUNCTION__);
			return FALSE;
		}
		else
		{
			RTMP_Log(RTMP_LOGDEBUG, "%s: Genuine Adobe Flash Media Server", __FUNCTION__);
		}

		if (encrypted)
		{
			char buff[RTMP_SIG_SIZE];
			/* set keys for encryption from now on */
			r->Link.rc4keyIn = keyIn;
			r->Link.rc4keyOut = keyOut;

			/* update the keystreams */
			if (r->Link.rc4keyIn)
			{
				RC4_encrypt(r->Link.rc4keyIn, RTMP_SIG_SIZE, (uint8_t * ) buff);
			}

			if (r->Link.rc4keyOut)
			{
				RC4_encrypt(r->Link.rc4keyOut, RTMP_SIG_SIZE, (uint8_t * ) buff);
			}
		}
	}
	else
	{
		//比较c1的内容跟s2的内容是否一致。
		//其实验证支持的协议版本号、服务器的时间戳等等。确保连接的对端真的是RTMP支持。
		if (memcmp(serversig, clientsig, RTMP_SIG_SIZE) != 0)
		{
			RTMP_Log(RTMP_LOGWARNING, "%s: client signature does not match!", __FUNCTION__);
		}
	}

	RTMP_Log(RTMP_LOGDEBUG, "%s: Handshaking finished....", __FUNCTION__);
	return TRUE;
}

参考:http://blog.csdn.net/defonds/article/details/17534903

时间: 2024-10-13 16:35:45

rtmpdump源代码分析------HandShake的相关文章

转:RTMPDump源代码分析

0: 主要函数调用分析 rtmpdump 是一个用来处理 RTMP 流媒体的开源工具包,支持 rtmp://, rtmpt://, rtmpe://, rtmpte://, and rtmps://.也提供 Android 版本. 最近研究了一下它内部函数调用的关系. 下面列出几个主要的函数的调用关系. RTMPDump用于下载RTMP流媒体的函数Download: 用于建立网络连接(NetConnect)的函数Connect: 用于建立网络流(NetStream)的函数 rtmpdump源代码

Java中arraylist和linkedlist源代码分析与性能比較

Java中arraylist和linkedlist源代码分析与性能比較 1,简单介绍 在java开发中比較经常使用的数据结构是arraylist和linkedlist,本文主要从源代码角度分析arraylist和linkedlist的性能. 2,arraylist源代码分析 Arraylist底层的数据结构是一个对象数组.有一个size的成员变量标记数组中元素的个数,例如以下图: * The array buffer into which the elements of the ArrayLis

Kafka SocketServer源代码分析

Kafka SocketServer源代码分析 标签: kafka 本文将详细分析Kafka SocketServer的相关源码. 总体设计 Kafka SocketServer是基于Java NIO来开发的,采用了Reactor的模式,其中包含了1个Acceptor负责接受客户端请求,N个Processor负责读写数据,M个Handler来处理业务逻辑.在Acceptor和Processor,Processor和Handler之间都有队列来缓冲请求. kafka.network.Accepto

pomelo源代码分析(一)

千里之行始于足下,一直说想了解pomelo,对pomelo有兴趣,但一直迟迟没有去碰,尽管对pomelo进行源代码分析,在网络上肯定不止我一个,已经有非常优秀的前辈走在前面,如http://golanger.cn/,在阅读Pomelo代码的时候,已经连载到了11篇了,在我的源代码分析參考了该博客,当然,也会添?我对pomelo的理解,借此希望能提高一下自己对node.js的了解和学习一些优秀的设计. 开发环境:win7 调试环境:webstorm5.0 node.js版本号:v0.8.21 源代

Jafka源代码分析——随笔

Kafka是一个分布式的消息中间件,可以粗略的将其划分为三部分:Producer.Broker和Consumer.其中,Producer负责产生消息并负责将消息发送给Kafka:Broker可以简单的理解为Kafka集群中的每一台机器,其负责完成消息队列的主要功能(接收消息.消息的持久化存储.为Consumer提供消息.消息清理.....):Consumer从Broker获取消息并进行后续的操作.每个broker会有一个ID标识,该标识由人工在配置文件中配置. Kafka中的消息隶属于topic

ftp server源代码分析20140602

当前是有些工具比如apktool,dextojar等是可以对我们android安装包进行反编译,获得源码的.为了减少被别人破解,导致源码泄露,程序被别人盗取代码,等等.我们需要对代码进行混淆,android的sdk中为我们提供了ProGrard这个工具,可以对代码进行混淆(一般是用无意义的名字来重命名),以及去除没有使用到的代码,对程序进行优化和压缩,这样可以增加你想的难度.最近我做的项目,是我去配置的混淆配置,因此研究了一下,这里分享一下. 如何启用ProGuard ant项目和eclipse

Spark SQL之External DataSource外部数据源(二)源代码分析

上周Spark1.2刚公布,周末在家没事,把这个特性给了解一下,顺便分析下源代码,看一看这个特性是怎样设计及实现的. /** Spark SQL源代码分析系列文章*/ (Ps: External DataSource使用篇地址:Spark SQL之External DataSource外部数据源(一)演示样例 http://blog.csdn.net/oopsoom/article/details/42061077) 一.Sources包核心 Spark SQL在Spark1.2中提供了Exte

【转载】linux环境下tcpdump源代码分析

linux环境下tcpdump源代码分析 原文时间 2013-10-11 13:13:02   原文链接   主题 Tcpdump 作者:韩大卫 @ 吉林师范大学 tcpdump.c 是tcpdump 工具的main.c, 本文旨对tcpdump的框架有简单了解,只展示linux平台使用的一部分核心代码. Tcpdump 的使用目的就是打印出指定条件的报文,即使有再多的正则表达式作为过滤条件.所以只要懂得tcpdump -nXXi eth0 的实现原理即可. 进入main之前,先看一些头文件 n

Android万能适配器base-adapter-helper的源代码分析

项目地址:https://github.com/JoanZapata/base-adapter-helper 1. 功能介绍 1.1. base-adapter-helper base-adapter-helper 是对传统的 BaseAdapter ViewHolder 模式的一个封装.主要功能就是简化我们书写 AbsListView 的 Adapter 的代码,如 ListView,GridView. 1.2 基本使用 mListView.setAdapter(mAdapter = new