http请求如何防止重放攻击?

2,271 阅读3分钟

为了防止重放攻击,我们可以使用一种叫做“时间戳+随机数+签名”的方法,其中签名通过密钥进行生成。

以下是一个基本的流程:

  1. 客户端生成请求参数,生成一个时间戳和随机数。
  2. 客户端使用特定的密钥和算法(例如SHA256)对请求参数、时间戳和随机数进行签名。
  3. 客户端将时间戳、随机数和签名一起作为请求参数发送到服务端。
  4. 服务端接收到请求后,使用同样的密钥和算法对接收到的请求参数、时间戳和随机数进行签名,然后比较这个签名和接收到的签名是否一致。如果一致,说明这个请求是合法的。
  5. 服务端还需要验证时间戳和随机数,来防止重放攻击。服务端可以保存过去一段时间内接收到的时间戳和随机数,如果接收到的时间戳和随机数在已保存的列表中,则拒绝请求。

预防重放攻击的随机数(Nonce)逻辑

生成的随机数(Nonce)主要是为了增加重放攻击的复杂性,每次请求的nonce应该都是不同的。在一个给定的时间戳内,即使请求参数相同,由于nonce不同,生成的签名也会不同,这样就可以防止重放攻击。

Nonce 的处理逻辑通常如下:

  1. 客户端生成请求时,创建一个随机数作为nonce。这个随机数应该有足够的长度和复杂性,以防止被轻易猜到。
  2. 生成签名时,将这个nonce作为签名的一部分,这样即使其他部分(如时间戳和请求参数)相同,由于nonce不同,签名也会不同。
  3. 将nonce和其他请求参数一起发送到服务端。
  4. 服务端收到请求后,会将nonce、请求参数和时间戳重新计算签名,并与客户端发送的签名进行比较,以验证请求的有效性。
  5. 服务端还需要检查nonce是否已经使用过。服务端需要维护一个记录已使用nonce的列表或数据库。如果接收到的nonce已经在这个列表中,那么请求应该被认为是重放的,服务端应该拒绝此请求。
  6. 如果nonce是新的,那么在处理请求后,应该将其添加到已使用的nonce列表中。为了防止列表过大,可以在nonce过期后从列表中删除。

Nonce表

防重放攻击的Nonce表至少需要两个字段:一个用于存储Nonce值,另一个用于存储Nonce生成或使用的时间戳。

这样在检查Nonce是否已被使用时,然后可以根据时间戳来删除过期的Nonce。

客户端请求代码

下面给出客户端的请求代码,使用python的requests库实现。

import requests
import hashlib
import time
import random

def generate_signature(secret_key, data, timestamp, nonce):
    h = hashlib.sha256()
    h.update((secret_key + data + str(timestamp) + str(nonce)).encode('utf-8'))
    return h.hexdigest()

timestamp = int(time.time())
nonce = random.randint(10000, 99999)
secret_key = 'YourSecretKey'
data = 'YourRequestData'

signature = generate_signature(secret_key, data, timestamp, nonce)

response = requests.post('https://yourwebsite.com/api', 
                         data={'data': data, 'timestamp': timestamp, 'nonce': nonce, 'signature': signature})

服务端处理代码

服务端处理代码使用python的apiflask实现。

from apiflask import APIFlask, input, output, Schema
from flask import request
import hashlib
import time

def generate_signature(secret_key, data, timestamp, nonce):
    h = hashlib.sha256()
    h.update((secret_key + data + str(timestamp) + str(nonce)).encode('utf-8'))
    return h.hexdigest()

# 定义输入格式
class InputSchema(Schema):
    data = String()
    timestamp = Integer()
    nonce = Integer()
    signature = String()

app = APIFlask(__name__)

@app.post('/api')
@input(InputSchema)
def check_request():
    input_data = request.json
    secret_key = 'YourSecretKey'

    # 校验时间戳是否在5分钟之内
    if abs(int(time.time()) - input_data['timestamp']) > 300:
        return {'message': 'Timestamp check failed'}, 400

    # 检查 nonce 是否存在数据库中,防止重放攻击
    # 如果存在则返回错误,不存在则存入数据库
    # 请自行实现相关的数据库操作
    # ...

    # 校验签名
    server_signature = generate_signature(secret_key, input_data['data'], input_data['timestamp'], input_data['nonce'])

    if server_signature != input_data['signature']:
        return {'message': 'Signature check failed'}, 400

    return {'message': 'Request is valid'}