PHP

[PHP] 範例實做 RSA, 公鑰、私鑰非對稱加密、解密演算法 – 串接 iOS,Android

(本文於 2017-08-08 補充更新) 很多時候,資料在傳遞間需要也必須做好保密動作,之前分享過 [PHP] 實做 AES 資料加密(含範例) 的方式,而 AES 這樣的加密方式並不 100% 安全! 萬一 Clinet 端 的 “金鑰“ 遭到破解,資料內容就完全曝光、失去意義….
因此更多時候我們會採用 『非對稱加密演算法』 !透過公鑰與私鑰的方式能讓資料加密更安全,更有保障!
具體的做法如下…

 

 

 


 

什麼是 RSA 加密演算法?

Wiki 上定義的 RSA加密演算法是一種非對稱加密演算法 (Wiki 真是偷懶好東西)

簡單來說,加密時需要透過 公鑰 進行,而解密時需要利用 私鑰 進行解密!
也就是說,就算 Client 端遭到破解,造成 公鑰 流出,也不會有任何危害。

‼️ 注意:透過 RSA 加密後的內容(密文),每次都會不同!
因為 演算法 (PKCS PADDING) 的關係,每次加密後的結果都不同,但透過同組公、私鑰都能正常解密.
(PKCS PADDING 會在加密前對明文內容加入亂數填充 Padding – wiki)

 

 


 

如何產生公鑰與私鑰?

在這裡,需要利用 openssl 產出一 私鑰(private key)
你需要在 CLI (command-line interface) 裡使用 opensll 輸入下面指令,windows 的使用者請先安裝 openssl (這次先不另外講解這部份…)

 

可以透過兩種方式建立 公鑰與私鑰,則一即可..

方法一:

>openssl genrsa -out private_1024.key 1024

使用 RSA 1024 位元加密,產出 Private Key

接著再使用剛剛產出的 private key 建立 public key

>openssl rsa -in private.key -out public_1024.pem -outform PEM -pubout

 

 

方法二: (產出的副檔名,不是那麼重要)

>sudo openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -keyout ~/php/private_2048.key -out ~/php/public_2048.crt

