Selección de esquema para firma y cifrado de parámetros cuando se comunica entre el cliente y el servidor

Prefacio

Ahora necesitamos desarrollar una plataforma similar a un centro de autorización, a la que en el futuro se conectarán muchas aplicaciones para intercambiar datos.
Al desarrollar esta plataforma, es necesario considerar los problemas de firma y cifrado durante cada comunicación, sin embargo, dado que la plataforma se utiliza internamente y no estará conectada a la red pública por el momento, es posible encontrar una solución de firma y cifrado simple y conveniente. ahora es necesario.

análisis de la demanda

Aplicar credenciales

La plataforma y la aplicación están en una relación superior-subordinado (cliente y servidor), por lo que introducimos los conceptos de appid y appkey al registrar la aplicación. Appid es una credencial pública utilizada para la comunicación y la aplicación de recursos, y la appkey es privada para la aplicación Claves no públicas utilizadas para diversas operaciones confidenciales.
Esto garantiza que cada aplicación tenga una clave independiente que solo la plataforma y la aplicación conocen.

Solicitar parámetros

Los parámetros de solicitud deben contener los parámetros comerciales originales, más la firma y el appid. La firma es para verificar la legitimidad de los parámetros y el appid es para permitir que la aplicación y la plataforma encuentren la clave correspondiente y completen la generación de la firma en función de la clave..

solución

Solución básica: identificación de la aplicación + texto sin formato del parámetro + firma

Sabemos que la firma generada por el algoritmo MD5 es irreversible y la clave de la aplicación (appkey) puede considerarse inaccesible para terceros.
Aquí elegimos establecer la regla de generación del texto de la firma original en: concatenar el formato de cadena de todos los parámetros appkey.

ID de aplicación

Supongamos que el valor de appid es id_1234y el valor de appkey eskey_abcd

Texto sin formato de parámetros

Supongamos que los parámetros que deben transmitirse son:

"appid": "id_1234"
"name": "张三"
"tel": "13812345678"

Para solicitudes de obtención http, el formato de transmisión de parámetros original antes de firmar es:
appid=id_1234&name=张三&tel=13812345678
Para solicitudes de publicación http, generalmente es necesario transmitir un objeto en formato Json al enviar, por lo que el formato de transmisión de parámetros antes de firmar aquí es:
{"appid": "id_1234","name":"张三","tel":"13812345678"}

firmar

Definición de firma digital:
La firma digital es una tecnología que implementa funciones equivalentes a sellar y firmar en el mundo real en el mundo informático. El uso de firmas digitales puede identificar la manipulación y la suplantación de identidad y también evitar el repudio.

El texto de la firma original debe fusionarse con la información de la clave privada de la aplicación mediante un formato de empalme personalizado basado en el texto sin formato del parámetro.
Obtener ejemplo de solicitud: name=张三&tel=13812345678+ &appkey=+ key_abcd
Ejemplo de solicitud posterior: {"appid": "id_1234","name":"张三","tel":"13812345678"}+ &appkey=+ key_abcd
La segunda parte de la cadena aquí &appkey=es un formato fijo de empalme personalizado. Podemos cambiarlo a cualquier otro formato. También puede dejar esta parte en blanco (es decir, los parámetros son directamente la clave de empalme). ).
En resumen, el texto de la firma original = texto sin formato del parámetro + sufijo fijo + clave privada de la aplicación .
Suponiendo que se utiliza el algoritmo MD5 para el texto de la firma original en el ejemplo anterior, la firma resultante es:56282c3eabe16c5945c7287ded2b45de

parámetros de solicitud final

Sobre la base de los parámetros originales, se agregan la firma appid y calculada, por lo que los parámetros finales enviados son:

// 即:请求参数 = 应用id + 参数明文 + 签名
// Get请求参数
appid=id_1234&name=张三&tel=13812345678&sign=56282c3eabe16c5945c7287ded2b45de
// Post请求参数
{"appid": "id_1234","name":"张三","tel":"13812345678","sign":"56282c3eabe16c5945c7287ded2b45de"}

Resumir

Cuando la plataforma recibe este parámetro , encuentra la clave de aplicación de la aplicación según el appid. De acuerdo con la clave de aplicación y las reglas para generar firmas anteriores, también calcula una firma B. Si la firma B es consistente con el signo en el parámetro recibido , puede considerarse como una solicitud legítima y no manipulada.

ventaja

La ventaja es que se puede evitar que el contenido transmitido sea manipulado . La generación de la firma se basa en los parámetros de texto sin formato y la clave privada de la aplicación, por lo que cualquier modificación separada de los parámetros de texto sin formato hará que el receptor calcule una firma diferente, y el receptor puede tratar esta solicitud como una solicitud no válida que ha sido manipulada. .

defecto

