Freund oder Feind?

Freund oder Feind?

Das System besteht aus zwei Phasen: Datenbankgenerierung und Vor-Ort-Verifizierung. Datenbank (Datenbankgenerierung): Es wird ein Array eindeutiger geheimer Schlüssel (Token) für alle „Freunde“ generiert. Diese Schlüssel werden durch Hash-Funktionen geleitet und bilden eine Bitmap (Bloom-Filter). Export: Die Bitmap wird an die Verifizierungsgeräte kopiert. Die geheimen Schlüssel selbst werden nicht an die Geräte übertragen. Verifizierung: Wenn ein „Freund“-Objekt angefordert wird, sendet es seinen geheimen Token. Das Verifizierungsgerät filtert den Token. Gibt der Filter „false“ zurück, ist das Objekt garantiert ein Freund. Gibt der Filter „true“ zurück, ist das Objekt (innerhalb der festgelegten Fehlertoleranz) ein Freund.

1. Das Hauptmerkmal des Bloom-Filters

Das Hauptmerkmal des Bloom-Filters ist die geringe Falsch-Positiv-Rate. Das bedeutet, dass der Algorithmus ein „Freund“-Token fälschlicherweise als „Feind“ erkennen kann, nur weil die Bits verschiedener anderer „Freunde“ die Map versehentlich überlappen und so die erforderlichen Indizes für das feindliche Token belegen. Dies ist in Sicherheitssystemen von entscheidender Bedeutung – eine sehr niedrige Fehlerrate ist in das Design des Filters integriert. Eine Fehlerrate von 0,000001 (eins zu einer Million) erhöht zwar die Bitmap-Größe geringfügig, das System wird aber extrem präzise.

2. Die Logik hier ist asymmetrisch

Absolute Gewissheit für Narren: Der Bloom-Filter ist so konzipiert, dass er bei mindestens einem Bit im Array mit dem Wert 0 ein 100%ig korrektes Ergebnis liefert: Dieses Element existierte nie in der Datenbank. Das bedeutet, dass wir das Element „Narr“ bereits im ersten Schleifendurchlauf sofort und sicher eliminieren können. Wahrscheinliche Gewissheit für Freunde: Sind alle Bits 1, erklärt der Algorithmus: „Das Objekt ist höchstwahrscheinlich ein Freund.“ Um Zugriff zu gewähren, müssen wir alle k Hash-Funktionen bis zum Schluss prüfen. Daher suchen wir im Code zunächst nach Anzeichen des „Narr“-Flags (einem Bit mit dem Wert 0), um das Programm schnellstmöglich zu beenden und einen Alarm auszulösen. Werden während der gesamten Schleife keine Anzeichen für „Narr“ gefunden, wird das Objekt als „Freund“ erkannt.


for (int index : indices) {
    if (!bit_array[index]) {
        return "STORNIEREN (100%)";
    }
}
return "ZUGRIFF GEWÄHRT";
              

3. Code

Zum Kompilieren benötigen Sie die OpenSSL-Bibliothek. Diese ist üblicherweise auf allen Linux-Systemen installiert oder kann mit einem einzigen Befehl installiert werden: sudo apt install libssl-dev


#include <iostream>
#include <vector>
#include <string>
#include <cmath>
#include <ctime>
#include <fstream>
#include <iomanip>
#include <sstream>
#include <algorithm>
#include <cctype>
#include <openssl/evp.h>
#include <openssl/hmac.h>

bool QUIET_MODE = false;

class TimeBasedIFFBloomFilter {
private:
    int n;
    double p;
    int step;
    int m;
    int k;
    std::vector<bool> bit_array;

    long long get_time_window() const {
        return std::time(nullptr) / step;
    }

    std::string sha256(const std::string& str) const {
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int hash_len;
        EVP_MD_CTX* context = EVP_MD_CTX_new();
        EVP_DigestInit_ex(context, EVP_sha256(), nullptr);
        EVP_DigestUpdate(context, str.c_str(), str.size());
        EVP_DigestFinal_ex(context, hash, &hash_len);
        EVP_MD_CTX_free(context);

        std::stringstream ss;
        for(unsigned int i = 0; i < hash_len; ++i) {
            ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
        }
        return ss.str();
    }

    std::string hmac_sha256(const std::string& key, const std::string& msg) const {
        unsigned char hash[EVP_MAX_MD_SIZE];
        unsigned int hash_len;
        HMAC(EVP_sha256(), key.c_str(), key.length(),
             reinterpret_cast<const unsigned char*>(msg.c_str()), msg.length(),
             hash, &hash_len);

        std::stringstream ss;
        for(unsigned int i = 0; i < hash_len; ++i) {
            ss << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
        }
        return ss.str();
    }

    std::vector<int> get_bloom_indices(const std::string& token) const {
        std::vector<int> indices;
        for (int i = 0; i < k; ++i) {
            std::string salt_input = "salt_" + std::to_string(i) + ":" + token;
            std::string hash_hex = sha256(salt_input);
            unsigned long long hash_value = std::stoull(hash_hex.substr(0, 12), nullptr, 16);
            indices.push_back(hash_value % m);
        }
        return indices;
    }

