Improper Protection of Physical Side Channels

Description

Improper Protection of Physical Side Channels occurs when a device lacks adequate protection mechanisms to prevent physical side channels from exposing sensitive information due to patterns in physically observable phenomena such as variations in power consumption, electromagnetic emissions (EME), or acoustic emissions. Adversaries with physical access or proximity can monitor and measure these physical phenomena to detect patterns and extract secrets. If the adversary can monitor hardware operation and correlate its data processing with power, EME, and acoustic measurements, they might be able to recover secret keys and data.

Risk

Physical side channels have severe security implications. Cryptographic keys extractable through power analysis. Electromagnetic emissions reveal processing patterns. Acoustic analysis can extract secrets. PIN codes recoverable from timing. Private keys compromised. Authentication bypassed. All secrets at risk from physical proximity. Particularly severe for cryptographic implementations and security features.

Solution

Apply blinding or masking techniques to cryptographic algorithm implementations during architecture and design phase. Add shielding or tamper-resistant protections during implementation to increase measurement difficulty. Use constant-time implementations. Add random delays and operation ordering. Implement power filtering and EMI shielding. Use physically unclonable functions (PUFs) for key storage.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Secret keys and sensitive data extractable through physical observation.

Example Code

Vulnerable Code

// Vulnerable: RSA with power side-channel leakage

module vulnerable_rsa_modexp (
    input  wire        clk,
    input  wire        rst_n,
    input  wire        start,
    input  wire [2047:0] base,      // Message to encrypt/decrypt
    input  wire [2047:0] exponent,  // Private key (secret!)
    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
                    // Always square
                    temp_base <= mod_multiply(temp_base, temp_base, modulus);
                    state <= MULTIPLY;
                end

                MULTIPLY: begin
                    // VULNERABLE: Conditional multiply based on exponent bit
                    // This creates different power consumption patterns
                    // revealing the secret exponent bits!
                    if (exponent_reg[bit_counter]) begin
                        // Power spike when bit is 1
                        temp_result <= mod_multiply(temp_result, temp_base, modulus);
                    end
                    // No operation when bit is 0 - detectable!

                    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

    // Attack: Monitor power consumption
    // - High power during MULTIPLY when exponent bit = 1
    // - Low power (no multiply) when exponent bit = 0
    // - Attacker can recover entire private exponent

endmodule

// Vulnerable: PIN comparison with timing side-channel
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
);

    // Secret PIN stored in register
    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;  // Assume correct until mismatch
            end

            if (checking) begin
                // VULNERABLE: Early termination on mismatch
                // Different number of cycles reveals correct digit count
                case (byte_counter)
                    3'd0: begin
                        if (entered_pin[7:0] != stored_pin[7:0]) begin
                            pin_correct <= 1'b0;
                            check_done <= 1'b1;   // VULNERABLE: Early exit
                            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;   // VULNERABLE: Early exit
                            checking <= 1'b0;
                        end else begin
                            byte_counter <= 3'd2;
                        end
                    end

                    // ... continues for other 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

    // Attack: Count cycles until check_done
    // - 1 cycle: first digit wrong
    // - 2 cycles: second digit wrong (first correct!)
    // - etc.
    // Attacker can brute-force one digit at a time

endmodule
// Vulnerable: AES with cache timing side-channel

#include <stdint.h>

// VULNERABLE: Table-based AES with cache timing leakage
static const uint8_t sbox[256] = {
    // S-box lookup table
    0x63, 0x7c, 0x77, 0x7b, /* ... rest of S-box ... */
};

// VULNERABLE: Table lookups have variable timing
// Cache hits are faster than cache misses
// Attacker can determine which table entries were accessed
uint8_t vulnerable_sbox_lookup(uint8_t input) {
    // VULNERABLE: Access time depends on cache state
    return sbox[input];
}

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

    // Copy plaintext to state
    for (int i = 0; i < 16; i++) {
        state[i] = plaintext[i] ^ key[i];
    }

    // VULNERABLE: S-box lookups leak cache timing
    for (int round = 0; round < 10; round++) {
        for (int i = 0; i < 16; i++) {
            // Cache miss = slow, cache hit = fast
            // Attacker can determine state[i] values
            state[i] = vulnerable_sbox_lookup(state[i]);
        }
        // MixColumns, ShiftRows, AddRoundKey...
    }

    // Copy state to ciphertext
    for (int i = 0; i < 16; i++) {
        ciphertext[i] = state[i];
    }
}

