OpenSSL 簽名驗證詳解:PKCS7* p7、cafile 與 RSA 驗簽實現

OpenSSL 簽名驗證詳解:PKCS7* p7、cafile 與 RSA 驗簽實現

摘要

本文深入剖析 OpenSSL 中 PKCS7* p7 數據結構和 cafile 的作用及相互關系,詳細講解基于 OpenSSL 的 RSA 驗簽字符串的 C 語言實現,涵蓋簽名解析、證書加載、驗證流程及關鍵要點,助力開發者掌握數字簽名驗證技術,確保數據完整性和來源可靠性。

一、PKCS7* p7 與 cafile 的關鍵作用

(一)PKCS7* p7:簽名數據的核心載體

  1. 結構與內容

    • PKCS#7(Public Key Cryptography Standards #7)標準涵蓋數字簽名、證書、數據加密及消息認證。PKCS7* p7 是處理 PKCS#7 格式數字簽名的關鍵數據結構,通過 d2i_PKCS7_bio() 函數將 DER 編碼的 PKCS#7 數據解析為該結構體。
    • p7 包含豐富的簽名信息,如簽名者信息、簽名算法、簽名數據及證書鏈等。在驗證簽名時,PKCS7_verify() 函數利用這些信息驗證簽名有效性、檢查證書鏈完整性,確保數據未被篡改。
  2. 缺失影響

    PKCS7* p7 = d2i_PKCS7_bio(signp7_mem, 0);
    if (p7 == NULL) {// 無法獲取簽名信息,驗證過程無法進行
    }
    
    • 若缺失 p7,將無法讀取簽名數據,整個驗證過程直接失敗,相當于沒有驗證對象。
  3. 釋放內存

    • 使用完畢后,需調用 PKCS7_free(p7); 釋放結構體,避免內存泄漏。

(二)cafile:信任鏈的根基

  1. 作用與使用

    • cafile 是包含根證書或中間證書的 CA(Certificate Authority,證書頒發機構)證書文件,用于建立信任鏈,驗證簽名者證書合法性,是數字簽名驗證的信任錨點。
    • 在代碼中,通過 X509_STORE_load_locations(store, cafile, NULL) 將 CA 證書加載到證書庫。PKCS7_verify() 函數利用加載的 CA 證書驗證簽名者證書,檢查證書鏈完整性,確認簽名者證書由可信 CA 簽發。
  2. 缺失影響

    if (!X509_STORE_load_locations(store, cafile, NULL)) {LOG_ERR("Load CA file %s fail\n", cafile);// 無法建立信任鏈,驗證過程無法完成return CRYPTO_FAIL;
    }
    
    • cafile 缺失或無效,無法驗證簽名者證書真實性,無法確認簽名者身份,相當于有簽名但無法確認其真實性,驗證過程無法完成。
  3. 兩者關系

    • p7 是驗證對象(要驗證什么),cafile 是驗證依據(如何驗證),二者缺一不可,只有結合才能完成完整的簽名驗證過程。

二、OpenSSL RSA 驗簽字符串的 C 語言實現