    void recalculate_dimensions() {
        m = static_cast<int>(- (n * std::log(p)) / (std::log(2) * std::log(2)));
        k = static_cast<int>((m / n) * std::log(2));
        if (m <= 0) m = 8;
        if (k <= 0) k = 1;
    }

public:
    TimeBasedIFFBloomFilter() : n(1), p(0.01), step(30) {
        recalculate_dimensions();
        bit_array.assign(m, false);
    }

    TimeBasedIFFBloomFilter(int expected_elements, double false_positive_rate, int step_seconds)
        : n(expected_elements), p(false_positive_rate), step(step_seconds) {
        recalculate_dimensions();
        bit_array.assign(m, false);

        if (!QUIET_MODE) {
            std::cout << "[SYSTEM] Filterkonfiguration:\n"
                      << "          -> Geschätzte Kapazität (n): " << n << "\n"
                      << "          -> Fehler (p): " << p * 100 << "%\n"
                      << "          -> Zeitschritt (step): " << step << " Sek.\n"
                      << "          -> Kartengröße (m): " << m << " Bit (" << std::ceil(m / 8.0) << " Byte)\n"
                      << "          -> Anzahl der Hashfunktionen (k): " << k << "\n\n";
        }
    }

    void build_matrix(const std::vector<std::string>& friends_secrets) {
        bit_array.assign(m, false);
        long long current_window = get_time_window();
        for (const auto& secret : friends_secrets) {
            std::string token = hmac_sha256(secret, std::to_string(current_window));
            for (int index : get_bloom_indices(token)) {
                bit_array[index] = true;
            }
        }
    }

    void save_to_file(const std::string& filename) {
        std::ofstream outfile(filename, std::ios::binary);
        if (!outfile.is_open()) return;
        outfile.write(reinterpret_cast<const char*>(&n), sizeof(n));
        outfile.write(reinterpret_cast<const char*>(&p), sizeof(p));
        outfile.write(reinterpret_cast<const char*>(&step), sizeof(step));

        char current_byte = 0;
        int bit_count = 0;
        for (int i = 0; i < m; ++i) {
            if (bit_array[i]) current_byte |= (1 << bit_count);
            bit_count++;
            if (bit_count == 8 || i == m - 1) {
                outfile.put(current_byte);
                current_byte = 0;
                bit_count = 0;
            }
        }
        outfile.close();
    }

    bool load_from_file(const std::string& filename) {
        std::ifstream infile(filename, std::ios::binary);
        if (!infile.is_open()) return false;
        infile.read(reinterpret_cast<char*>(&n), sizeof(n));
        infile.read(reinterpret_cast<char*>(&p), sizeof(p));
        infile.read(reinterpret_cast<char*>(&step), sizeof(step));

        recalculate_dimensions();
        bit_array.assign(m, false);

        if (!QUIET_MODE) {
            std::cout << "[STELLE] Metadaten aus Datei importiert:\n"
                      << "          -> Basis-Matrixkapazität (n): " << n << "\n"
                      << "          -> Berechnungsfehler (p): " << p * 100 << "%\n"
                      << "          -> Gültigkeitsfenster (step): " << step << " Sek.\n"
                      << "          -> Bit rastergröße: " << m << " Bit\n\n";
        }

        char byte;
        int bit_index = 0;
        while (infile.get(byte)) {
            for (int i = 0; i < 8; ++i) {
                if (bit_index < m) {
                    if (byte & (1 << i)) bit_array[bit_index] = true;
                    bit_index++;
                }
            }
        }
        infile.close();
        return true;
    }

    bool verify_key(const std::string& secret_key) const {
        std::string client_token = hmac_sha256(secret_key, std::to_string(get_time_window()));
        std::vector<int> indices = get_bloom_indices(client_token);
        for (int index : indices) {
            if (!bit_array[index]) return false;
        }
        return true;
    }
};

std::vector<std::string> read_secrets_from_file(const std::string& filepath) {
    std::vector<std::string> secrets;
    std::ifstream file(filepath);
    if (!file.is_open()) {
        if (!QUIET_MODE) std::cerr << "[FEHLER] Schlüsseldatei konnte nicht geöffnet werden: " << filepath << "\n";
        return secrets;
    }
    std::string line;
    while (std::getline(file, line)) {
        line.erase(std::remove_if(line.begin(), line.end(), ::isspace), line.end());
        if (!line.empty()) secrets.push_back(line);
    }
    return secrets;
}

void print_help() {
    std::cout << "==================================================================\n"
              << "              Mehrfachprüfung der IFF-Systembefehle                  \n"
              << "==================================================================\n"
              << "Verwendung:\n"
              << "  1. MATRIXINITIALISIERUNG (Hauptsitz):\n"
              << "     ./iff_system --init -k KEY1 KEY2\n"
              << "     ./iff_system --init -f whitelist.txt\n\n"
              << "  2. TASTENANORDNUNGSPRÜFUNG (Stelle):\n"
              << "     ./iff_system --check -k KEY1 KEY2 KEY3 ...\n"
              << "     ./iff_system --check -f check_list.txt\n\n"
              << "Optionen:\n"
              << "  -q, --quiet       Stiller Modus (Ausgabe ausschließlich 'KEY:1' oder 'KEY:0')\n"
              << "  -h, --help        Hilfeinformationen anzeigen.\n"
              << "==================================================================\n";
}

