Unzureichender Schutz physischer Seitenkanale

Beschreibung

Unzureichender Schutz physischer Seitenkanale tritt auf, wenn ein Gerät keine angemessenen Schutzmechanismen besitzt, um zu verhindern, dass physische Seitenkanale sensible Informationen durch Muster in physisch beobachtbaren Phänomenen wie Variationen im Stromverbrauch, elektromagnetischen Emissionen (EME) oder akustischen Emissionen preisgeben. Angreifer mit physischem Zugang oder in der Nähe können diese physischen Phänomene überwachen und messen, um Muster zu erkennen und Geheimnisse zu extrahieren. Wenn der Angreifer den Hardware-Betrieb überwachen und dessen Datenverarbeitung mit Strom-, EME- und Akustikmessungen korrelieren kann, könnte er in der Lage sein, geheime Schlüssel und Daten wiederherzustellen.

Risiko

Physische Seitenkanale haben schwerwiegende Sicherheitsauswirkungen. Kryptographische Schlüssel durch Stromanalyse extrahierbar. Elektromagnetische Emissionen enthüllen Verarbeitungsmuster. Akustische Analyse kann Geheimnisse extrahieren. PIN-Codes über Timing wiederherstellbar. Private Schlüssel kompromittiert. Authentifizierung umgangen. Alle Geheimnisse bei physischer Nähe gefährdet. Besonders schwerwiegend für kryptographische Implementierungen und Sicherheitsfunktionen.

Lösung

Wenden Sie Blinding- oder Masking-Techniken auf kryptographische Algorithmus-Implementierungen während der Architektur- und Designphase an. Fügen Sie Abschirmung oder manipulationssichere Schutzmaßnahmen während der Implementierung hinzu, um die Messung zu erschweren. Verwenden Sie Constant-Time-Implementierungen. Fügen Sie zufällige Verzögerungen und Operationsreihenfolgen hinzu. Implementieren Sie Stromfilterung und EMI-Abschirmung. Verwenden Sie Physically Unclonable Functions (PUFs) für Schlüsselspeicherung.

Häufige Auswirkungen

AuswirkungDetails
VertraulichkeitScope: Vertraulichkeit

Geheime Schlüssel und sensible Daten durch physische Beobachtung extrahierbar.

Beispielcode und Lösung

Verwundbarer Code

// VERWUNDBAR: RSA mit Strom-Seitenkanal-Leckage

module vulnerable_rsa_modexp (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        start,
    input  wire [2047:0] base,      // Zu verschlüsselnde/entschlüsselnde Nachricht
    input  wire [2047:0] exponent,  // Privater Schlüssel (geheim!)
    input  wire [2047:0] modulus,
    output reg  [2047:0] result,
    output reg         done
);

    reg [2047:0] temp_result;
    reg [2047:0] temp_base;
    reg [2047:0] exponent_reg;
    reg [11:0]   bit_counter;
    reg          busy;

    localparam IDLE = 2'b00;
    localparam SQUARE = 2'b01;
    localparam MULTIPLY = 2'b10;
    localparam DONE_STATE = 2'b11;

    reg [1:0] state;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            done <= 1'b0;
            result <= 2048'b0;
        end else begin
            case (state)
                IDLE: begin
                    if (start) begin
                        temp_result <= 2048'b1;
                        temp_base <= base;
                        exponent_reg <= exponent;
                        bit_counter <= 12'd2047;
                        state <= SQUARE;
                    end
                end

                SQUARE: begin
                    // Immer quadrieren
                    temp_base <= mod_multiply(temp_base, temp_base, modulus);
                    state <= MULTIPLY;
                end

                MULTIPLY: begin
                    // VERWUNDBAR: Bedingte Multiplikation basierend auf Exponent-Bit
                    // Dies erzeugt unterschiedliche Stromverbrauchsmuster
                    // die die geheimen Exponent-Bits enthüllen!
                    if (exponent_reg[bit_counter]) begin
                        // Stromspitze wenn Bit 1 ist
                        temp_result <= mod_multiply(temp_result, temp_base, modulus);
                    end
                    // Keine Operation wenn Bit 0 ist - erkennbar!

                    if (bit_counter == 0) begin
                        state <= DONE_STATE;
                    end else begin
                        bit_counter <= bit_counter - 1;
                        state <= SQUARE;
                    end
                end

                DONE_STATE: begin
                    result <= temp_result;
                    done <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

    // Angriff: Stromverbrauch überwachen
    // - Hoher Strom während MULTIPLY wenn Exponent-Bit = 1
    // - Niedriger Strom (keine Multiplikation) wenn Exponent-Bit = 0
    // - Angreifer kann gesamten privaten Exponenten wiederherstellen

