Schemaauswahl zur Parametersignierung und Verschlüsselung bei der Kommunikation zwischen Client und Server

Vorwort

Nun gilt es, eine Plattform ähnlich einem Autorisierungszentrum zu entwickeln, an die in Zukunft viele Anwendungen angeschlossen werden, um Daten auszutauschen.
Bei der Entwicklung dieser Plattform müssen die Signatur- und Verschlüsselungsprobleme bei jeder Kommunikation berücksichtigt werden. Da die Plattform jedoch intern verwendet wird und vorerst nicht an das öffentliche Netzwerk angeschlossen wird, ist eine einfache und bequeme Signatur- und Verschlüsselungslösung erforderlich jetzt nötig.

Bedarfsanalyse

Anmeldeinformationen anwenden

Die Plattform und die Anwendung stehen in einer übergeordneten Unterordnungsbeziehung (Client und Server), daher führen wir bei der Registrierung der Anwendung die Konzepte von Appid und Appkey ein . Die Appid ist ein öffentlicher Berechtigungsnachweis, der für die Kommunikation und Ressourcenanwendung verwendet wird, und der Appkey ist privat der Anwendung. Nicht öffentliche Schlüssel, die für verschiedene sensible Vorgänge verwendet werden.
Dadurch wird sichergestellt, dass jede Anwendung über einen separaten Schlüssel verfügt, der nur der Plattform und der Anwendung bekannt ist.

Parameter anfordern

Die Anforderungsparameter müssen die ursprünglichen Geschäftsparameter sowie die Signatur und die App-ID enthalten. Die Signatur dient dazu, die Legitimität der Parameter zu überprüfen, und die App-ID soll es der Anwendung und Plattform ermöglichen, den entsprechenden Schlüssel zu finden und die Signaturgenerierung darauf basierend abzuschließen der Schlüssel. .

Lösung

Grundlegende Lösung: Anwendungs-ID + Parameter-Klartext + Signatur

Wir wissen, dass die vom MD5-Algorithmus generierte Signatur irreversibel ist und der Anwendungsschlüssel (Appkey) als für Dritte unzugänglich angesehen werden kann.
Hier legen wir die Generierungsregel des ursprünglichen Signaturtextes auf Folgendes fest: Verketten des Zeichenfolgenformats aller Parameter appkey.

Anwendungs-ID

Angenommen, der Wert von appid ist id_1234und der Wert von appkey istkey_abcd

Parameter-Klartext

Gehen Sie davon aus, dass folgende Parameter übertragen werden müssen:

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

Für HTTP-Get-Anfragen lautet das ursprüngliche Parameterübertragungsformat vor dem Signieren:
appid=id_1234&name=张三&tel=13812345678
Bei HTTP-Post-Anfragen muss beim Senden im Allgemeinen ein Objekt im Json-Format übertragen werden. Daher lautet das Parameterübertragungsformat vor dem Signieren hier:
{"appid": "id_1234","name":"张三","tel":"13812345678"}

Zeichen

Definition der digitalen Signatur:
Digitale Signatur ist eine Technologie, die Funktionen, die dem Stempeln und Signieren in der realen Welt entsprechen, in der Computerwelt implementiert. Mithilfe digitaler Signaturen können Manipulationen und Identitätsdiebstahl erkannt und eine Zurückweisung verhindert werden.

Der ursprüngliche Signaturtext muss über ein benutzerdefiniertes Spleißformat basierend auf dem Parameter Klartext in die privaten Schlüsselinformationen der Anwendung gespleißt werden.
Beispiel für Anforderung abrufen: name=张三&tel=13812345678+ &appkey=+ key_abcd
Beispiel für Anforderung posten: {"appid": "id_1234","name":"张三","tel":"13812345678"}+ &appkey=+ key_abcd
Der zweite Zeichenfolgenteil hier &appkey=ist ein benutzerdefiniertes Spleiß-Festformat. Wir können es in jedes andere Format ändern. Sie können diesen Teil auch leer lassen (d. h. die Parameter sind direkt Spleißschlüssel). ).
Kurz gesagt, der ursprüngliche Signaturtext = Parameter-Klartext + festes Suffix + privater Anwendungsschlüssel .
Unter der Annahme, dass im obigen Beispiel der MD5-Algorithmus für den ursprünglichen Signaturtext verwendet wird, lautet die resultierende Signatur:56282c3eabe16c5945c7287ded2b45de

