標準與格式 | 金鑰檢查 | 非對稱式金鑰產生
加解密/簽驗章實作 | 公鑰加密/私鑰解密 | 私鑰加密/公鑰解密 | 簽章/驗章
PKCS#1 | PKCS#8 | X.509 | PKCS#7 | PKCS#12
Intro
標準與格式
編碼格式
| 格式 | 檔案內容 | 副檔名 | 標準來源 |
|---|---|---|---|
| DER (Distinguished Encoding Rules) |
Binary | .der.cer |
X.690 ASN.1 的編碼規則之一 |
| PEM (Privacy Enhanced Mail) |
Base64 with BEGIN & END |
.pem.cert |
RFC 1421 |
OpenSSL 指令中預設的 format 都是使用 PEM,可使用 inform 或 outform 來指定輸出入格式:
# 編碼格式轉換公式
openssl <rsa|pkey|x509|pcks7> -inform <PEM|DER> -in <origin file> -outform <PEM|DER> -out <output file>
公鑰加密標準
| 格式/標準 | PEM 標頭範例 | 常見用途 / 備註 |
|---|---|---|
| PKCS #1 (RSA) | -----BEGIN RSA PRIVATE KEY----- |
傳統 RSA 專用格式。屬於舊版標準,不支援非 RSA(如 ECC 橢圓曲線)的加密演算法。 |
| PKCS #8 (通用) | -----BEGIN PRIVATE KEY----- |
現代主流標準。屬於通用型私鑰結構,不僅支援多種演算法(RSA, ECC 等),且原生支援私鑰本身的加密(Passphrase)保護。 |
| SPKI (純公鑰) | -----BEGIN PUBLIC KEY----- |
SubjectPublicKeyInfo 格式。外部 API 對接、數位簽章校驗(驗章)時最常使用的標準純公鑰格式。 |
| X.509 (憑證) | -----BEGIN CERTIFICATE----- |
包含公鑰的數位憑證。常見於 HTTPS、SSL、金融產線對接。除了公鑰外,還包含了發行者(CA)資訊、持有者身分與效期。 |
PKCS #1
- RSA Cryptography Standard,為 RSA 基本公鑰及私鑰的傳統格式
- ASN.1 區分方式:為
prim: INTEGER組成而無prim: OBJECT - PEM 標頭區分方式:
- PKCS #1 Private Key:
-----BEGIN RSA PRIVATE KEY----- - PKCS #1 Public Key:
-----BEGIN RSA PUBLIC KEY-----
- PKCS #1 Private Key:
# PKCS#1 Public Key 轉換為 X.509 SPKI
$ openssl rsa -pubin -RSAPublicKey_in -outform PEM -in pkcs1_key.pem -out spki_key.pem
$ openssl pkey -pubin -outform PEM -in pkcs1_key.pem -out spki_key.pem
# X.509 SPKI with RSA 轉換為 PKCS#1 Public Key
$ openssl rsa -pubin -outform PEM -RSAPublicKey_out -in spki_rsa_key.pem -out pkcs1_key.pem
PKCS #8
- Private-Key Information Syntax Standard,為通用私鑰格式,可另外加密 (passphrase)
- ASN.1 區分方式:相較於 PKCS#1 有
prim: OBJECT提供演算法,通常第二列為prim: INTEGER :00 - 常見副檔名:
.p8,.p8e,.pk8 - PEM 標頭區分方式:
- PKCS #8 Unencrypted Private Key:
-----BEGIN PRIVATE KEY----- - PKCS #8 Encrypted Private Key:
-----BEGIN ENCRYPTED PRIVATE KEY-----
- PKCS #8 Unencrypted Private Key:
# PKCS#1 private key 轉為 PKCS#8
$ openssl pkcs8 -topk8 -inform PEM -outform PEM -nocrypt -in private-key-pkcs1.pem -out private-key-pkcs8.pem
# PKCS#8 轉為 PKCS#1 private key
$ openssl rsa -in private-key-pkcs8.pem -out private-key-pkcs1.pem
# Encrypted PKCS#8 to Unencrypted PKCS#8
$ openssl pkcs8 -in encrypted-key.pem -out decrypted-key.pem -nocrypt
# Unencrypted PKCS#8 to Encrypted PKCS#8
$ openssl pkcs8 -in unencrypted-key.pem -topk8 -out encrypted-key.pem
X.509
- 標準定義:X.509 為國際公鑰憑證與 PKI 的架構標準。而 OpenSSL
-pubout預設輸出的純公鑰格式,即為 X.509 規範(RFC 5280)中所定義的 Subject Public Key Info (SPKI) 標準格式。此格式具備通用性,可視需求與 PKCS#1 公鑰進行相互轉換。 - ASN.1 區分方式:
- 結構最外層由
SEQUENCE包裹。 - 內部包含一組
AlgorithmIdentifier(含有OBJECT IDENTIFIER欄位),用來明確宣告此公鑰所使用的加密演算法(如 RSA 或 ECC)。
- 結構最外層由
- 常見副檔名與實務混淆:
.crt/.cer:X.509 完整憑證(包含身分、效期、CA 簽章及公鑰)。.pem/.pub/.key:SPKI 純公鑰格式。
注意:在 OpenSSL 生態系中,純公鑰最常命名為
.pem。若命名為.pub,在實務上極易與 Linux SSH 登入專用的 OpenSSH 公鑰格式(ssh-rsa AAAAB...)混淆,兩者底層結構完全不同不可混用。
- PEM 標頭區分方式:
- X.509 Certificate:
-----BEGIN CERTIFICATE----- - X.509 Certificate Subject Public Key Info (SPKI):
-----BEGIN PUBLIC KEY-----
- X.509 Certificate:
# 檢視查看 X.509 憑證內容 (不支援單一 SPKI 檔)
openssl x509 -noout -text -in cert.pem
# Private key (PCKS#1/#8) 簽發 X.509 certificate
$ openssl req -new -key private-key.pem -out csr.pem
$ openssl x509 -req -days 365 -in csr.pem -signkey private-key.pem -out certificate.pem
# 檢視查看 X.509 公鑰檔 (SPKI - Subject Public Key Info) 內容
$ openssl pkey -pubin -text -noout -in public-key.pem
# X.509 憑證提取公鑰 (SPKI)
$ openssl x509 -in certificate.pem -pubkey -noout > public-key-spki.pem
# X.509 公鑰 (SPKI) 轉為 PKCS#1 public key
$ openssl rsa -pubin -in public-key-spki.pem -RSAPublicKey_out > public-key-pkcs1.pem
# PKCS#1 public key 轉為 X.509 公鑰 (SPKI)
$ openssl rsa -RSAPublicKey_in -in public-key-pkcs1.pem -pubout -out public-key-spki.pem
PKCS #7
- 是一種「自給自足(Self-contained)」的數位簽名容器格式。封裝包含「簽章值」、「演算法規格」、「X.509 憑證鏈」以及「原文數據」(選用)。驗證時會提取封包內附帶的 X.509 憑證(包含公鑰)進行簽名驗證與信任鏈比對。
- 常見副檔名與用途:
.p7s: 數位簽章檔(Signature / SignedData)。主要用於分離式簽章(Detached Signature),即檔案僅包含簽章值與簽署者憑證,不包含原始內文(實務上如 Email 的 smime.p7s 附件).p7m: 封裝訊息檔(Message / MIME)。內含 SignedData(附加式簽章,含原文)或 EnvelopedData(純加密密文),常用於 S/MIME 安全電子郵件中的加密或「簽章加加密」訊息。.p7b/.p7c: PKCS#7 憑證包(Certificate Bundle)。結構上為特殊的 SignedData,專門用於一次打包傳輸完整的 X.509 憑證鏈(Certificate Chain)與 CRL,僅存憑證、不含私鑰。
- PEM 標頭區分方式:
- 通用格式 (RFC 2315):
-----BEGIN PKCS7-----(openssl smime 指令輸出) - 現代 CMS 格式 (RFC 5652):
-----BEGIN CMS-----(openssl cms 指令輸出)
改用 cms 指令的核心目的,在於避免 smime 指令強制進行 MIME 格式規範與 CRLF 換行轉換所導致的 digest failure(簽章驗證失敗)。cms 指令能忠實維持原檔內容(LF/CRLF/二進位),確保驗簽成功。
- 通用格式 (RFC 2315):
# [簽章] 建立不含原文的分離式簽章檔 P7S (因原文分離使用 cms 降低轉碼風險)
$ openssl cms -sign -in content.txt -signer certificate.crt -inkey private-key.pem -outform PEM -out signed_data.p7s
# [驗簽] 指定外部原文檔 (-content),提取封包內憑證進行簽章驗證 (驗章),但跳過憑證信任鏈與效期驗證 (-noverify)
$ openssl cms -verify -inform PEM -in signed_data.p7s -content plaintext.txt -noverify
# [驗簽] 指定外部原文檔 (-content),提取封包內憑證進行簽章驗證 (驗章),並依據指定的 CA 信任鏈 (-CAfile) 完整驗證憑證合法性與效期
openssl cms -verify -inform PEM -in signed_data.p7s -content plaintext.txt -CAfile certificate.crt
# [簽章] 建立含原文的 PKCS#7 簽章檔 (-nodetach)
$ openssl smime -sign -in content.txt -signer certificate.crt -inkey private-key.pem -outform PEM -out signed_message.p7m -nodetach
# [驗簽] 提取封包內原文與憑證進行簽章驗證 (驗章),但跳過憑證信任鏈與效期驗證 (-noverify)
$ openssl smime -verify -inform PEM -in signed_message.p7m -noverify -out extracted_content.txt
# [驗簽] 提取封包內原文與憑證進行簽章驗證 (驗章),並依據指定的 CA 信任鏈 (-CAfile) 完整驗證憑證合法性與效期
$ openssl smime -verify -inform PEM -in signed_message.p7m -out extracted_content.txt -CAfile certificate.crt
# 查看 PKCS#7 內部結構細節 (P7S 格式因不含原文,內容節點 d.data 會顯示為 <ABSENT>)
$ openssl pkcs7 -print -noout -in <PKCS7 file>
# 列出 PKCS#7 檔案中所包含的所有憑證 (-print_certs)
$ openssl pkcs7 -print_certs -text -in <PKCS7 file>
PKCS #12 / PFX
- 是一種包含私鑰與公鑰憑證/憑證鏈(public key certificates/chain)的格式。封裝 P12 時,至少需要憑證與私鑰 (給 Encrypted PKCS#8 Key 可在 Prompt 時輸入 passphrase 進行解密後再封裝)。整個 P12 檔案預設使用 Import Password 進行加密保護;在從 P12 檔案取出或查看憑證/私鑰時,預設會要求輸入 PEM passphrase 來加密私鑰並匯出 (-nodes 參數可跳過)。
- 常見副檔名:
p12,pfx - PKCS#12 僅採用 DER (binary) 格式,若轉換為 PEM 格式即等於將私鑰與各個憑證分別儲存於同一個檔案中:
- 轉換後的 PEM 包含多個區塊如: 多個
-----BEGIN CERTIFICATE-----與一個-----BEGIN PRIVATE KEY-----
- 轉換後的 PEM 包含多個區塊如: 多個
# 查看 P12 資訊 (若需輸入 passphrase 代表該 P12 為加密格式)
$ openssl pkcs12 -info -in keystore.p12
# 包裝產生 P12 檔案,預設使用 passphrase (-certfile 憑證鏈選項為 optional)
$ openssl pkcs12 -export -out keystore.p12 -inkey private-key.pem -in cert.pem -certfile ca-chain.pem
# 不設定 export/Import password,預設為空值 (-passout pass:)
$ openssl pkcs12 -export -out keystore.p12 -inkey private-key.pem -in cert.pem -passout pass:
# 從 P12 取出 Certificate
$ openssl pkcs12 -in keystore.p12 -clcerts -nokeys -out certificate.pem
# 從 P12 取出私鑰加密為 Encrypted PKCS#8 Key (預設)
$ openssl pkcs12 -in keystore.p12 -nocerts -out private-key.pem
# 從 P12 取出私鑰為 Unencrypted PKCS#8 Key (-nodes)
$ openssl pkcs12 -in keystore.p12 -nocerts -out private-key.pem -nodes
金鑰檢查
ASN1parse
使用 OpenSSL ASN.1 Parser,但須先知道編碼格式:
$ openssl asn1parse -in your-file.pem
# 利用 -inform 解析 DER 格式檔案
$ openssl asn1parse -inform DER -in your-file.der
result 若有 object 則可以快速識別演算法,如
6:d=2 hl=2 l= 9 prim: OBJECT :rsaEncryption
pkey 輸出
使用預設 privateKey 解析工具,需先知道編碼格式及公/私鑰:
$ openssl pkey -text -noout -in private-key.pem
# 利用 -pubin 解析 Public key
$ openssl pkey -text -noout -pubin -in public-key.pem
根據演算法輸出或檢查
基於 pkey,另外已知其演算法去解析:
# Read/check RSA Private Key (僅能讀取 RSA key)
$ openssl rsa -text -noout -in private-key.pem
$ openssl rsa -check -in private-key.pem
# Read EC Private Key (EC 指令能向下兼容 RSA Key)
$ openssl ec -text -noout -in private-key.pem
$ openssl ec -check -in private-key.pem
# Read Private Key (通用)
$ openssl pkey -text -noout -in private-key.pem
$ openssl pkey -check -in private-key.pem
# 利用 -pubin Read Public Key
$ openssl rsa -text -noout -pubin -in public-key.pem
$ openssl pkey -text -noout -pubin -in public-key.pem
# 利用 -inform 指定讀取 DER 格式
$ openssl rsa -text -noout -inform DER -in private-key.der
金鑰編碼格式判定
PEM 有規範為 Text 且應有表頭 hyphen 符號如 -----BEGIN PUBLIC KEY-----,DER 則為 binary。
基於以上,若收到公鑰是一組單行 base64 encoded text,可以驗證看看是不是 DER 直接做 base64 encoding:
$ echo 'MIIBIjANBgk.....QIDAQAB' | base64 -d | openssl asn1parse -inform DER
# base64 file to DER
$ cat public-base64.txt | base64 -d > public.der
如果上述是標準 base64 String (RFC 4648 §4),在知道演算法狀況下也可以直接用文字處理方式轉成 PEM: 頭與尾加上對應--KEY--格式,base64 string 每 64 字元換行
PEM 是使用標準 base64 String (RFC 4648 §4),不支援base64url (RFC 4648 §5)
非對稱式金鑰產生
OpenSSL
openssl 的操作通常會以第一個參數來指定要執行的功能(如 genpkey 或 pkey),大多數版本可以用help指令列出演算法列表,本篇會盡量使用pkey通用指令來方便統一操作不同演算法的金鑰
$ openssl version
$ openssl help
產生一組金鑰 (私鑰+公鑰)
通用產生金鑰可以使用genpkey指令,
$openssl genpkey -algorithm RSA -out private-key.pem
# 指定 key bits 長度
$openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out private-key.pem
# 指定輸出格式為 DER
$openssl genpkey -algorithm RSA -outform DER -out private-key.pem
# 產生一組 ECDSA 金鑰
$ openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:secp521r1 -out private-key.pem
# genpkey 預設產生私鑰格式為 PKCS #8
$openssl genpkey -algorithm RSA -out private-key.pkcs8.pem
# genrsa 預設產生私鑰格式為 RSA 傳統標準 PKCS #1
$openssl genrsa -out private-key.pkcs1.pem
-algorithm,-pkeyopt參數文件可以參考 openssl man & KEY GENERATION OPTIONS
genrsa產出私鑰檔案標準為 PKCS #1,為早期給 RSA 專用較舊的標準
genpkey產出私鑰檔案標準為 PKCS #8,為後期通用標準,可另外再加密
利用私鑰指定輸出公鑰
# 利用 -pubout 指定輸出公鑰
$ openssl pkey -in private-key.pem -pubout -out public-key.pem
# OpenSSL 現行版本預設 -pubout 為 X.509 Subject Public Key Info
$ openssl pkey -in private-key.any.pem -pubout -out public-key.spki.pem
$ openssl rsa -in private-key.any.pem -pubout -out public-key.spki.pem
# RSA 利用 `-RSAPublicKey_out` 指定輸出為 PKCS #1 Public Key
$ openssl rsa -in private-key.any.pem -pubout -RSAPublicKey_out -out public-key.pkcs1.pem
格式轉換
# DER to PEM for private key
$ openssl pkey -inform DER -in public.der -outform PEM -out public.pem
# DER to PEM for public key
$ openssl pkey -inform DER -pubin -in public.der -outform PEM -out public.pem
SSH-Key
使用 ssh-keygen 可以快速產生用於 SSH protocol 的公私鑰:
$ ssh-keygen
# 指定路徑與檔名
$ ssh-keygen -f $HOME/.ssh/id_rsa
Key Type
$ ssh-keygen -t [ dsa | ecdsa | ecdsa-sk | ed25519 | ed25519-sk | rsa ]
$ ssh-keygen -t dsa
轉換成 PEM 格式
To PKCS#8:
$ ssh-keygen -f id_rsa -e -m PKCS8 > id_rsa.pem
SSH 公鑰也可以拿來轉換成 PKCS#8 PEM 即可相容於 OpenSSL 加解密
加解密與簽驗章實作 (Cryptography Implementation)
透過 OpenSSL command 簡易產生公私鑰:
$ openssl genrsa -out private-key.pem
$ openssl rsa -in private-key.pem -pubout -out public-key.pem
在密碼學標準中,RSA 是目前主流同時支援「資料加解密」與「數位簽章」的非對稱演算法(數學上支援公私鑰對應運算);而其他現代非對稱演算法(如 ECDSA / Ed25519)則專門用於數位簽章,不直接用於資料加解密。
公鑰加密私鑰解密
Public-Key Encryption and Private-Key Decryption
以 OpenSSL command 為例,以下會直接使用usautl指令做範例,usautl跟pkeyutl指令在此應用下是相同用法
加密 (使用 RSA Public key):
$ openssl rsautl -encrypt -pubin -inkey public-key.pem -in plaintext.txt -out encrypted.txt
// pkeyutl
$ openssl pkeyutl -encrypt -pubin -inkey public-key.pem -in plaintext.txt -out encrypted.txt
加密預設是使用 private key,所以用 public key 加密要另外宣告
-pubin,解密則會自動判斷不需另外宣告
解密 (使用 RSA Private key):
$ openssl rsautl -decrypt -inkey private-key.pem -in encrypted.txt -out plaintext.txt
// pkeyutl
$ openssl pkeyutl -decrypt -inkey private-key.pem -in ciphertext.bin -out decrypted.txt
私鑰加密公鑰解密 (非正規)
Private-Key Encryption and Public-Key Decryption
密碼學標準中沒有「公鑰解密」這種正規應用,各程式語言(Java, C#, Python 等)的標準 Crypto API 也通常不提供「公鑰解密」函式。若嘗試透過底層工具用私鑰加密,產生的密文在其他語言環境下將無法正常解密。
OpenSSL 舊版
rsautl指令可以強制用私鑰加密且公鑰解密,但新版pkeyutl在解密時已有阻擋
數位簽名簽署與驗證 (簽章/驗章)
Digital Signature Signing and Verification (Signing/Verification)
數位簽章結合了雜湊演算法 (Hash) 與非對稱密碼學:
- 簽章 (Sign):將原始數據經 Hash 運算產出固定長度的雜湊摘要 (Digest),再透過私鑰進行加密簽署生成二進位簽名值,最後通常編碼為 Base64 方便傳輸與儲存。
- 驗章 (Verify):接收端解碼 Base64 取得二進位簽名,利用對應的公鑰(或從 X.509 憑證提取公鑰)解開簽名並比對摘要,以確認資料完整性(未經竄改)與來源真實性(不可否認性)。
1. 金鑰演算法類型 (Key Algorithms)
簽章金鑰並不局限於 RSA,現代系統更推薦使用橢圓曲線:
- RSA:傳統標準 (如 2048-bit / 4096-bit)。
- ECDSA / EdDSA (如 Ed25519):現代推薦。金鑰極短 (256-bit 效能與安全性即超越 RSA 3072-bit)、運算速度快且開銷極低。
2. 跨系統/跨語言對接必備規格參數
跨系統驗章時(如 Java、Python、Node.js),雙方必須精確定義以下 4 項規格:
- 金鑰演算法與長度:如
RSA 2048或ECDSA P-256。 - 雜湊演算法 (Hash):如
SHA-256、SHA-512(已不該使用 SHA-1/MD5)。 - 填充模式 Padding (RSA 獨有):
PKCS#1 v1.5(傳統預設) 或RSA-PSS(現代安全標準)。 - 資料正規化:統一為
UTF-8編碼與換行符號 (\n);JSON 需約定 Key 排序與格式。
// 簽章:openssl dgst 指令會根據傳入的 private key 自動辨識是 RSA 還是 ECDSA 金鑰。
$ openssl dgst -sha512 -sign private-key.pem -out digest.sha512 plaintext.txt
// 使用公鑰驗章
$ openssl dgst -sha512 -verify public-key.pem -signature digest.sha512 plaintext.txt
Verified OK
// 使用憑證驗章 (取出公鑰後驗章)
openssl x509 -in certificate.pem -pubkey -noout | openssl dgst -sha512 -verify /dev/stdin -signature digest.sha512 plaintext.txt
演算法選擇
更多指令選項包含演算法設定可以參考:openssl-pkeyutl man
# OAEP with SHA-256 and MGF1 with SHA1
$ openssl pkeyutl -encrypt -in plaintext.txt -out ciphertext.bin -pubin -inkey public-key-x509.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha1
# OAEP with SHA-256 and MGF1 with SHA-256
$ openssl pkeyutl -encrypt -in plaintext.txt -out ciphertext.bin -pubin -inkey public-key-x509.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -pkeyopt rsa_mgf1_md:sha256
Programing Lanuage
公私鑰皆可以非對稱的加解密,例如PHP functions: - openssl_private_decrypt - openssl_private_encrypt - openssl_public_decrypt - openssl_public_encrypt