endmodule

// VERWUNDBAR: PIN-Vergleich mit Timing-Seitenkanal
module vulnerable_pin_check (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] entered_pin,
    input  wire        check_enable,
    output reg         pin_correct,
    output reg         check_done
);

    // Geheime PIN in Register gespeichert
    reg [31:0] stored_pin;

    reg [2:0] byte_counter;
    reg checking;

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            pin_correct <= 1'b0;
            check_done <= 1'b0;
            byte_counter <= 3'b0;
            checking <= 1'b0;
        end else begin
            if (check_enable && !checking) begin
                checking <= 1'b1;
                byte_counter <= 3'b0;
                pin_correct <= 1'b1;  // Als korrekt annehmen bis Nichtübereinstimmung
            end

            if (checking) begin
                // VERWUNDBAR: Frühes Beenden bei Nichtübereinstimmung
                // Unterschiedliche Zyklusanzahl enthüllt korrekte Ziffernanzahl
                case (byte_counter)
                    3'd0: begin
                        if (entered_pin[7:0] != stored_pin[7:0]) begin
                            pin_correct <= 1'b0;
                            check_done <= 1'b1;   // VERWUNDBAR: Frühes Beenden
                            checking <= 1'b0;
                        end else begin
                            byte_counter <= 3'd1;
                        end
                    end

                    3'd1: begin
                        if (entered_pin[15:8] != stored_pin[15:8]) begin
                            pin_correct <= 1'b0;
                            check_done <= 1'b1;   // VERWUNDBAR: Frühes Beenden
                            checking <= 1'b0;
                        end else begin
                            byte_counter <= 3'd2;
                        end
                    end

                    // ... weiter für andere Bytes

                    3'd3: begin
                        if (entered_pin[31:24] != stored_pin[31:24]) begin
                            pin_correct <= 1'b0;
                        end
                        check_done <= 1'b1;
                        checking <= 1'b0;
                    end
                endcase
            end
        end
    end

    // Angriff: Zyklen bis check_done zählen
    // - 1 Zyklus: erste Ziffer falsch
    // - 2 Zyklen: zweite Ziffer falsch (erste korrekt!)
    // - usw.
    // Angreifer kann eine Ziffer nach der anderen brute-forcen

endmodule
// VERWUNDBAR: AES mit Cache-Timing-Seitenkanal

#include <stdint.h>

// VERWUNDBAR: Tabellenbasiertes AES mit Cache-Timing-Leckage
static const uint8_t sbox[256] = {
    // S-Box Lookup-Tabelle
    0x63, 0x7c, 0x77, 0x7b, /* ... Rest der S-Box ... */
};

// VERWUNDBAR: Tabellen-Lookups haben variables Timing
// Cache-Hits sind schneller als Cache-Misses
// Angreifer kann bestimmen, auf welche Tabelleneinträge zugegriffen wurde
uint8_t vulnerable_sbox_lookup(uint8_t input) {
    // VERWUNDBAR: Zugriffszeit hängt vom Cache-Zustand ab
    return sbox[input];
}

void vulnerable_aes_encrypt(const uint8_t* plaintext,
                            const uint8_t* key,
                            uint8_t* ciphertext) {
    uint8_t state[16];

    // Klartext in State kopieren
    for (int i = 0; i < 16; i++) {
        state[i] = plaintext[i] ^ key[i];
    }

    // VERWUNDBAR: S-Box-Lookups lecken Cache-Timing
    for (int round = 0; round < 10; round++) {
        for (int i = 0; i < 16; i++) {
            // Cache-Miss = langsam, Cache-Hit = schnell
            // Angreifer kann state[i]-Werte bestimmen
            state[i] = vulnerable_sbox_lookup(state[i]);
        }
        // MixColumns, ShiftRows, AddRoundKey...
    }

    // State in Ciphertext kopieren
    for (int i = 0; i < 16; i++) {
        ciphertext[i] = state[i];
    }
}

Sichere Lösung

// SICHER: RSA mit Constant-Time-Operation (maskiert)