endgültige Anforderungsparameter

Auf der Grundlage der ursprünglichen Parameter werden die Appid und die berechnete Signatur hinzugefügt, sodass die endgültigen gesendeten Parameter sind:

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

Zusammenfassen

Wenn die Plattform diesen Parameter empfängt , findet sie den Appkey der Anwendung basierend auf der App-ID. Gemäß dem Appkey und den oben genannten Regeln zum Generieren von Signaturen berechnet sie auch eine Signatur B. Wenn Signatur B mit dem Zeichen im empfangenen Parameter übereinstimmt , kann es als legitime, unverfälschte Anfrage angesehen werden.

Vorteil

Der Vorteil besteht darin, dass eine Manipulation der übertragenen Inhalte verhindert werden kann . Die Generierung der Signatur basiert auf den Klartextparametern und dem privaten Schlüssel der Anwendung. Daher führt jede separate Änderung der Klartextparameter dazu, dass der Empfänger eine andere Signatur berechnet, und der Empfänger kann diese Anfrage als ungültige, manipulierte Anfrage behandeln .

Mangel

Ein offensichtlicher Nachteil besteht darin, dass die Kernanforderungsparameter offengelegt werdenname , wie z. B. und in diesem Beispiel, teldie von der Außenwelt abgefangen und angezeigt werden können.
Ein weiterer Nachteil besteht darin, dass zwar garantiert werden kann, dass die signierte Anfrage nicht manipuliert wird, sie aber keinen Replay-Angriff vermeiden kann . Das heißt, wenn ein Dritter den vollständigen Anfragetext erhält, kann er ihn speichern und erneut senden, und der Empfänger hat keine Möglichkeit Mittel zum Nachweis, dass die Anfrage von legitimen Benutzern stammt.

Codebeispiel
// 建议将签名工具写在一个单独的项目中,这样应用方和平台可以同时引用该工具进行加签验签
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;
        }
    }
}

Upgrade-Plan: Anwendungs-ID + Chiffretext (Parameter Klartext + eindeutige Identifikation + Zeitstempel)

Oben haben wir den MD5-Digest-Algorithmus als Signatur verwendet, um zu beweisen, dass der Anforderungstext nicht manipuliert wurde. Die Mängel der Klartextparameter und die Unfähigkeit, Wiederholungsangriffe zu vermeiden, können jedoch nicht ignoriert werden. Im Folgenden versuchen wir, diese beiden Probleme zu lösen.

Replay-Angriffe lösen

Das Lösen von Replay-Angriffen ist ein bisschen wie das Lösen des Idempotenzproblems von Schnittstellen, beides, um mehrfache Anfragen nach legitimen Parametern zu vermeiden.

Option 1: Fügen Sie eine eindeutige Seriennummer hinzu

Durch Eingabe der Seriennummer fügt der Absender den Parametern eine Seriennummer hinzu , und der Empfänger überprüft die Eindeutigkeit der Seriennummer nach der Entschlüsselung und vor der Verarbeitung der Geschäftslogik. Nach bestandener Überprüfung wird die Geschäftslogik verarbeitet und schließlich die Anfrage gestellt Der Parameter lautet: Seriennummern werden in der Datenbank gespeichert .
Dies kann gegen Replay-Angriffe immun sein . Der Nachteil besteht jedoch darin, dass eine Datenbank eingeführt und die Seriennummern aller Anforderungen gespeichert werden, was die Überprüfungszeit der Anforderungen erhöht und auch die Belastung der Datenbank erhöht .

Option 2: Zeitstempel hinzufügen

