滑动窗口防重放算法使用经验分享

2020-03-06  本文已影响0人  王小宇gg

一、概要

在安全机制的设计过程中,通常考虑多种安全机制,其中防重放机制是众多安全机制中较为重要的一个。在尤其在控制信令传输环节,防重放攻击尤为重要。目前项目中使用到的防重放机制多为流水编号机制,该机制实现简单,但无法满足复杂网络环境下存在丢包的场景。该案例介绍了两种滑动窗口的设计思路和实现,以满足复杂网络环境下丢包和包不连续的场景。

二、滑动窗口算法描述

(一)标准滑动窗口算法

  1. 窗口右边界为已接收的,且最大的数据包序列号(Max Sequence Number),窗口左边界为最大数据包序列号减去窗口大小得到的数值(Max Sequence Number - Windows Size)。
    假设:窗口大小为32,Max Sequence Number为100,窗口如下:
    |_____________32_____________|
    68          100

  2. 当新接收的数据包的Sequence Number 大于68且小于100时,数据包合法,且窗口中对应的位置设置为1,说明该序号已接收。当该序号再次出现时,认为非法。如当前Sequence Number为78:
    |________1_____32_____________|
    68   \color{red}{78}         100

  3. 当新接收的数据包的Sequence Number 小于68时,认为该数据包非法。

  4. 当新接收的数据包的Sequence Number 大于100,小于100+32时,数据包合法,并将窗口右边界设置为该数据包Sequence Number , 如当前Sequence Number 为120:
    |_____________32_____________|
    88            120

  5. 当新接收的数据包的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;
}

上一篇下一篇

猜你喜欢

热点阅读