module secure_rsa_modexp (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        start,
    input  wire [2047:0] base,
    input  wire [2047:0] exponent,
    input  wire [2047:0] modulus,
    output reg  [2047:0] result,
    output reg         done
);

    reg [2047:0] temp_result;
    reg [2047:0] temp_base;
    reg [2047:0] exponent_reg;
    reg [11:0]   bit_counter;

    // SICHER: Masking-Register für Dummy-Operationen
    reg [2047:0] mask_reg;

    localparam IDLE = 2'b00;
    localparam SQUARE = 2'b01;
    localparam MULTIPLY = 2'b10;
    localparam DONE_STATE = 2'b11;

    reg [1:0] state;

    // SICHER: Immer Multiplikation durchführen, Ergebnis verwerfen wenn nicht benötigt
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            state <= IDLE;
            done <= 1'b0;
            result <= 2048'b0;
            mask_reg <= 2048'b0;
        end else begin
            case (state)
                IDLE: begin
                    if (start) begin
                        temp_result <= 2048'b1;
                        temp_base <= base;
                        exponent_reg <= exponent;
                        bit_counter <= 12'd2047;
                        state <= SQUARE;
                    end
                end

                SQUARE: begin
                    temp_base <= mod_multiply(temp_base, temp_base, modulus);
                    state <= MULTIPLY;
                end

                MULTIPLY: begin
                    // SICHER: Immer Multiplikation durchführen (konstanter Strom)
                    reg [2047:0] multiply_result;
                    multiply_result = mod_multiply(temp_result, temp_base, modulus);

                    // SICHER: Masking verwenden, um Ergebnis zu wählen
                    // Beide Zweige führen gleiche Operationen durch
                    if (exponent_reg[bit_counter]) begin
                        temp_result <= multiply_result;  // Ergebnis behalten
                        mask_reg <= temp_result;         // Dummy-Speicherung
                    end else begin
                        temp_result <= temp_result;      // Alten Wert behalten
                        mask_reg <= multiply_result;     // Zur Maske verwerfen
                    end

                    // SICHER: Gleiche Anzahl Operationen unabhängig vom Bit-Wert
                    // Stromverbrauch ist konstant

                    if (bit_counter == 0) begin
                        state <= DONE_STATE;
                    end else begin
                        bit_counter <= bit_counter - 1;
                        state <= SQUARE;
                    end
                end

                DONE_STATE: begin
                    result <= temp_result;
                    done <= 1'b1;
                    state <= IDLE;
                end
            endcase
        end
    end

endmodule

