Signature scheme based on "request replay"

1: Background

Recently, we are doing service docking with a third party. The third party needs to call our service interface to query data. For the exposed interface, security is very important, so we must add a signature when doing interface verification, which can effectively protect our interface security

2: Encryption

It is impossible to let the data run naked in the network, so we need to encrypt the interface parameters or the returned data, and we must use the state secret SM4 to encrypt the requirements of the requester (Party A)

3: Signature

In addition to the request parameters, we also need to include the signature in the data sent. After we receive the request, we judge whether the signature is legal. If it is not legal, we will not deal with it, but there will be problems. If the hacker intercepts the request and resends it again and again, your server will pass. Because your signature has not been changed, I just send the request repeatedly

4: Time stamp

The signature is not just an agreed string, we also need to add a timestamp. Add the time stamp when the request is sent to the sender's signature. When the server receives the request, it decrypts and obtains the time stamp. If the time stamp in the signature is more than 1 minute or 2 minutes from the current time (this time is defined by itself), the request will not be processed or the request failure information will be returned

client:
//The agreed string in the signature
String sign  = "adnchgsnsb";
//Gets the current timestamp
long timeMillis = System.currentTimeMillis();
//Splicing signature
StringBuilder sb = new StringBuilder();
sb.append(sign).append('-').append(timeMillis);
//Encrypt signature
SM4.decode(sb.toString);
//Call remote service
Server:
//Get signature from parameter
String sign = sign;
//Gets the timestamp and agreed signature string from the signature
String[] strs = sign.split('-');
//Gets the signed string
String singStr = strs.[0];
//Judge whether the signature string is agreed
//Get timestamp
String time = strs[1];
//Get current timestamp
long timeMillis = System.currentTimeMillis();
//Judge whether the time of both exceeds the limit
if(Beyond) {
    return "request timeout";
}else{
  //Processing requests
}

It seems that it does meet our requirements, but there will still be problems in practice. For example, how much is the timeout appropriate?? At the beginning, I set it to 10s, but then there were a lot of request timeout exceptions, which led the customer to keep saying,

There's no way to do this later. I can only set the time longer, but the time is set longer, and the customer's response timeout exception is almost gone. But now there's another problem. Suppose you set the time to 5 minutes (yes, I set it for so long), you still can't solve the problem of requesting replay, because it's too long, After you send a request, others get your request and have enough time to operate

5: Add Redis

At this time, our signature consists of signature string + timestamp. Now we add a random number,

client:
//The agreed string in the signature
String sign  = "adnchgsnsb";
//Gets the current timestamp
long timeMillis = System.currentTimeMillis();
//Get random number
String uuid = UUID.randomUUID().toString();
//Splicing signature
StringBuilder sb = new StringBuilder();
sb.append(sign).append('-').append(timeMillis).append('-').append(uuid);
//Encrypt signature
SM4.decode(sb.toString);
//Call remote service
Server:
//Get signature from parameter
String sign = sign;
//Gets the timestamp and agreed signature string from the signature
String[] strs = sign.split('-');
//Gets the signed string
String singStr = strs.[0];
//Judge whether the signature string is agreed
//Get timestamp
String time = strs[1];
//Get current timestamp
long timeMillis = System.currentTimeMillis();
//Get random number
String uuid = strs[2];
//Judge whether the time of both exceeds the limit
if(Beyond) {
    return "request timeout";
}else{
  //Determine whether the request has been processed before
  Boolean uuid = redisUtil.get(uuid);
  //If it exists, it indicates that the request has been processed before, so it will not be processed
  if(uuid) {
     return "The request has been processed";
  }else{
     //If it does not exist, it can be handled normally
     //Processing requests
     //Put the random number into redis
     redisUtil.set(uuid,uuid);  //Do not set expiration time
  }
}

At this time, we can see that the key in redis does not set the expiration time, which will lead to higher and higher memory consumption in redis on the server

6: Optimize

Let's think about it. The key in redis can actually set the expiration time, and the size of this time is just the time to judge the timestamp. If we set the request time to 5 minutes, I just need to ensure that I don't receive the request within these 5 minutes. After 5 minutes, if it's still the request, In fact, it can be filtered when judging where the timestamp is, so the server code is modified as follows

Server:
//Get signature from parameter
String sign = sign;
//Gets the timestamp and agreed signature string from the signature
String[] strs = sign.split('-');
//Gets the signed string
String singStr = strs.[0];
//Judge whether the signature string is agreed
//Get timestamp
String time = strs[1];
//Get current timestamp
long timeMillis = System.currentTimeMillis();
//Get random number
String uuid = strs[2];
//Judge whether the time of both exceeds the limit
if(Beyond) {
    return "request timeout";
}else{
  //Determine whether the request has been processed before
  Boolean uuid = redisUtil.get(uuid);
  //If it exists, it indicates that the request has been processed before, so it will not be processed
  if(uuid) {
     return "The request has been processed";
  }else{
     //If it does not exist, it can be handled normally
     //Processing requests
     //Put the random number into redis
     redisUtil.set(uuid,uuid,5);  //Set the expiration time to 5 minutes
  }
}


 

Keywords: Java server http

Added by werkkrew on Wed, 22 Dec 2021 01:18:49 +0200