Signature Verification in Embedded Systems: Design and Principle Analysis (C/C++ Implementation)

In embedded systems, the integrity and legitimacy of firmware are crucial. For example, if the bootloader of a smart device loads tampered firmware, it may lead to system crashes or even the injection of malicious code. The “vboot” system of Chrome OS provides a lightweight and efficient signature verification scheme, the core logic of which can be extracted and applied to various embedded scenarios. This article will explore the design ideas, implementation principles, and related technical knowledge points, analyzing this signature verification mechanism based on RSA and SHA256.

1. Core Functionality: Verifying “Who” and “What Data” Has Not Been Tampered With

The core goal of this code is to verify the integrity and legitimate source of a piece of data (such as firmware). Specifically, it aims to answer two questions:

  1. Is this data consistent with the original data at the time of signing (not tampered with)?
  2. Is this data indeed signed by the entity holding the corresponding private key (legitimate source)?

In embedded scenarios, the most typical application is “firmware update verification”: before a device loads new firmware, it checks whether the firmware has been tampered with and whether it comes from a trusted vendor through this mechanism, allowing execution only if the verification passes, thus preventing malicious firmware attacks.

2. Design Ideas: Layered Verification and Lightweight Adaptation

To efficiently implement secure verification in resource-constrained embedded systems (such as ARM microcontrollers), this scheme adopts a “layered design” and “lightweight adaptation” approach, which can be broken down into three core considerations:

1. Layered Verification: Hash First, Then Sign

Directly encrypting and signing the original data (which may be large, such as several MB of firmware) would result in excessive computational load, making it unsuitable for embedded devices. Therefore, the scheme employs a “hash + sign” layered logic:

  • Step 1: Use a hash algorithm (SHA256) to compute a fixed-length hash value (256 bits) for the original data. The hash value can be understood as the “digital fingerprint” of the data; even a slight modification to the original data will result in a completely different hash value.
  • Step 2: Encrypt the hash value with the private key (i.e., “sign” it), and during verification, use the public key to decrypt the signature to obtain the hash value, which is then compared with the hash value of the original data.

This design transforms the signature problem of large files into the processing of short hash values, significantly reducing computational and storage overhead, thus adapting to the resource constraints of embedded systems.

2. Structured Verification: Ensuring Data Format Compliance

To avoid parsing errors or maliciously constructed signature/key data, the scheme introduces structured verification logic (corresponding to the <span>vb21_packed_key</span> and <span>vb21_signature</span> structures in the code). These structures include:

  • Magic number (e.g., <span>VB21_MAGIC_PACKED_KEY</span>): Quickly verifies whether the data is in the expected format, avoiding parsing errors.
  • Algorithm identifiers (e.g., <span>sig_alg</span>, <span>hash_alg</span>): Ensures that the signature and hash algorithms match the verification logic (e.g., RSA corresponds to SHA256).
  • Offset and length information: Accurately locates the key and signature in the binary data, preventing out-of-bounds access.

This structured design acts as a “format firewall” for the data, ensuring that the data input into the core verification logic is compliant.

3. Portability: Adapting to Embedded Hardware Characteristics

Embedded systems typically have limited memory and computational power (e.g., no hardware encryption acceleration). The scheme adapts in the following ways:

  • Memory reuse: The <span>rsa_workbuf</span> in the code serves as a working buffer, avoiding frequent dynamic memory allocation and reducing memory fragmentation.
  • Lightweight algorithm implementation: RSA (2048 bits) and SHA256 are chosen, both of which have relatively low complexity in software implementation, and 2048-bit RSA provides sufficient security without imposing excessive computational burdens (compared to 4096 bits, it is more suitable for embedded systems).

3. Implementation Principles: The Complete Link from Hashing to Signature Verification

struct sha256_ctx {
 uint32_t h[8];
 uint32_t tot_len;
 uint32_t len;
 uint8_t block[2 * SHA256_BLOCK_SIZE];
 uint8_t buf[SHA256_DIGEST_SIZE];  
};

void SHA256_init(struct sha256_ctx *ctx);
void SHA256_update(struct sha256_ctx *ctx, const uint8_t *data, uint32_t len);
uint8_t *SHA256_final(struct sha256_ctx *ctx);

void hmac_SHA256(uint8_t *output, const uint8_t *key, const int key_len,
   const uint8_t *message, const int message_len);
int rsa_check_signature(
 const uint8_t *rwdata, 
 unsigned int rwlen, 
 const struct rsa_public_key *key, 
 const uint8_t *sig 
);

int vblock_check_signature(
 const uint8_t *rwdata,
 const struct vb21_packed_key *vb21_key,
 const struct vb21_signature *vb21_sig
);
...
struct rsa_public_key {
 uint32_t size;
 uint32_t n0inv;           
 uint32_t n[RSANUMWORDS]; 
 uint32_t rr[RSANUMWORDS];
};

int rsa_verify(const struct rsa_public_key *key,
        const uint8_t *signature,
        const uint8_t *sha,
        uint32_t *workbuf32);
...