// SICHER: PIN-Vergleich mit Constant-Time-Operation
module secure_pin_check (
    input  wire        clk,
    input  wire        rst_n,
    input  wire [31:0] entered_pin,
    input  wire        check_enable,
    output reg         pin_correct,
    output reg         check_done
);

    reg [31:0] stored_pin;
    reg [2:0] byte_counter;
    reg checking;
    reg [3:0] mismatch_flags;  // SICHER: Alle Nichtübereinstimmungen sammeln

    always @(posedge clk or negedge rst_n) begin
        if (!rst_n) begin
            pin_correct <= 1'b0;
            check_done <= 1'b0;
            byte_counter <= 3'b0;
            checking <= 1'b0;
            mismatch_flags <= 4'b0;
        end else begin
            if (check_enable && !checking) begin
                checking <= 1'b1;
                byte_counter <= 3'b0;
                mismatch_flags <= 4'b0;  // SICHER: Flags zurücksetzen
            end

            if (checking) begin
                // SICHER: ALLE Bytes prüfen, kein frühes Beenden
                case (byte_counter)
                    3'd0: begin
                        // SICHER: Nur Nichtübereinstimmung aufzeichnen, nicht beenden
                        mismatch_flags[0] <= (entered_pin[7:0] != stored_pin[7:0]);
                        byte_counter <= 3'd1;
                    end

                    3'd1: begin
                        mismatch_flags[1] <= (entered_pin[15:8] != stored_pin[15:8]);
                        byte_counter <= 3'd2;
                    end

                    3'd2: begin
                        mismatch_flags[2] <= (entered_pin[23:16] != stored_pin[23:16]);
                        byte_counter <= 3'd3;
                    end

                    3'd3: begin
                        mismatch_flags[3] <= (entered_pin[31:24] != stored_pin[31:24]);
                        byte_counter <= 3'd4;
                    end

                    3'd4: begin
                        // SICHER: Ergebnis erst nach Prüfung ALLER Bytes melden
                        // Gleiches Timing unabhängig davon, welches Byte falsch ist
                        pin_correct <= (mismatch_flags == 4'b0);
                        check_done <= 1'b1;
                        checking <= 1'b0;
                    end
                endcase
            end
        end
    end

    // SICHER: Dauert immer genau 5 Zyklen
    // Angreifer kann nicht bestimmen, welche Ziffer falsch ist

endmodule
// SICHER: AES mit Seitenkanal-Gegenmaßnahmen

#include <stdint.h>
#include <string.h>

// SICHER: Bit-Sliced S-Box-Implementierung
// Keine Tabellen-Lookups, konstante Zeit
static void secure_sbox_bitslice(uint8_t* state) {
    // SICHER: S-Box nur mit booleschen Operationen berechnen
    // Keine Speicherzugriffe, die durch Cache lecken könnten
    // Implementierung verwendet algebraische Zerlegung der AES-S-Box

    for (int i = 0; i < 16; i++) {
        uint8_t x = state[i];

        // SICHER: Constant-Time S-Box-Berechnung
        // Verwendet GF(2^8)-Inversion über boolesche Operationen
        uint8_t result = compute_sbox_algebraic(x);

        state[i] = result;
    }
}

// SICHER: Constant-Time-Vergleich
int secure_memcmp(const void* a, const void* b, size_t len) {
    const volatile uint8_t* aa = a;
    const volatile uint8_t* bb = b;
    uint8_t diff = 0;

    // SICHER: Immer alle Bytes vergleichen
    // Kein frühes Beenden
    for (size_t i = 0; i < len; i++) {
        diff |= aa[i] ^ bb[i];
    }

    // SICHER: Rückgabewert enthüllt nicht Position des Unterschieds
    return diff;
}

// SICHER: AES mit Masking
void secure_aes_encrypt(const uint8_t* plaintext,
                        const uint8_t* key,
                        uint8_t* ciphertext) {
    uint8_t state[16];
    uint8_t mask[16];

    // SICHER: Zufällige Maske generieren
    get_random_bytes(mask, 16);

    // SICHER: Maske auf Klartext anwenden
    for (int i = 0; i < 16; i++) {
        state[i] = plaintext[i] ^ key[i] ^ mask[i];
    }

    // SICHER: Maskierte Berechnung
    // Operationen auf maskierten Daten durchgeführt
    // Maske verhindert Korrelation zwischen Strom und tatsächlichen Werten
    for (int round = 0; round < 10; round++) {
        secure_sbox_bitslice(state);  // SICHER: Keine Tabellen-Lookups
        // MixColumns, ShiftRows, AddRoundKey auf maskiertem State
    }

    // SICHER: Maske am Ende entfernen
    for (int i = 0; i < 16; i++) {
        ciphertext[i] = state[i] ^ final_mask[i];
    }

    // SICHER: Sensible Daten aus Speicher löschen
    secure_memzero(mask, 16);
    secure_memzero(state, 16);
}

// SICHER: Sicheres Speicherlöschen (wird nicht wegoptimiert)
void secure_memzero(void* ptr, size_t len) {
    volatile uint8_t* p = ptr;
    while (len--) {
        *p++ = 0;
    }
}

CVE-Beispiele

  • CVE-2022-35888: Prozessor-Strom-Seitenkanale lecken Geheimnisse.
  • CVE-2021-3011: Google Titan Security Key anfällig für elektromagnetischen Seitenkanal.
  • CVE-2019-14353: Krypto-Hardware-Wallet USB-Stromverbrauch enthüllt PIN/Passwort.
  • CVE-2013-4576: Akustische Kryptoanalyse extrahiert RSA-Schlüssel.
  • CVE-2020-28368: Guest-AES-Schlüssel-Wiederherstellung über Stromüberwachungs-Interface.

Verwandte CWEs

  • CWE-203: Observable Discrepancy (übergeordnet)
  • CWE-1255: Comparison Logic is Vulnerable to Power Side-Channel Attacks (untergeordnet)
  • CWE-1388: Physical Access Issues and Concerns (Kategorie)
  • CWE-208: Observable Timing Discrepancy (verwandt)

Referenzen

  1. MITRE Corporation. "CWE-1300: Improper Protection of Physical Side Channels." https://cwe.mitre.org/data/definitions/1300.html
  2. ISO 17825: Information technology — Security techniques — Testing methods for the mitigation of non-invasive attack classes against cryptographic modules
  3. Kocher, P., et al. "Differential Power Analysis"