int main(int argc, char* argv[]) {
    if (argc < 3) {
        print_help();
        return 1;
    }

    for (int i = 1; i < argc; ++i) {
        std::string arg = argv[i];
        if (arg == "-h" || arg == "--help") {
            print_help();
            return 0;
        }
        if (arg == "-q" || arg == "--quiet") {
            QUIET_MODE = true;
        }
    }

    std::string mode = argv[1];
    std::string file_path = "iff_matrix.bin";

    // ==================================================================
    // WICHTIGE ANALYSE FÜR BEIDE TEAMS (--init und --check)
    // ==================================================================
    std::vector<std::string> target_keys;
    int last_arg_index = 3;

    std::string sub_flag = argv[2];
    if (sub_flag == "-k") {
        int i = 3;
        for (; i < argc; ++i) {
            std::string arg = argv[i];
            if (arg == "-k" || arg == "-q" || arg == "--quiet") continue;
            if (std::isdigit(arg[0]) || (arg[0] == '0' && arg.find('.') != std::string::npos)) break;
            target_keys.push_back(arg);
        }
        last_arg_index = i;
    }
    else if (sub_flag == "-f") {
        target_keys = read_secrets_from_file(argv[3]);
        last_arg_index = 4;
    }
    else {
        // Syntax für einfache Prüfung ohne Flag: --check <single_key>
        target_keys.push_back(argv[2]);
    }

    if (target_keys.empty()) {
        std::cerr << "[FEHLER] Es wurden keine Parameter angegeben. Verwenden Sie den Parameter -k oder die Option -f..\n";
        return 1;
    }

    // ==================================================================
    // AUSFÜHRUNG VON MODI
    // ==================================================================
    if (mode == "--init") {
        int filter_n = target_keys.size();
        double filter_p = 0.000001;
        int filter_step = 30;

        if (argc - last_arg_index == 3) {
            try {
                filter_n = std::stoi(argv[last_arg_index]);
                filter_p = std::stod(argv[last_arg_index + 1]);
                filter_step = std::stoi(argv[last_arg_index + 2]);
            } catch (...) {}
        }

        TimeBasedIFFBloomFilter hq_system(filter_n, filter_p, filter_step);
        hq_system.build_matrix(target_keys);
        hq_system.save_to_file(file_path);

        if (!QUIET_MODE) {
            std::cout << "[HAUPTSITZ] Die Matrix wurde erfolgreich erstellt und gespeichert in '" << file_path << "'.\n";
        }
        return 0;
    }

    if (mode == "--check") {
        TimeBasedIFFBloomFilter outpost_system;
        if (!outpost_system.load_from_file(file_path)) {
            std::cerr << "[FEHLER] Datei '" << file_path << "' Nicht gefunden. Bitte zuerst ausführen. --init.\n";
            return 1;
        }

        if (!QUIET_MODE) {
            std::cout << "------------------------------------------------------------------\n"
                      << "                       ERGEBNISSE DER PRÜFUNG                        \n"
                      << "------------------------------------------------------------------\n";
        }

        for (const auto& key : target_keys) {
            bool is_friend = outpost_system.verify_key(key);

            if (QUIET_MODE) {
                std::cout << key << ":" << (is_friend ? "1" : "0") << "\n";
            } else {
                std::cout << "Schlüssel: [" << std::setw(25) << std::left << key << "] -> "
                          << (is_friend ? "ZUGRIFF GEWÄHRT (EIGEN)" : "ABBRECHEN (100% FREMD)"") << "\n";
            }
        }

        if (!QUIET_MODE) {
            std::cout << "------------------------------------------------------------------\n";
        }
        return 0;
    }

    print_help();
    return 1;
}

4. Kompilation


g++ -std=c++17 main.cpp -o iff_system -lssl -lcrypto
              

5. Beispiele für Stapelverarbeitung

Standardmäßig wird die Datei iff_matrix.bin im aktuellen Arbeitsverzeichnis erstellt, also im selben Terminalordner, von dem aus Sie den Befehl ./iff_system --init ... ausführen. Wenn Sie diese Binärdatei zur Überprüfung mittels --check auf einen anderen Rechner übertragen haben, legen Sie sie einfach in denselben Ordner, aus dem Sie das Dienstprogramm auf dem Zielgerät ausführen werden!


./iff_system -h
./iff_system --init -k MY-KEY-A MY-KEY-B
./iff_system --check -k MY-KEY-A SPY-KEY MY-KEY-B UNKNOWN-DEVICE
./iff_system --check -f suspects.txt
./iff_system --check -f suspects.txt -q

Sehr wichtiger Hinweis:

  • Nur für friedliche Zwecke verwenden !
  • Nur für friedliche Zwecke verwenden !!
  • Nur für friedliche Zwecke verwenden !!!