上述方式較為嚴僅,可以設定更多細節,可以參考(https://blog.gtwang.org/linux/nginx-create-and-install-ssl-certificate-on-ubuntu-linux/)

一些參數說明….(也可以選擇要不要再加入密碼保護 )

  • req:使用 X.509 Certificate Signing Request(CSR) Management 產生憑證。
  • x509:建立自行簽署的憑證。
  • nodes:不要使用密碼保護,因為這個憑證是 NGINX 伺服器要使用的,如果設定密碼的話,會讓伺服器每次在啟動時書需要輸入密碼
  • days 3650:設定憑證的使用期限,單位是天,如果不想時常重新產生憑證,可以設長一點。
  • newkey rsa:2048:同時產生新的 RSA 2048 位元的金鑰。
  • keyout:設定金鑰儲存的位置。
  • out:設定憑證儲存的位置。

分別產出 私鑰 (Private Key) 和公鑰 (Public Key) ,也確認兩者都放到正確位置就可以開始實做&測試加解密了!

若要將公鑰交給 iOS 或 Android 端,他們會需要 .pem 或 .der 檔,可以參考下面的文章進行轉換
OpenSSL 操作筆記 – 檔案格式轉換

 

 


 

PHP 實做 RSA, 公私鑰非對稱加解密 – 範例

用簡單的 Plain PHP 做範例…



// 加密前原始資料-信用卡資料
$fullText = '{"card_number":"1234567812345678","expiry_date":"202012","cvc2":"000"}';

// 設定公、私鑰檔名
const PRIVATE_KEY = 'private_1024.key';
const PUBLIC_KEY = 'public_1024.pem';

// 如果你是用第二種方法產生
// const PRIVATE_KEY = 'private_2048.key';
// const PUBLIC_KEY = 'public_2048.crt';

// 如果設定密碼
// const PASSPHRASE = 'my_pasword';


function public_encrypt($plain_text)
{
    $fp = fopen(PUBLIC_KEY, "r");
    $pub_key = fread($fp, 8192);
    fclose($fp);
    $pub_key_res = openssl_get_publickey($pub_key);
    if(!$pub_key_res) {
        throw new Exception('Public Key invalid');
    }
    openssl_public_encrypt($plain_text, $crypt_text, $pub_key_res, OPENSSL_PKCS1_OAEP_PADDING);
    openssl_free_key($pub_key_res);
    return base64_encode($crypt_text); // 加密後的內容為 binary 透過 base64_encode() 轉換為 string 方便傳輸
}

function private_decrypt($encrypted_text)
{
    $fp = fopen(PRIVATE_KEY, "r");
    $priv_key = fread($fp, 8192);
    fclose($fp);
    $private_key_res = openssl_get_privatekey($priv_key);
    // $private_key_res = openssl_get_privatekey($priv_key, PASSPHRASE); // 如果使用密碼
    if(!$private_key_res) {
        throw new Exception('Private Key invalid');
    }

    // 先將密文做 base64_decode() 解釋
    openssl_private_decrypt(base64_decode($encrypted_text), $decrypted, $private_key_res, OPENSSL_PKCS1_OAEP_PADDING);
    openssl_free_key($private_key_res);
    return $decrypted;
}

// 將資料進行加密
$r = public_encrypt($fullText);
var_dump($r);

// 將資料進行解密
$r = private_decrypt($r);
var_dump($r);

非常簡單吧~

 

另外… 使用 1024, 2048 更甚至是 3072 加密時所需的加、解密耗時
1024
加解密約 0.0001~0.0006 秒
失敗時約 0.005~0.007 秒

3072
加解密約 0.0003~0.0007 秒
失敗時約 0.006~0.009 秒

做個參考~

 

 


 

PHP openssl 內建函式說明

官方手冊:openssl_public_encrypt
官方手冊:openssl_private_decrypt

上述兩個函式第四個參數 padding 模式可以指定加、解密使用的演算法
(OPENSSL_PKCS1_PADDING, OPENSSL_SSLV23_PADDING, OPENSSL_PKCS1_OAEP_PADDING, OPENSSL_NO_PADDING)
預設為:OPENSSL_PKCS1_PADDING
目前比較推薦 OPENSSL_PKCS1_OAEP_PADDING

 

 


 

iOS, Android 串接 – 注意事項

  1. Server 端產出 private key + public key 後,先自行測試 publice key 加密、private key 解密
  2. 將 public key 提供給 iOS 或 Android 端
  3. 提供必要資訊
    padding mode:OPENSSL_PKCS1_OAEP_PADDING
  4. 建議提供一個 API 做為 server 與 client 兩者之間的加、解密測試用(方便開發!)
    e.g. client 同時傳入自己處理好的 密文明文
    server 解密後與明文做比對,相同 回傳 true,反之 回傳 false
    這樣能加快兩方的開發速度

 

‼️ 注意: 產生金鑰時的長度, RSA 1024 或 RSA 2048 會影響可被加密的明文長度
e.g 1024 bit 的金鑰能加密的明文只有約 128 bytes (實際上再少一些,詳情請看 參考文章 – 密文與明文長度)
因此如果預期明文的長度比較長,建議最初生成 RSA 金鑰的時候設定大一點 2048, 4096…

 

 


 

參考文章

Java 進行 RSA 加解密時不得不考慮到的那些事兒

生成密文的長度和明文長度無關,但明文長度不能超過密鑰長度

可樂

View Comments

  • 不好意思 我想請問 我產生的 private_1024.key 要放在電腦的哪個地方??

    • 放在任何位置都可以哦,只是你 PHP 能讀取的地方就可以了。
      通常和專案放在一塊,比較方便測試和讀取。
      安全一點的話,可以放到 .ssh 這類,安全性比較高的目錄。

Recent Posts

plain PHP 搭配 Slack 進行錯誤追蹤、回報(Error Tracking、Error Handling)

錯誤追蹤、回報非常重要,看到的錯誤才知道怎麼修。現今 PHP 流行的 Laravel 有很好的 Error Tracking, Error Handling。但 plain PHP 怎麼辦呢? 在 production 為了安全考量會設定…

4 years ago

Drone CI/CD 配合 Github 使用 Rsync 進行 Deploy

jenkins、circleci、travis 或 Gitlab CI 皆為目前暫知名的 CI/CD 服務,各自缺點也不言而喻...過於肥大、收費略高(?)、速度不夠快執問題...此時使用 go language 開發的 Drone 就出現啦,完全 docker 容器化的運行方式讓整個 CI…

4 years ago

Nginx brotli 設定

網頁壓縮技術中 gzip 很好用,deflate 己經過時,但你聽過 brotli 嗎? 有著比 gzip 更好、更快的壓縮效率。看起來利大於弊有什麼不用他的理由嗎?簡單從優、缺點來看 brotli!到底 brotli 布羅特利是什麼、如何設定呢。 目前大多的 web server…

4 years ago

本機使用 Docker 容器內 PHP (wrapper/expose PHP)

為什麼要讓本機使用 Docker 內 PHP? 情境... docker 容器內用的是 PHP 7.4 但你的開發本機還在跑 PHP 5.6 或是更舊,因為 dockerize 的關係會將所有相關環境都轉移到…

4 years ago

為什麼你需要密碼管理工具

為什麼你需要密碼管理工具現代人一天下來需要輸入多少組密碼,工作與生活己經和密碼密不可分! 除了足夠全安的密碼,密碼記錄、儲存的方式又足夠安全嗎?密碼管理工具可以帶來什麼幫助呢? 為什麼你需要密碼管理工具 資安問題!!大多人說著沒做壞事不怕被偷資料、監聽。嚴重曝露出現代人的基本科技素養的低落和無知 🤯 密碼的使用無所不在!! 行動裝置的普及,APP 、手機遊戲、銀行帳戶所有和生活相關的東西都需要密碼!!facebook, line 只要打開 APP 也會輸入密碼只是他是自動輸入、一般情況不可視 (auth token) 一般人最常發生的密碼資安問題…

4 years ago

簡單使用 Mysql Partition 優化查詢

mysql 資料表分區 mysql table partition 從架構上調整 mysql 的查詢效率。mysql DB 的優化可以簡單也能複雜,除了調整設定值。也可以透過水平分割(Horizontal Partitioning)、垂直分割(Vertical Partitioning) 分庫或分表將資料分散儲存減少資料搜尋、group by 時的效能消耗。拆開批次處理,理論上效率都會變好,本文就水平分割的…

4 years ago