Una desventaja obvia es que los parámetros principales de la solicitud están expuestosname , como y en este ejemplo, telque pueden ser interceptados y vistos por el mundo exterior.
Otra desventaja es que, aunque se puede garantizar que la solicitud firmada no será manipulada, no puede evitar ataques de repetición . Es decir, cuando un tercero obtiene el cuerpo completo de la solicitud, puede guardarlo y enviarlo nuevamente, y el destinatario no tiene significa acreditar que la solicitud proviene de usuarios legítimos.

ejemplo de código
// 建议将签名工具写在一个单独的项目中,这样应用方和平台可以同时引用该工具进行加签验签
import com.alibaba.fastjson.JSONObject;
import cn.hutool.crypto.SecureUtil;
/**
 * @author: csdn@Ka_ze
 * @date: 2023/8/9
 */
public class SignUtil {
    
    

    /**
     * 获取加签后的 jsonObject
     *
     * @param salt       用于加签的特殊值
     * @param jsonObject post请求参数的jsonObject格式
     * @return
     */
    public static JSONObject addSign(String salt, JSONObject jsonObject) throws Exception {
    
    
        if (ObjectUtil.isEmpty(jsonObject)) {
    
    
            throw new Exception("jsonObject is empty");
        }
        // json对象格式加签前的明文:调用json对象的toStirng方法
        String sign = getSign(salt, jsonObject.toString());
        jsonObject.put("sign", sign);
        return jsonObject;
    }

    /**
     * 获取加签后的参数字符串
     *
     * @param salt  用于加签的特殊值
     * @param param get请求参数的String格式,应该为key1=value1&key2=value2
     * @return
     * @throws Exception
     */
    public static String addSign(String salt, String param) throws Exception {
    
    
        if (ObjectUtil.isEmpty(param)) {
    
    
            throw new Exception("param is empty");
        }
        // 参数格式检测
        List<String> paramList;
        try {
    
    
            paramList = Arrays.asList(param.split("&"));
        } catch (Exception e) {
    
    
            e.printStackTrace();
            throw new Exception("parameter parsing exception");
        }
        if (ObjectUtil.isEmpty(paramList)) {
    
    
            throw new Exception("param is empty");
        }
        AtomicBoolean isFormatError = new AtomicBoolean(false);
        paramList.forEach(paramStr -> {
    
    
            String[] split = paramStr.split("=");
            if (split.length!=2) {
    
    
                isFormatError.set(true);
            }
        });
        if (isFormatError.get()) {
    
    
            throw new Exception("parameter format exception");
        }
        // 获取签名
        String sign = getSign(salt, param);
        return param + "&sign=" + sign;
    }

    /**
     * 获取签名
     *
     * @param salt      签名源文中的特殊字符串
     * @param plaintext 签名源文
     * @return
     */
    public static String getSign(String salt, String plaintext) throws Exception {
    
    
        if (ObjectUtil.isEmpty(plaintext)) {
    
    
            throw new Exception("plaintext is null");
        }
        // 此处签名原文的生成规则为:参数明文直接拼接应用密钥
        String signSourceStr = plaintext + salt;
        return SecureUtil.md5(signSourceStr);
    }

    /**
     * 校验jsonObject中的签名
     * @param salt 签名特殊值
     * @param jsonObject 接收到的参数jsonObject对象
     * @return
     * @throws Exception
     */
    public static boolean checkSign(String salt, JSONObject jsonObject) throws Exception {
    
    
        if (ObjectUtil.isEmpty(jsonObject)) {
    
    
            throw new Exception("jsonObject is null");
        }
        // 验签时先获取sign,然后将原jsonObject中的sign字段去除,用以获得加签前的参数原文
        String sign = jsonObject.getString("sign");
        String plaintext = jsonObject.fluentRemove("sign").toJSONString();

        if (ObjectUtil.isEmpty(sign)) {
    
    
            return false;
        }

        if (sign.equals(getSign(salt, plaintext))) {
    
    
            return true;
        } else {
    
    
            return false;
        }
    }

    /**
     * 校验字符串参数的签名
     * @param salt 签名特殊值
     * @param map 接收到的参数map
     * @return
     * @throws Exception
     */
    public static boolean checkSign(String salt, LinkedHashMap<String, String> map) throws Exception {
    
    
        if (ObjectUtil.isEmpty(map)) {
    
    
            throw new Exception("map is null");
        }
        String sign = null;
        StringBuilder builder = new StringBuilder();
        String plaintext = null;
        // 动态拼接参数map,以还原加签前的参数原文
        for (String key : map.keySet()) {
    
    
            String value = map.get(key);
            if ("sign".equals(key)) {
    
    
                sign = value;
            } else {
    
    
                builder.append(key + "=" + value + "&");
            }
        }

        if (ObjectUtil.isEmpty(sign)) {
    
    
            return false;
        }

        plaintext = builder.toString();
        plaintext = plaintext.substring(0, plaintext.length()-1);
        if (sign.equals(getSign(salt, plaintext))) {
    
    
            return true;
        } else {
    
    
            return false;
        }
    }
}