(一)完整代碼

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/evp.h>
#include <openssl/pem.h>
#include <openssl/err.h>
#include <openssl/bio.h>
#include <openssl/buffer.h>// 錯誤處理函數
void handle_openssl_error() {ERR_print_errors_fp(stderr);exit(EXIT_FAILURE);
}// Base64解碼函數
int base64_decode(const char *base64_data, unsigned char **decoded_data, size_t *decoded_len) {BIO *bio, *b64;int len = strlen(base64_data);b64 = BIO_new(BIO_f_base64());BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);bio = BIO_new_mem_buf((void*)base64_data, len);bio = BIO_push(b64, bio);*decoded_data = (unsigned char*)malloc(len);if (!*decoded_data) {BIO_free_all(bio);return -1;}*decoded_len = BIO_read(bio, *decoded_data, len);BIO_free_all(bio);return (*decoded_len > 0) ? 0 : -1;
}// RSA驗簽函數
int rsa_verify_string(const char *pubkey_path, const char *message,const char *base64_signature,const char *hash_alg) {EVP_MD_CTX *mdctx = NULL;EVP_PKEY *pubkey = NULL;FILE *pubkey_fp = NULL;unsigned char *signature = NULL;size_t sig_len = 0;int ret = -1;const EVP_MD *md = NULL;// 1. 根據算法名稱獲取哈希算法if (strcmp(hash_alg, "sha1") == 0) {md = EVP_sha1();} else if (strcmp(hash_alg, "sha256") == 0) {md = EVP_sha256();} else {fprintf(stderr, "Unsupported hash algorithm: %s\n", hash_alg);goto cleanup;}// 2. 加載公鑰pubkey_fp = fopen(pubkey_path, "r");if (!pubkey_fp) {fprintf(stderr, "Error opening public key file\n");goto cleanup;}pubkey = PEM_read_PUBKEY(pubkey_fp, NULL, NULL, NULL);if (!pubkey) {fprintf(stderr, "Error reading public key\n");goto cleanup;}// 3. Base64解碼簽名if (base64_decode(base64_signature, &signature, &sig_len) != 0) {fprintf(stderr, "Error decoding base64 signature\n");goto cleanup;}// 4. 初始化驗簽上下文mdctx = EVP_MD_CTX_new();if (!mdctx) {fprintf(stderr, "Error creating EVP_MD_CTX\n");goto cleanup;}if (EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pubkey) != 1) {fprintf(stderr, "Error initializing verification\n");goto cleanup;}// 5. 更新驗簽數據if (EVP_DigestVerifyUpdate(mdctx, message, strlen(message)) != 1) {fprintf(stderr, "Error updating verification data\n");goto cleanup;}// 6. 完成驗簽ret = EVP_DigestVerifyFinal(mdctx, signature, sig_len);if (ret == 1) {printf("Signature verification successful (%s)\n", hash_alg);} else if (ret == 0) {printf("Signature verification failed (%s)\n", hash_alg);} else {fprintf(stderr, "Error during verification\n");ret = -1;}cleanup:// 7. 清理資源if (mdctx) EVP_MD_CTX_free(mdctx);if (pubkey) EVP_PKEY_free(pubkey);if (pubkey_fp) fclose(pubkey_fp);if (signature) free(signature);return ret;
}int main(int argc, char *argv[]) {if (argc != 5) {printf("Usage: %s <public_key.pem> <message> <base64_signature> <sha1|sha256>\n", argv[0]);return 1;}// 初始化OpenSSLOpenSSL_add_all_algorithms();ERR_load_crypto_strings();int result = rsa_verify_string(argv[1], argv[2], argv[3], argv[4]);// 清理OpenSSLEVP_cleanup();ERR_free_strings();return (result != 1);
}

(二)代碼詳解

  1. 哈希算法選擇

    if (strcmp(hash_alg, "sha1") == 0) {md = EVP_sha1();
    } else if (strcmp(hash_alg, "sha256") == 0) {md = EVP_sha256();
    }
    
    • 根據傳入的哈希算法名稱(“sha1” 或 “sha256”),獲取對應的哈希算法結構體指針。也可使用 EVP_get_digestbyname("sha256") 動態獲取算法。
  2. 公鑰加載

    pubkey = PEM_read_PUBKEY(pubkey_fp, NULL, NULL, NULL);
    
    • 從 PEM 格式的公鑰文件中讀取公鑰,支持 RSA、DSA、ECDSA 等多種公鑰類型。
  3. Base64 解碼

    BIO *b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    
    • 利用 OpenSSL 的 BIO 接口進行 Base64 解碼,BIO_FLAGS_BASE64_NO_NL 標志表示不處理換行符。
  4. 驗簽流程

    EVP_DigestVerifyInit(mdctx, NULL, md, NULL, pubkey);
    EVP_DigestVerifyUpdate(mdctx, message, strlen(message));
    EVP_DigestVerifyFinal(mdctx, signature, sig_len);
    
    • EVP_DigestVerifyInit:初始化驗簽上下文,指定哈希算法和公鑰。
    • EVP_DigestVerifyUpdate:輸入待驗證的數據,可多次調用以處理大消息。
    • EVP_DigestVerifyFinal:完成驗簽并返回結果。
  5. 錯誤處理

    ERR_print_errors_fp(stderr);
    
    • OpenSSL 錯誤處理機制可輸出詳細錯誤信息,每個 OpenSSL 函數調用后都應檢查返回值。

(三)使用示例

  1. 編譯命令

    gcc rsa_verify.c -o rsa_verify -lssl -lcrypto
    
  2. 運行示例

    • 使用 SHA256 驗證:
      ./rsa_verify public_key.pem "message to verify" "base64_signature" sha256
      
    • 使用 SHA1 驗證:
      ./rsa_verify public_key.pem "message to verify" "base64_signature" sha1
      

