关于发送超过协商最大长度的SChannel TLS消息的问题咨询
我之前也踩过SChannel这个坑!当你调用EncryptSend时传入的缓冲区超过SecPkgContext_StreamSizes.cbMaximumMessage,超出部分之所以不被服务器和Wireshark识别,核心原因是SChannel的TLS加密是基于记录的,每个TLS记录有严格的最大长度限制——这个cbMaximumMessage就是当前会话允许的单条TLS明文记录的最大长度,超过这个值的内容,EncryptSend不会自动帮你分片,只会处理前cbMaximumMessage长度的数据,剩下的部分直接被丢弃或者无法被正确封装成TLS记录,自然就无法被接收端解析了。
正确的处理方式:手动分片发送
你需要自己把大缓冲区拆分成多个不超过cbMaximumMessage的小块,对每个小块单独调用EncryptSend,然后逐个发送加密后的TLS记录。具体步骤如下:
确认有效最大长度
先通过QueryContextAttributesW获取最新的SecPkgContext_StreamSizes结构,确保你拿到的cbMaximumMessage是当前TLS会话的有效值(这个值在会话建立后一般不会变化,但最好每次发送前都确认一下,避免异常情况)。拆分缓冲区并逐个加密发送
循环处理你的大缓冲区,每次取不超过cbMaximumMessage的长度作为分片,调用EncryptSend加密后发送。注意要处理EncryptSend返回的ERROR_INSUFFICIENT_BUFFER——这个返回值是用来让你获取加密后所需的输出缓冲区大小的,属于正常流程。
举个简单的C语言示例:
// 假设已完成TLS会话建立,获取到streamSizes结构 // pBuffer: 待发送的明文缓冲区,totalLength: 缓冲区总长度 DWORD bytesProcessed = 0; while (bytesProcessed < totalLength) { // 计算当前分片的大小,不超过最大消息长度 DWORD chunkLen = min(totalLength - bytesProcessed, streamSizes.cbMaximumMessage); PBYTE chunkBuf = pBuffer + bytesProcessed; DWORD encryptedLen = 0; // 先调用EncryptSend获取加密所需的输出缓冲区大小 SECURITY_STATUS status = EncryptSend( hContext, // 已建立的SChannel上下文 chunkBuf, chunkLen, NULL, // 先传NULL获取所需大小 &encryptedLen, 0 // 标志位,根据你的场景调整 ); if (status == SEC_E_OK || status == ERROR_INSUFFICIENT_BUFFER) { // 分配加密缓冲区 PBYTE encryptedBuf = (PBYTE)malloc(encryptedLen); if (encryptedBuf != NULL) { // 真正执行加密 status = EncryptSend( hContext, chunkBuf, chunkLen, encryptedBuf, &encryptedLen, 0 ); if (status == SEC_E_OK) { // 将加密后的TLS记录发送到服务器 send(socketHandle, encryptedBuf, encryptedLen, 0); } free(encryptedBuf); } } bytesProcessed += chunkLen; }
额外注意事项
- 不要合并加密后的记录:每个
EncryptSend调用会生成一个完整的TLS记录(包含记录头、加密内容等),你需要逐个发送这些记录,不要把多个加密后的缓冲区合并后再发送,否则会破坏TLS的记录结构。 - 处理异常情况:如果
EncryptSend返回其他错误(比如会话中断),要及时终止发送流程并处理错误。 - 接收端适配:如果你的接收端也是用SChannel实现的,
DecryptMessage会自动处理分片的TLS记录,不需要额外操作——它会把多个分片的明文数据拼接起来返回给你。
内容的提问来源于stack exchange,提问作者Dnyan Waychal