int main(int argc, char *argv[])
{
...
    FILE *f = fopen(argv[1], "r");
    fseek(f, 0, SEEK_END);
    rwlen = ftell(f);
    rewind(f);

    rwdata = (uint8_t *)malloc(rwlen);
    if (1 != fread(rwdata, rwlen, 1, f)) {
        printf("Couldn't load %s\n", argv[1]);
        return -1;
    }
    fclose(f);

    status = vblock_check_signature(rwdata, (const struct vb21_packed_key *)key_vbpubk2, (const struct vb21_signature *)signature);
    if (status)
      printf("Signature matches!\n");
    else
      printf("Signature does not match :-(\n");
    return status;
}

The core process of the entire verification mechanism can be divided into two major steps: “data hashing calculation” and “RSA signature verification”, which are interlinked to ensure security.

1. Hash Calculation: Generating the “Digital Fingerprint” of Data

The hash function (in this case, SHA256) is the foundation for verifying data integrity, and its core characteristics are:

  • Deterministic: The same input will always produce the same output.
  • Collision-resistant: It is difficult to find two different inputs that produce the same output.
  • One-way: It is impossible to reverse-engineer the input from the output.

In the code, <span>SHA256_init</span>, <span>SHA256_update</span>, and <span>SHA256_final</span> sequentially complete the hash initialization, incremental calculation (suitable for large files), and final hash value generation. For example, after calculating the hash of the firmware data <span>rwdata</span>, the resulting <span>hash</span> is the unique “fingerprint” of this firmware.

2. RSA Signature Verification: Confirming Source Legitimacy

RSA is an asymmetric encryption algorithm based on the characteristics of “public key encryption, private key decryption” or “private key signing, public key verification”. In the signature verification scenario:

  • The signer (e.g., the vendor) encrypts the hash value of the data with the private key to generate a “signature” (<span>sig</span>).
  • The verifier (e.g., the device) decrypts the signature with the corresponding public key to obtain a hash value, which is then compared with the hash value calculated from the original data.

If both match, it indicates:

  • The data has not been tampered with (hash values match);
  • The signature indeed comes from the entity holding the private key (only the corresponding public key can decrypt).

The <span>rsa_verify</span> function in the code implements this process: it uses the public key <span>key</span> to decrypt the signature <span>sig</span>, compares the decrypted hash value with the hash value generated by <span>SHA256_final</span>, and ultimately returns the verification result.

4. Related Knowledge Points: Building the Technical Foundation for Secure Verification

This scheme involves several core knowledge points in information security and embedded systems, understanding them helps to grasp the essence of the design.

1. Asymmetric Encryption and RSA

  • Core Idea: Achieving encryption/signing through a pair of mathematically related keys (public key, private key). The public key can be disclosed, while the private key must be kept secret.
  • Security Basis: Based on the difficulty of the large integer factorization problem (the computational complexity of factoring large prime products into their original primes is extremely high). 2048-bit RSA is currently considered resistant to conventional attacks and is a mainstream choice in embedded scenarios.

2. Hash Functions and SHA256

  • Function: Maps data of arbitrary length to a fixed length (256 bits) output, used to verify data integrity.
  • Advantages: Compared to MD5 (which has been compromised) and SHA1 (which lacks security), SHA256 is still widely recognized, and its computation process is suitable for software optimization (such as loop unrolling), adapting to embedded CPUs.

3. Trusted Boot

  • Concept: When a device starts, it begins from the lowest level of trusted code (such as hardware-embedded ROM) and verifies the signature of the next level of code layer by layer, ensuring that every piece of software loaded is trusted.
  • Relation: The scheme in this article is a key link in trusted boot—verifying firmware signatures to prevent malicious code from being loaded during the boot process. The vboot system of Chrome OS ensures the security of the device boot chain through a similar mechanism.

4. Security Features of Embedded Systems

  • Resource Constraints: Embedded devices typically have limited memory (in KB) and computational power, making it impossible to run complex encryption algorithms (such as AES-256-GCM or a full implementation of 4096-bit RSA).
  • Security Needs: Must resist physical attacks (such as firmware extraction) and communication attacks (such as man-in-the-middle tampering with update packages), thus signature verification must be lightweight and tamper-resistant (for example, the public key must be embedded in read-only storage).

5. Conclusion: Design Insights for Embedded Security Verification

This signature verification scheme extracted from Chrome OS vboot provides a “sufficient and efficient” security verification template for embedded systems. Its design ideas can be summarized as follows:

  1. Use “hash + asymmetric encryption” to balance security and efficiency, adapting to embedded resource constraints;
  2. Enhance robustness through structured data verification (magic numbers, algorithm identifiers) to prevent malicious input;
  3. Ensure security based on mature algorithms (SHA256, RSA) while simplifying implementation to adapt to hardware.

In practical applications, developers can adjust details according to the scenario (such as replacing RSA with ECC to further reduce key length), but the core idea—”first verify data integrity, then confirm source legitimacy”—is a universal principle of embedded security verification. Whether for firmware updates in smart appliances or program verification in industrial controllers, this design philosophy can provide a solid foundation for system security.

Welcome to follow the WeChat official account 【Programmer Coding

Leave a Comment