(四)關鍵點說明

  1. 哈希算法選擇

    • SHA1 產生 160 位(20 字節)摘要,SHA256 產生 256 位(32 字節)摘要。現代應用推薦使用 SHA256,SHA1 已逐漸被淘汰。
  2. 簽名格式

    • RSA 簽名通常是 PKCS#1 v1.5 格式,簽名長度等于 RSA 密鑰長度(如 2048 位 = 256 字節)。
  3. 性能考慮

    • 對于大消息,可分塊調用 EVP_DigestVerifyUpdate。SHA256 計算比 SHA1 稍慢但更安全。
  4. 資源管理

    • 必須正確釋放所有 OpenSSL 對象,使用 goto cleanup 模式集中處理資源釋放。
  5. Base64 處理

    • 簽名通常以 Base64 編碼傳輸,驗簽前需要解碼為二進制格式。

通過上述內容,開發者可以全面了解 OpenSSL 中 PKCS7* p7 和 cafile 的作用、關系以及 RSA 驗簽的 C 語言實現細節,從而在實際項目中靈活應用數字簽名驗證技術,保障數據的安全性和完整性。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82959.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82959.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82959.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

9:OpenCV—模板匹配

模版匹配 1、模板匹配概念 模板匹配是一項在一副圖像中尋找與另一幅模板圖像最匹配&#xff08;相似&#xff09;部分的技術。模板匹配不是基于直方圖的&#xff0c;而是通過在輸入圖像上滑動圖像塊&#xff08;模板&#xff09;同時對比相似度&#xff0c;來對模板和輸入圖像…

Composer 常規操作說明與問題處理

目錄 一、 Composer 簡介&#xff0c;安裝二、全局配置三、項目配置&#xff08;composer.json&#xff09;3.1 composer.json 文件1. 基礎字段信息2. **require&#xff08;生產環境依賴&#xff09;**3. **require-dev&#xff08;開發環境依賴&#xff09;** 3.2 composer.l…

Spring Boot 3.0與Java 17:企業級應用開發的新范式

引言 隨著Spring Boot 3.0和Java 17的正式發布&#xff0c;企業級應用開發迎來了新的技術范式。這兩項技術的結合不僅帶來了性能提升&#xff0c;還引入了眾多現代化的編程特性&#xff0c;為開發者提供了更強大、更高效的開發體驗。本文將深入探討Spring Boot 3.0與Java 17的…

Vue 組件 - 指令

Vue 漸進式JavaScript 框架 基于Vue2的學習筆記 - Vue指令 目錄 指令寫法 自定義指令 簡單封裝指令 指令傳遞字符串 update事件 指令應用 指令實現輪播 指令函數簡寫 指令函數列表 bind inserted update componentUpdated unbind Vue3指令輪播 nextick 總結 指…

5.28 后端面經

為什么golang在并發環境下更有優勢 Go語言&#xff08;Golang&#xff09;在并發環境下的優勢主要源自其設計哲學和內置的并發機制&#xff0c;這些機制在語言層面提供了高效、簡潔且安全的并發編程工具。以下是其核心優勢的詳細分析&#xff1a; 1. Goroutine&#xff1a;輕量…

Linux線程入門

目錄 Linux線程概念 什么是線程 重新理解進程 線程的優點 線程的缺點 線程的異常 線程用途 Linux線程概念 什么是線程 在一個程序里的一個執行路線就叫做線程&#xff08;thread&#xff09;。更準確的定義是&#xff1a;線程是“一個進程內部的控制序列”。一切進程至…

通信應用高速模數轉換器ADC

在5G通信、醫療成像、航空航天及工業自動化等關鍵領域&#xff0c;高速ADC模數轉換器作為信號鏈的“心臟”&#xff0c;其性能直接決定了系統的精度與效率。然而&#xff0c;如何精確測試高速ADC的動態參數、優化設計驗證流程、應對復雜應用場景的挑戰&#xff0c;始終是工程師…

PostgreSQL 中 JSONB 數據類型的深度解析以及如何使用

一、JSONB 核心特性解析 1. 存儲結構與優勢 ??二進制存儲??&#xff1a;將 JSON 數據解析為二進制格式&#xff08;分解鍵值對&#xff0c;去除空格和重復鍵&#xff09;??高效查詢??&#xff1a;支持 GIN/GiST 索引&#xff0c;查詢速度比 JSON 類型快 10 倍??數據…

C++_核心編程_ 左移運算符重載 “<<” 左移運算符

作用&#xff1a;可以輸出自定義數據類型 */ //目標 調用p1,輸出Person 中的屬性 m_A ,m_B &#xff1a; /* #### 4.5.2 左移運算符重載 “<<” 左移運算符 作用&#xff1a;可以輸出自定義數據類型 *///目標 調用p1,輸出Person 中的屬性 m_A ,m_B &#xff1a; class…

thinkphp 5.1 部分知識記錄<一>

