一、概要
在安全机制的设计过程中,通常考虑多种安全机制,其中防重放机制是众多安全机制中较为重要的一个。在尤其在控制信令传输环节,防重放攻击尤为重要。目前项目中使用到的防重放机制多为流水编号机制,该机制实现简单,但无法满足复杂网络环境下存在丢包的场景。该案例介绍了两种滑动窗口的设计思路和实现,以满足复杂网络环境下丢包和包不连续的场景。
二、滑动窗口算法描述
(一)标准滑动窗口算法
-
窗口右边界为已接收的,且最大的数据包序列号(Max Sequence Number),窗口左边界为最大数据包序列号减去窗口大小得到的数值(Max Sequence Number - Windows Size)。
假设:窗口大小为32,Max Sequence Number为100,窗口如下:
|_____________32_____________|
68 100 -
当新接收的数据包的Sequence Number 大于68且小于100时,数据包合法,且窗口中对应的位置设置为1,说明该序号已接收。当该序号再次出现时,认为非法。如当前Sequence Number为78:
|________1_____32_____________|
68 100 -
当新接收的数据包的Sequence Number 小于68时,认为该数据包非法。
-
当新接收的数据包的Sequence Number 大于100,小于100+32时,数据包合法,并将窗口右边界设置为该数据包Sequence Number , 如当前Sequence Number 为120:
|_____________32_____________|
88 120 -
当新接收的数据包的Sequence Number 大于100+32时,数据包不合法。
(二)无右边界滑动窗口算法
该算法对标准算法第五步进行调整: 当新接收的数据包Sequence Number大于窗口大小,右边界设置为新接收的Sequence Number,窗口最右端bit设置为1,其余设置为0。
该算法设计背景: 在部分UDP连接情况下,数据包会有连续发送失败的场景,导致接收端接收的数据包Sequence Number骤然增大,防重放机制会阻挡正常数据包的接收,因此需关闭滑动窗口右边界限制。
该算法引入的风险:新方案关闭滑动窗口右边界限制,当同一Session下有大于当前Sequence Number的数据包混入,会将滑动窗口向右推移,导致正常数据无法接收。
风险解决方案:Sequence Number在Session会话有效期内不会重新计数即可。
三、滑动窗口算法运行要求
防重放程序必须在签名校验完成且通过后才能开始运行,防止窗口被恶意推动,导致业务处理中断。
四、算法实现
考虑到滑动窗口计算需要较短的处理时间,因此采用bitmap算法设计滑动窗口实现。并提供固定窗口大小算法和动态设定窗口大小算法,来满足复杂环境下业务对窗口动态调整的需求。
固定窗口大小
// C++ program to demonstrate various functionality of bitset
#include <bits/stdc++.h>
using namespace std;
enum {
ReplayWindowSize = 128 //如果需要将窗口大小设置为1,此处设置ReplayWindowSize = 2
};
typedef unsigned long u_long;
u_long lastSeq = 0;
bitset<ReplayWindowSize> bitmap;
int ChkReplayWindow(u_long seq) {
u_long diff;
if (seq == 0) return 0; /* first == 0 or wrapped */
if (seq > lastSeq) { /* new larger sequence number */
diff = seq - lastSeq;
if (diff < ReplayWindowSize) { /* In window */
bitmap <<= diff;
bitmap |= 1; /* set bit for this packet */
//} else return 0; /* 启用窗口右边界限制 */
} else bitmap = 1; /*关闭窗口右边界限制 */
lastSeq = seq;
return 1; /* larger is good */
}
diff = lastSeq - seq;
if (diff >= ReplayWindowSize) return 0; /* too old or wrapped */
if (bitmap[diff] == 1) return 0; /* already seen */
bitmap |= ((u_long)1 << diff); /* mark as seen */
return 1; /* out of order but good */
}
char string_buffer[512];
#define STRING_BUFFER_SIZE sizeof(string_buffer)
int main()
{
cout << bitmap << endl; // 00000000000000000000000000000000
int result;
u_long last, current;
uint64_t bits;
printf("Input initial state (bits in hex, last msgnum):\n");
if (!fgets(string_buffer, STRING_BUFFER_SIZE, stdin)) exit(0);
sscanf(string_buffer, "%lx %lu", &bits, &last);
if (last != 0)
bits |= 1;
bitmap = bits;
lastSeq = last;
cout << "bits: " << bitmap << "\n";
printf("last:%lu\n", lastSeq);
printf("Input value to test (current):\n");
while (1) {
if (!fgets(string_buffer, STRING_BUFFER_SIZE, stdin)) break;
sscanf(string_buffer, "%lu", ¤t);
result = ChkReplayWindow(current);
printf("%-3s", result ? "OK " : "BAD ");
cout << "bits: " << bitmap << "\n";
printf("last:%lu\n",lastSeq);
}
return 0;
}
动态窗口大小
// C++ program to demonstrate various functionality of bitset
#include <bits/stdc++.h>
using namespace std;
enum {
ReplayWindowSize = 8 //如果需要将窗口大小设置为1,此处设置ReplayWindowSize = 2
};
typedef unsigned long u_long;
u_long lastSeq = 0;
std::vector<bool> bitmap;
void LeftShift(int n){
for (int i = 0;i < n;i++)
{
bitmap.insert(bitmap.begin(),0);
bitmap.erase(bitmap.end());
}
}
void PrintBit(){
for (int i = 0; i<bitmap.size();i++)
{
std::cout << bitmap[i] << ",";
}
std::cout << std::endl;
}
int ChkReplayWindow(u_long seq) {
u_long diff;
if (seq == 0) return 0; /* first == 0 or wrapped */
if (seq > lastSeq) { /* new larger sequence number */
diff = seq - lastSeq;
if (diff < ReplayWindowSize) { /* In window */
LeftShift(diff);
bitmap[0] = 1; /* set bit for this packet */
//} else return 0; /*启用窗口右边界限制*/
} else bitmap.clear();bitmap.resize(ReplayWindowSize);bitmap[0] = 1; /*关闭窗口右边界限制 */
lastSeq = seq;
return 1; /* larger is good */
}
diff = lastSeq - seq;
if (diff >= ReplayWindowSize) return 0; /* too old or wrapped */
if (bitmap[diff] == 1) return 0; /* already seen */
bitmap[diff] = 1; /* mark as seen */
return 1; /* out of order but good */
}
char string_buffer[512];
#define STRING_BUFFER_SIZE sizeof(string_buffer)
int main()
{
bitmap.resize(ReplayWindowSize);
PrintBit(); // 00000000000000000000000000000000
int result;
u_long last, current;
uint64_t bits;
printf("Input initial state (last msgnum):\n");
if (!fgets(string_buffer, STRING_BUFFER_SIZE, stdin)) exit(0);
sscanf(string_buffer, "%lu", &last);
if (last != 0)
bitmap[0] = 1;
lastSeq = last;
PrintBit();
printf("last:%lu\n", lastSeq);
printf("Input value to test (current):\n");
while (1) {
if (!fgets(string_buffer, STRING_BUFFER_SIZE, stdin)) break;
sscanf(string_buffer, "%lu", ¤t);
result = ChkReplayWindow(current);
printf("%-3s", result ? "OK " : "BAD ");
PrintBit();
//cout << "bits: " << bitmap << "\n";
printf("last:%lu\n",lastSeq);
}
return 0;
}
网友评论