非對稱加密與OpenSSL

有價值炮灰發表於2016-10-29

隨著個人隱私越來越受重視, HTTPS也漸漸的流行起來, 甚至有許多網站都做到了全站HTTPS,
然而這種加密和信任機制也不斷遭遇挑戰,比如戴爾根證照攜帶私鑰,Xboxlive證照私鑰瀉露,
還有前一段時間的沃通錯誤頒發Github根域名SSL證照事件. 因此本文從非對稱加密說起,
介紹了證照的簽證流程, 並且通過openssl的命令列工具對這些過程都轉化為相對具體的命令,
也算是一個溫故知新的簡要記錄吧.

前言

一般來說,常見的數字加密方式都可以分為兩類,即對稱加密和非對稱加密. 對於對稱加密來說,
加密和解密用的是同一個金鑰, 加密方法有AES,DES,RC4,BlowFish等; 對應的, 非對稱加密在
加密和解密時, 用的是不同的金鑰, 分別稱為公鑰或私鑰. 非對稱加密的加密方法有RSA, DSA,
Diffie-Hellman等.

OpenSSL是一個開源專案,為傳輸層安全(TLS)和安全套接字(SSL)協議提供了比較完整的實現,
同時也致力於將自身打造為一個通用的密碼學工具集. 其中包括:

  • libssl : 提供了SSL(包括SSLv3)和TLS的伺服器端以及客戶端的實現.
  • libcrypto : 通用的密碼學庫以及對X.509的支援
  • openssl : 一個多功能的命令列工具

本文主要使用openssl的命令列工具來示例非對稱加密的流程, 如果有興趣的話,也可以用其SDK
來實現更具體的操作.

加解密過程

建立公私鑰對

首先用openssl生成私鑰:

openssl genrsa -out private.pem 1024 

當然為了更加安全,可以在生成私鑰的時候同時指定密碼, 這樣即使不小心瀉露了私鑰,也能增加別人的盜用難度:
openssl genrsa -aes256 -passout stdin -out private.pem 1024
openssl genrsa -aes256 -passout file:passwd.txt  -out private.pem 1024
openssl genrsa -aes256 -passout pass:my_password -out private.pem 1024

其中 -passout 指定密碼的輸入方式,可以分別是stdin,從檔案中讀取或者緊接著pass:後面輸入.
有了私鑰,便可以從其中提取出公鑰:

openssl rsa -in private.pem -pubout -out public.pem

用公私鑰進行加解密

在一次祕密的資訊傳輸中, 我們首先通過可信的方式(比如面對面)將公鑰告知對方, 對方傳送機密資訊的時候
就可以用我們的公鑰加密:

openssl rsautl -encrypt -pubin -inkey public.pem -in file.txt -out file.txt.enc

在傳送的過程中即便洩露了檔案,也無法檢視檔案的明文資訊. 而我們收到密文後, 用私鑰解密即可:

openssl rsautl -decrypt -inkey private.pem -in file.txt.enc -out file.txt.dec

和對稱加密協作

雖然公私鑰加密很好用, 但事實上非對稱加密的缺點是加解密速度要遠遠慢於對稱加密, 在某些極端情況下,
甚至能比非對稱加密慢上千倍. 另外由於RSA演算法的工作機理, 如果金鑰是n位元的,那麼其加密的資訊容量就
不能大於(n-11)位元. 因此對於大檔案的加密傳輸, 通常還是使用對稱加密的方式, 例如

openssl rand -base64 128 -out aeskey.txt
openssl enc -aes-256-cbc -salt -in file.txt -out file.txt.aesenc -pass file:aeskey.txt
openssl enc -d -aes-256-cbc -in file.txt.aesenc -out file.txt.aesdec -pass file:aeskey.txt

其中aeskey.txt是我們隨機生成密碼檔案, 並且用其可以對大檔案進行對稱的加解密, 在實際中,
通常還會將密碼檔案用公私鑰加密的方式來傳送給對方. 值得一提的是,這也正是PGP的工作方式,
如下圖所示:

pgp

證照

對任一個體來說, 它都有公鑰,私鑰和證照. 其中私鑰用來加密發出去的資訊,公鑰用來解密收到的資訊,
而證照則用來證明自己的身份. 一般來說,證照中包含自己的公鑰以及額外的資訊,如簽發機構(CA, Certificate Authority),
證照用途(比如適用的域名)和有效時間等. CA通常是個第三方的可信機構, 比如VeriSign, GeoTrust,
DigiCert和沃通等, 當然也可以是未知的主體, 比如說自己.

獲得一張證照的流程通常是: 1)用私鑰生成證照籤名請求(csr), 2)將csr檔案傳送給CA,待其驗證資訊無誤後,
CA會用自己的私鑰對其進行簽名表示確認.