1、配置基礎 慣例配置->應用配置->模塊配置->動態配置 慣例配置:核心框架內置的配置文件,無需更改。應用配置:每個應用的全局配置文件(框架安裝后會生成初始的應用配置文件),有部分配置參數僅能在應用配置文件中設置。模塊配置:每個模塊的配置文件(相同的配置…

數據結構 -- 樹相關面試題

二、樹相關的填空題 1.對于一個具有 n 個結點的二叉樹&#xff0c;當它為一棵 ________ 二叉樹時&#xff0c;具有最小高度&#xff0c;即為 ________&#xff1b;當它為一棵單支樹時具有最大高度&#xff0c;即為 ________。 2.對于一個具有 n 個結點的二叉樹&#xff0c;當它…

2025河北CCPC 題解(部分)

簽到題&#xff1a;AC代碼如下 &#xff1a; // Problem: H - What is all you need? // Contest: Virtual Judge - sdccpc20250526 // URL: https://vjudge.net/contest/718568#problem/H // Memory Limit: 1024 MB // Time Limit: 1000 ms // // Powered by CP Editor (ht…

計算機視覺---YOLOv4

YOLOv4&#xff08;You Only Look Once v4&#xff09;于2020年由Alexey Bochkovskiy等人提出&#xff0c;是YOLO系列的重要里程碑。它在YOLOv3的基礎上整合了當時最先進的計算機視覺技術&#xff0c;實現了檢測速度與精度的顯著提升。以下從主干網絡、頸部網絡、頭部檢測、訓練…

OpenCV 第7課 圖像處理之平滑(一)

1. 圖像噪聲 在采集、處理和傳輸過程中,數字圖像可能會受到不同噪聲的干擾,從而導致圖像質量降低、圖像變得模糊、圖像特征被淹沒,而圖像平滑處理就是通過除去噪聲來達到圖像增強的目的。常見的圖像噪聲有椒鹽噪聲、高斯噪聲等。 1.1 椒鹽噪聲 椒鹽噪聲(Salt-and-pepper N…

Spring AI 系列3: Promt提示詞

一、Promt提示詞 Promt提示是引導 AI 模型生成特定輸出的輸入&#xff0c; 提示的設計和措辭會顯著影響模型的響應。 在 Spring AI 中與 AI 模型交互的最低層級&#xff0c;處理提示有點類似于在 Spring MVC 中管理”視圖”。 這涉及創建帶有動態內容占位符的大段文本。 這些占…

隨叫隨到的電力補給:移動充電服務如何重塑用戶體驗?

在快節奏的現代生活中&#xff0c;電力已成為維系日常運轉的隱形血脈。智能手機、電動汽車、便攜設備的普及&#xff0c;讓“電量焦慮”逐漸演變為一種時代癥候。而移動充電服務的興起&#xff0c;正悄然改變這一局面。它像一位隱形的能源管家&#xff0c;隨時響應需求&#xf…

LeetCode 75. 顏色分類 - 雙指針法高效解決(Java實現)

文章目錄 問題描述算法思路&#xff1a;三指針分區法核心思想指針定義 Java實現算法執行流程關鍵問題解析&#xff1a;為什么交換0后不需要重新檢查&#xff1f;交換0時的兩種情況分析詳細解釋&#xff1a; 復雜度分析示例演示&#xff08;輸入&#xff1a;[2,0,2,1,1,0]&#…

【MySQL】C語言連接

要使用C語言連接mysql&#xff0c;需要使用mysql官網提供的庫&#xff0c;大家可以去官網下載 我們使用C接口庫來進行連接 要正確使用&#xff0c;我們需要做一些準備工作: 保證mysql服務有效在官網上下載合適自己平臺的mysql connect庫&#xff0c;以備后用 下載開發庫 s…

NFS 掛載配置與優化最佳實踐指南

文章目錄 NFS 掛載配置與優化最佳實踐指南1. 服務器端配置1.1 安裝 NFS 服務1.2 配置共享目錄常用配置選項說明 1.3 啟動與檢查服務 2. 客戶端掛載2.1 安裝 NFS 客戶端2.2 掛載 NFS 共享2.3 自動掛載 3. 客戶端掛載選項4. 性能優化與故障排查4.1 性能優化建議4.2 常見問題排查 …

3D PDF如何制作?SOLIDWORKS MBD模板定制技巧

SOLIDWORKS制作3D PDF模版 SOLIDWORKS MBD能夠幫助工程師以清晰直觀的方式描述產品尺寸信息。在3D PDF文件中&#xff0c;用戶可以自由旋轉和移動視圖&#xff0c;方便查看模型的各個尺寸細節。 本文將帶您一步步學習如何使用SOLIDWORKS MBD制作專業的3D PDF模板&#xff0c;…