Fixed Code

// Fixed: RSA with constant-time operation (masked)

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;

    // FIXED: Masking register for dummy operations
    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;

    // FIXED: Always perform multiply, discard result when not needed
    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
                    // FIXED: Always perform multiply (constant power)
                    reg [2047:0] multiply_result;
                    multiply_result = mod_multiply(temp_result, temp_base, modulus);

                    // FIXED: Use masking to select result
                    // Both branches perform same operations
                    if (exponent_reg[bit_counter]) begin
                        temp_result <= multiply_result;  // Keep result
                        mask_reg <= temp_result;         // Dummy store
                    end else begin
                        temp_result <= temp_result;      // Keep old value
                        mask_reg <= multiply_result;     // Discard to mask
                    end

                    // FIXED: Same number of operations regardless of bit value
                    // Power consumption is constant

                    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

// Fixed: PIN comparison with 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;  // FIXED: Accumulate all mismatches

    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;  // FIXED: Reset flags
            end

            if (checking) begin
                // FIXED: Check ALL bytes, no early termination
                case (byte_counter)
                    3'd0: begin
                        // FIXED: Just record mismatch, don't exit
                        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
                        // FIXED: Only report result after checking ALL bytes
                        // Same timing regardless of which byte is wrong
                        pin_correct <= (mismatch_flags == 4'b0);
                        check_done <= 1'b1;
                        checking <= 1'b0;
                    end
                endcase
            end
        end
    end

    // FIXED: Always takes exactly 5 cycles to complete
    // Attacker cannot determine which digit is wrong

endmodule
// Fixed: AES with side-channel countermeasures

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

// FIXED: Bit-sliced S-box implementation
// No table lookups, constant time
static void secure_sbox_bitslice(uint8_t* state) {
    // FIXED: Compute S-box using boolean operations only
    // No memory accesses that could leak through cache
    // Implementation uses algebraic decomposition of AES S-box

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

        // FIXED: Constant-time S-box computation
        // Uses GF(2^8) inversion via boolean operations
        uint8_t result = compute_sbox_algebraic(x);

        state[i] = result;
    }
}

// FIXED: Constant-time comparison
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;

    // FIXED: Always compare all bytes
    // No early termination
    for (size_t i = 0; i < len; i++) {
        diff |= aa[i] ^ bb[i];
    }

    // FIXED: Return value doesn't reveal position of difference
    return diff;
}

// FIXED: AES with masking
void secure_aes_encrypt(const uint8_t* plaintext,
                        const uint8_t* key,
                        uint8_t* ciphertext) {
    uint8_t state[16];
    uint8_t mask[16];

    // FIXED: Generate random mask
    get_random_bytes(mask, 16);

    // FIXED: Apply mask to plaintext
    for (int i = 0; i < 16; i++) {
        state[i] = plaintext[i] ^ key[i] ^ mask[i];
    }

    // FIXED: Masked computation
    // Operations performed on masked data
    // Mask prevents correlation between power and actual values
    for (int round = 0; round < 10; round++) {
        secure_sbox_bitslice(state);  // FIXED: No table lookups
        // MixColumns, ShiftRows, AddRoundKey on masked state
    }

    // FIXED: Remove mask at end
    for (int i = 0; i < 16; i++) {
        ciphertext[i] = state[i] ^ final_mask[i];
    }

    // FIXED: Clear sensitive data from memory
    secure_memzero(mask, 16);
    secure_memzero(state, 16);
}

// FIXED: Secure memory clear (not optimized away)
void secure_memzero(void* ptr, size_t len) {
    volatile uint8_t* p = ptr;
    while (len--) {
        *p++ = 0;
    }
}

CVE Examples

  • CVE-2022-35888: Processor power side-channels leak secrets.
  • CVE-2021-3011: Google Titan security key vulnerable to electromagnetic side-channel.
  • CVE-2019-14353: Crypto hardware wallet USB power consumption reveals PIN/password.
  • CVE-2013-4576: Acoustic cryptanalysis extracts RSA keys.
  • CVE-2020-28368: Guest AES key recovery via power monitoring interface.

  • CWE-203: Observable Discrepancy (parent)
  • CWE-1255: Comparison Logic is Vulnerable to Power Side-Channel Attacks (child)
  • CWE-1388: Physical Access Issues and Concerns (category)
  • CWE-208: Observable Timing Discrepancy (related)

References

  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"