Plan de actualización: identificación de la aplicación + texto cifrado (texto sin formato del parámetro + identificación única + marca de tiempo)

Arriba, utilizamos el algoritmo de resumen md5 como firma para demostrar que el cuerpo de la solicitud no ha sido manipulado. Sin embargo, no se pueden ignorar las deficiencias de los parámetros de texto sin formato y la incapacidad de evitar ataques de repetición. A continuación tratamos de resolver estos dos problemas.

Resolver ataques de repetición

Resolver ataques de repetición es un poco como resolver el problema de idempotencia de las interfaces, tanto para evitar múltiples solicitudes de parámetros legítimos.

Opción 1: agregar un número de serie único

Al introducir el número de serie, el remitente agrega un número de serie en los parámetros y el receptor verifica la unicidad del número de serie después del descifrado y antes de procesar la lógica de negocios. Después de pasar la verificación, se procesa la lógica de negocios y finalmente se envía la solicitud. El parámetro es Los números de serie se almacenan en la base de datos .
Esto puede ser inmune a los ataques de repetición , pero la desventaja es que introduce una base de datos y almacena los números de serie de todas las solicitudes, lo que aumenta el tiempo de verificación de la solicitud y también aumenta la carga sobre la base de datos .

Opción 2: agregar marca de tiempo

Al introducir una marca de tiempo, el remitente agrega la hora actual al cuerpo de la solicitud y el receptor descifra la solicitud y verifica si la hora en el cuerpo de la solicitud ha expirado y la descarta si expira .
Suponiendo que el tiempo de vencimiento se establece en 5 minutos, esto garantizará que las solicitudes después de 5 minutos se descarten, pero no resolverá el problema de múltiples solicitudes dentro de 5 minutos . Y si hay una diferencia entre los relojes locales del receptor y del remitente, establecer un tiempo de vencimiento demasiado corto puede impedir que el receptor reciba la solicitud.

Solución 3: Identificación única + marca de tiempo

Combinando las dos soluciones anteriores, utilizamos números de serie para evitar solicitudes repetidas en un corto período de tiempo y usamos marcas de tiempo para evitar solicitudes repetidas en un período de tiempo prolongado .
El remitente agrega un identificador único (caracteres aleatorios o generados en función de parámetros comerciales) y una marca de tiempo en el cuerpo de la solicitud.
Suponiendo que el tiempo de vencimiento se establece en 5 minutos, el receptor primero verifica la marca de tiempo después de descifrar la solicitud, descarta las solicitudes más allá de los 5 minutos y luego verifica el identificador único.
Aquí podemos almacenar en caché la configuración de identificación única en Redis y establecer el tiempo de vencimiento. El receptor pregunta si el identificador único existe en el caché. Si no existe, significa que la solicitud se procesa por primera vez y se permite que la solicitud ingrese al proceso comercial posterior; si existe, significa que el La solicitud ha sido procesada y la solicitud será descartada.
Diagrama de flujo de análisis de solicitudes del servidor

Generar texto cifrado

Sabemos que AES es un algoritmo de cifrado simétrico que cifra parámetros y obtiene texto cifrado a través de una clave, por lo que podemos cifrar AES el texto del parámetro original y utilizar el texto cifrado generado como objeto de transmisión, resolviendo así el problema del texto sin formato de parámetros .
Al mismo tiempo, creemos que la clave utilizada en el cifrado AES es privada para la aplicación y no se puede filtrar, por lo que la manipulación del cuerpo de la solicitud real (ya sea el appid o la marca de tiempo) no puede descifrar correctamente el texto original. Satisface el requisito de que la firma pueda ser identificada Características de manipulación .

Resumir

El método utilizado 应用id+密文(参数明文+唯一标识+时间戳)puede garantizar la transmisión cifrada de parámetros y evitar ataques de repetición. Es una solución de comunicación ideal entre el cliente y el servidor. Además, esta solución todavía tiene margen de mejora: por ejemplo, el método de cifrado se puede cambiar a cifrado asimétrico, el appid se puede transmitir en texto plano, se puede establecer una firma separada en el texto cifrado, etc. Los lectores interesados ​​pueden leer artículos relacionados sobre el esquema de cifrado RSA+AES, que no se analizarán en detalle aquí.

Artículo de referencia

Plan de implementación de firma de interfaz Java (Firma) ¿
Qué es el principio de cifrado RSA + AES del ataque de repetición
, el método de cifrado principal de los fabricantes de primer nivel?

Supongo que te gusta

Origin blog.csdn.net/Ka__ze/article/details/132110093
Recomendado
Clasificación