透過 Nodejs crypto 幫助男孩與女孩完成非對稱加密的私訊傳輸

整理了這篇內容,分享透過 Nodejs crypto 完成非對稱加密的思維與訊息傳輸與實作心得。

Whien
8 min readOct 30, 2019
Photo by Ian Tuck on Unsplash

現在瀏覽器上聽到 https(SSL) 已經成為標配,總是聽到使用非對稱加密法,並且可以在網路傳輸中保護我們的訊息,但卻一直沒有實際了解過為什麼,於是嘗試自己去了解後並實作後,快速記錄了此篇心得。

先來快速了解 HTTP/HTTPS 雖然我們重點不在此。

〉HTTP

在沒有 https 前,我們所傳的訊息內容都是這樣子的

http

〉HTTPS

HTTPS 目的是將原本明文訊息變成加密訊息,並且中途被監聽時都無法被知道內容,看到的就只是一堆加密過後的亂碼

接下來我們開始來實作鑰匙與密文

〉前置準備

  • Nodejs
  • crypto

〉舉個例子

有位男孩與女孩想要傳送秘密訊息給對方,但又不希望中途被別人知道內容,這時候要先做什麼呢?

》替男孩與女孩各建立一對鑰匙(公開鑰匙、私有鑰匙)

使用 crypto 可以很快地建立鑰匙

const {
generateKeyPairSync
} = require('crypto');
const boyKeys = generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});
const girlKeys = generateKeyPairSync('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem'
}
});

modulusLength

使用 RSA 演算法,一般來說 modulusLength 使用 2048 已經足夠,但建議上最好是 4096 越長越不容易被破解。

publicKeyEncoding

  • type

類型 PKI 指的是 Public Key Infrastructure公開金鑰基礎建設),又稱公開金鑰基礎架構、公鑰基礎建設、公鑰基礎設施、公開密碼匙基礎建設或公鑰基礎架構,是一組由硬體、軟體、參與者、管理政策與流程組成的基礎架構,其目的在於創造、管理、分配、使用、儲存以及復原數位憑證。(wiki

  • format

PEM 全名為 Privacy-Enhanced Mail隱私增強型電子郵件),由 DER 編碼的憑證再進行 Base64 編碼的資料存放在(wiki

-----BEGIN CERTIFICATE-----
// ............

-----END CERTIFICATE-----

privateKeyEncoding

  • type

PKCS 是由 RSA 公司制定的一組關於公鑰加密的標準

PKCS1 定義了RSA的數理基礎、公私鑰格式,以及加解密、籤/驗章的流程。

PKCS8 定義了私鑰消息的表示 PKCS12:定義了包含私鑰與公鑰證書的文件格式,其中私鑰採密碼保護。

資料來自這裡

  • format

publicKeyEncoding 相同

》完成產生鑰匙

先隨意配給男孩與女孩演算法過後的鑰匙,以下範例鑰匙是簡化過後的,並無法正常使用。

男孩公開鑰匙(0xf3b)男孩私有鑰匙(0x9b1)
女孩公開鑰匙(0x32a)女孩私有鑰匙(0xff3)

》彼此交換公開鑰匙

公開鑰匙是可以被看見的,因此用任何方式傳送給予都是可以的。

男孩公開鑰匙(0xf3b)男孩私有鑰匙(0x9b1)男孩拿到了女孩的公開鑰匙(0x32a)
女孩公開鑰匙(0x32a)女孩私有鑰匙(0xff3)女孩拿到了男孩的公開鑰匙(0xf3b)

》產生訊息

很簡單的用一份 JSON 檔案來當作男孩要傳送給女孩的內容,並命名為 data.json

{
"id": 1,
"name": "Whien",
"message": "Hello World"
}

》使用對方公開鑰匙加密訊息

今天男孩想要傳送一個訊息給女孩,則需要透過女孩的公開鑰匙來加密內容

const {
publicEncrypt
} = require('crypto');
const data = require('./data.json');
const message = new Buffer.from(JSON.stringify(data));
const enc = publicEncrypt(0x32a, message);

publicEncrypt(key, buffer)

透過 public key 來加密的方法,第一個參數放入對方的公開鑰匙(不是自己的),第二個參數必須將內容轉為 buffer

message

在這裡要使用 JSON.stringify 來轉成字串後才能轉為 buffer

enc

此變數則是將訊息以對方的公開鑰匙加密後的回傳結果,而 0x32a 就是我們範例中的公開鑰匙

》將加密訊息傳送回給女孩

收到後只要使用自己的私有鑰匙,就能將符合自己公開鑰匙所加密的內容解密並看到正確內容。

const {
privateDecrypt
} = require('crypto');
const dec = privateDecrypt(0xff3, enc);

》簽名後更確定是對方的訊息

女孩很開心地收到了訊息,但突然有點懷疑這是不是真的由男孩所發送的,這時候該怎麼辦呢?

》男孩傳送訊息前,在訊息上用私有鑰匙簽名,並一起送出

前面有提到,私有鑰匙只能自己擁有,不能散播,除了拿來解密由自己公開鑰匙所加密的訊息內容以外,另一個就是拿來做簽名(sign),加強一下剛剛的程式內容,0x9b1 是男孩的私有鑰匙

const {
publicEncrypt,
sign
} = require('crypto');
const message = new Buffer.from(JSON.stringify(data));
const enc = publicEncrypt(0x32a, message);
const signature = sign('sha256', enc, 0x9b1);

》女孩收到訊息後,必須先確認是否是男孩的,在解密

收到訊息後,可以先透過男孩的公開鑰匙來確認這個訊息的簽名是不是由男孩所簽的,當確定是由男孩所簽名的,女孩就可以安心地打開訊息內容,0xf3b 則是男孩的公開鑰匙。

const {
verify
} = require('crypto');
const isValid = verify('sha256', enc, 0xf3b, signature);
if (isValid) {
const dec = privateDecrypt(boyKeys.privateKey, enc);
console.log(dec.toString());
}

》最後女孩看到了訊息內容,並留下了一滴眼淚

{
"id": 1,
"name": "Whien",
"message": "Hello World"
}

》第三者想偷看訊息內容,就要有正確的私有鑰匙

如果今天剛好遇到一個第三者想偷看訊息內容時怎麼辦?不用擔心,經過公開鑰匙加密過後的訊息,除了原本公開鑰匙的擁有者使用私有鑰匙才能還原內容以外,是很難仿造的,因此不需要擔心這個情形。

》程式碼統整

要是你也想幫助下一對男孩與女孩,完整程式碼在這裡

--

--

Whien

遨遊在硬體與軟體世界中,對於計算機一切事物都充滿好奇及熱情。