Durch die Einführung eines Zeitstempels fügt der Absender die aktuelle Zeit zum Anfragetext hinzu , und der Empfänger entschlüsselt die Anfrage und überprüft, ob die Zeit im Anfragetext abgelaufen ist, und verwirft sie, wenn sie abläuft .
Unter der Annahme, dass die Ablaufzeit auf 5 Minuten eingestellt ist, stellt dies sicher, dass Anfragen nach 5 Minuten verworfen werden, löst jedoch nicht das Problem mehrerer Anfragen innerhalb von 5 Minuten . Und wenn zwischen den lokalen Uhren des Empfängers und des Senders ein Unterschied besteht, kann eine zu kurze Ablaufzeit dazu führen, dass der Empfänger die Anfrage nicht empfängt.

Lösung 3: Eindeutige Identifikation + Zeitstempel

Durch die Kombination der beiden oben genannten Lösungen verwenden wir Seriennummern, um wiederholte Anfragen in einem kurzen Zeitraum zu vermeiden, und Zeitstempel, um wiederholte Anfragen über einen längeren Zeitraum zu vermeiden .
Der Absender fügt dem Anfragetext eine eindeutige Kennung (zufällige Zeichen oder basierend auf Geschäftsparametern generiert) und einen Zeitstempel hinzu.
Unter der Annahme, dass die Ablaufzeit auf 5 Minuten eingestellt ist, überprüft der Empfänger nach der Entschlüsselung der Anfrage zunächst den Zeitstempel, verwirft Anfragen über 5 Minuten hinaus und überprüft dann die eindeutige Kennung.
Hier können wir die eindeutigen Identifikationseinstellungen auf Redis zwischenspeichern und die Ablaufzeit festlegen. Der Empfänger fragt ab, ob die eindeutige Kennung im Cache vorhanden ist. Wenn sie nicht vorhanden ist, bedeutet dies, dass die Anforderung zum ersten Mal verarbeitet wird und die Anforderung in den nachfolgenden Geschäftsprozess eintreten darf. Wenn sie vorhanden ist, bedeutet dies, dass die Anforderung zum ersten Mal verarbeitet wird Die Anfrage wurde bearbeitet und die Anfrage wird verworfen.
Flussdiagramm zum Parsen von Serveranforderungen

Chiffretext generieren

Wir wissen, dass AES ein symmetrischer Verschlüsselungsalgorithmus ist, der Parameter verschlüsselt und Chiffretext über einen Schlüssel erhält. Daher können wir den ursprünglichen Parametertext mit AES verschlüsseln und den generierten Chiffretext als Übertragungsobjekt verwenden, wodurch das Problem des Parameter-Klartexts gelöst wird .
Gleichzeitig glauben wir, dass der bei der AES-Verschlüsselung verwendete Schlüssel privat für die Anwendung ist und nicht preisgegeben werden kann. Daher kann eine Manipulation des eigentlichen Anforderungstexts (sei es die App-ID oder der Zeitstempel) den Originaltext nicht ordnungsgemäß entschlüsseln. Dies gilt auch Erfüllt die Anforderung, dass die Signatur identifiziert werden kann . Manipulationseigenschaften .

Zusammenfassen

Das verwendete Verfahren 应用id+密文(参数明文+唯一标识+时间戳)kann eine verschlüsselte Übertragung von Parametern gewährleisten und Replay-Angriffe vermeiden und ist eine ideale Kommunikationslösung zwischen Client und Server. Darüber hinaus bietet diese Lösung noch Raum für Verbesserungen. Beispielsweise kann das Verschlüsselungsverfahren auf asymmetrische Verschlüsselung umgestellt werden, die Appid kann im Klartext übertragen werden, eine separate Signatur kann im Chiffretext gesetzt werden usw. Interessierte Leser können verwandte Artikel zum RSA+AES-Verschlüsselungsschema lesen, auf die hier nicht im Detail eingegangen wird.

Referenzartikel

Implementierungsplan für die Java-Schnittstellensignatur (Signatur)
Was ist das
RSA + AES-Verschlüsselungsprinzip von Replay-Angriffen, die gängige Verschlüsselungsmethode von First-Tier-Herstellern?

おすすめ

転載: blog.csdn.net/Ka__ze/article/details/132110093