生成證照籤名請求

證照籤名請求(Certificate Signing Request)通常以.csr為字尾, 包含了請求方的公鑰和主體的詳細資訊,
如域名,公司名,國家,城市等資訊, 其完整內容可以參考這裡. 使用openssl也能很方便地生成csr:

openssl req -new -key private.pem -out pppan.csr

預設會在stdin中根據提示互動地輸入主體資訊,也可以通過 -config 選項來從檔案中讀取.
生成完之後可以通過:

openssl req -in pppan.csr -noout -text

來檢視csr檔案中的詳細資訊.

CA對csr檔案進行簽名

當CA收到csr檔案並且對請求方的域名,公司等內容校驗無誤後,便可以對csr請求進行確認(簽名),

openssl req -x509 -newkey rsa:4096 -nodes -keyout cakey.pem -out cacert.pem -outform PEM
openssl ca -config openssl-ca.cnf -policy SP -extensions SR -infiles pppan.csr -out pppan.crt

雖然這不是重點, 但也稍微解釋下這兩個命令的意思吧. 第一個命令是CA一開始建立私鑰和CA的證照,
第二個命令表示對csr檔案進行簽名確認, 用-config指定自定義的配置檔案, 如果不指定則預設為/usr/lib/ssl/openssl.cnf,
SP和SR都是自定義於配置檔案中的資訊, 此外配置檔案中還包括CA證照路徑和私鑰路徑,以及對req的預設校驗策略等,
有興趣的可以檢視詳細解釋.

另外值得一提的是, 我們用自己的私鑰也可以生成證照, 並且也能用這個證照來對自己的csr進行簽名,
這通常稱為自簽名(self-signed), 上面CA生成的證照cacert.pem就是自簽名的. 一般來說,
如果是自己隨便生成自簽名證照, 通常會被認為是不可信的, 除非手動新增到對方的信任CA證照列表中.

檢視和驗證證照

CA對csr進行簽名後, 我們就能得到對應的證照, 這裡是pppan.crt, 可以用openssl檢視證照的詳細資訊:

openssl x509 -noout -text -in pppan.crt

可以看到具體的簽發機構,簽發時間和證照的有效時間等資訊.
可以用命令驗證證照是否有效:

openssl verify -CAfile Trusts.pem pppan.crt

其中Trusts.pem是一系列所信任的證照集合,其中也包括了上述CA的證照cacert.pem

其他

上面所有用到的證照及其元件,如公鑰,私鑰,csr等,其格式都是PEM的,這也是最常見的一種格式,
可以用文字便及其開啟,通常是以-----BEGIN XXX------開頭, 以-----END XXX-----結束,
中間的部分則是實際金鑰的base64編碼, 其二進位制表示也稱為DER格式, 兩者可以用base64轉化,
因此都屬於x509實現的證照格式.

還有比較常見的證照格式,為PKCS7PKCS12. 其中PKCS7是由JAVA使用的開放標準,並且也被
Windows所支援, 其內是不包含私鑰資訊的; 而PKCS12則是一種非公開的標準,用來提供比PEM的
純文字格式更高的安全性, 這是Windows建議使用的格式, 其中可以包含私鑰資訊.

不同格式的轉換如下所示.

PEM <-> DER:

openssl x509 -in bar.pem -outform der -out bar.der
openssl x509 -inform der -in foo.der -out foo.pem

PEM <-> PKCS7:

openssl crl2pkcs7 -nocrl -certfile foocert.pem -out foocert.p7b 
openssl pkcs7 -in foocert.p7b  -print_certs -out barcert.pem  

PEM <-> PKCS12:

openssl pkcs12 -inkey private.key -in foocert.pem -export -out foocert.pfx
openssl pkcs12 -in foocert.pfx -nodes -out barcert.pem 

後記

當今我們使用最多的https本質上就是在http協議的基礎上對傳輸內容進行了非對稱的加密,
當然實現過程多了很多複雜的互動, 感興趣的可以去檢視SSL和TLS協議. 我想說的是,
這一切信任機制的基石是對於CA的信任, 如果說CA的私鑰瀉露,或者我們錯誤地信任了一個壞CA,
那麼https的隱私性也就不復存在了, 因為其可能對無效的csr進行簽名, 從而使得https中間人攻擊
成為現實. 據說早在兩年前偉大的防火牆就已經可以對https進行監聽,敏感詞識別和連線重置,
後來因為某種原因才從大範圍應用轉為只對特殊物件使用,不過那是後話了.

部落格地址:

歡迎交流,文章轉載請註明出處.

相關文章