Base64 是編碼不是加密 & 用 Node.js 實作簡易 RSA 非對稱式加密

前言

因為專案需求所以有稍微用到一些密碼學的部分,前幾天團隊內的同事在研究的時候,我也好奇的上前圍觀,但才疏學淺,居然脫口說出 base64 是加密的一種然後被噹爆,超想躲進土裡的啊啊啊!!

所以為了雪恥,決定稍微研究一下 RSA 非對稱式加密,以及整理一下 Base64 只是編碼不是加密這件事情。

Base64

在說明之前可以先從 WIKI 上了解 Base64 是什麼樣的東西

懶人包: Base64 是編碼的一種,並沒有 Base64 加密這回事。

以下節錄自 WIKI
Base64 是一種基於 64 個可列印字元來表示二進位資料的表示方法,常用於在通常處理文字資料的場合,表示、傳輸、儲存一些二進位資料,包括MIME的電子郵件及 XML 的一些複雜資料。

而如果我們有仔細看 WIKI 的話,會發現這份文檔皆是使用 Base64 編碼而非 Base64加密,從這邊其實就可以看出一些蛛絲馬跡。

加密?編碼?

這三者無論是用中文、英文來看都有相當大的差異:

  1. 加密 (Encrypt)
  2. 編碼 (Encode)

到底為什麼很多人會把這三種完全不一樣的東西都當成加密呢?

主要是因為透過這三種方式處理過後的資料,都會長的跟原本不一樣,一般人無法直接用肉眼辨別,就會讓人覺得像是被加密處理過的天書。

然而並不是把資料變成人看不懂的東西就可以稱為加密,身為一個工程師,如果搞不清楚箇中差異是會被笑的。 (就跟我被噹爆一樣…)

所幸這部分已經有前輩整理好了,以下敘述引用自 m157q 前輩的部落格 - 如何區分加密、壓縮、編碼,感謝前輩辛苦整理的資料。

加密 (Encrypt)

對稱式加密 (Symmetric Encryption)

  • 首先產生一個新的字串作為密鑰,也就是一把鑰匙
    • 可以想像成,加密演算法幫你打造出你給它的這把密鑰才可以開啟的寶箱,幫你把原文放入寶箱後,用這把密鑰上鎖,上鎖後的寶盒就是密文,看不到裡面的東西是什麼
    • 這種只有一把鑰匙的加密演算法被稱為對稱式加密 (Symmetric Encryption)

然而對稱式加密的安全性以及在實際應用上不夠理想,於是出現了安全性更高,應用範圍更廣的非對稱式加密 (Asymmetric Encryption)

非對稱式加密 (Asymmetric Encryption)

  • 非對稱式加密演算法會有兩把鑰匙,分別為公鑰、私鑰
  • 非對稱式加密除了可以做到加密以外,還可以生成數位簽章,確認密文的傳送方身份真的是本人

兩者各有各的優缺點,所以實際應用上通常都是視情況而定。

常見演算法

  • 對稱式:DES 、 3DES 、 AES
  • 非對稱式:RSA 、 DSA 、 ECC

根據這一段加密的介紹,再回頭過來想使用 Base64 的場景,可推出如下結論:
使用 base64 的時候不需要密鑰,而且任何人編碼的 base64 訊息,誰都可以經過 base64 解碼回來,所以 base64 不是加密。

編碼 (Encoding)

編碼牽涉的範圍非常廣,如:

  • 字元編碼 (Character Encoding)
  • 音訊編碼 (Audio Encoding)
  • 視訊編碼 (Video Encoding)

而 Base64 屬於字元編碼的部分,而編碼的特性為:

  • 將原文轉換成另外一種表達方式
  • 不需要密鑰,只要知道使用哪個編碼演算法,任何人都可以解碼
    • 這也是單純編碼被拿來誤用成加密演算法最危險的地方,因為完全不需要花時間猜密鑰
  • 不同的編碼演算法有不同的特性
    • 錯誤偵測 (Error Detection)
      • 檢查訊息在經過傳送後是否已經改變
    • 錯誤校正 (Error Correction)
      • 自動修正在經過傳送後錯誤的內容
    • 方便資料進行傳輸
      • 以不同的形式表示相同的資料
      • 例如: base64 就把二進位的資料用 ASCII 來表示

常見演算法

RSA 非對稱式加密

由於看同事們那個時候再弄的範例有公鑰 (public Key) 、私鑰 (private Key) 之分,藉由上面的介紹可知這是一種非對稱式的加密方式,因此更進一步 Google 後,決定使用 node.js 來簡單實作看看 RSA 非對稱式加密

有關 RSA 非對稱式加密的部分,可以參考:

實作

技術實作方面,得知 Node.js 已經有相關的加密包 (crypto) 可以使用,於是可以針對這個關鍵字搜尋,得出不少可用資源,整理如下:

直接上完整程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const crypto = require('crypto');

const priPassword = `Nice Password`;
let message = `Hello`;
play2RSA(message, priPassword);

async function play2RSA(message, priPassword) {
const keyPair = await makeKeyPair(priPassword);
console.log('公鑰:', keyPair.publicKey);
console.log('私鑰:', keyPair.privateKey);
let crypted = encrypt(message, keyPair.publicKey);
console.log('加密結果:', crypted.toString('base64'));
try {
let decrypted = decrypt(crypted, keyPair.privateKey, priPassword);
console.log('解密結果:', decrypted.toString());
} catch (error) {
console.log('解密失敗');
}
};

// 加密方法
function encrypt(data, key) {
return crypto.publicEncrypt(key, Buffer.from(data));
}

// 解密方法
function decrypt(encrypted, key, priPassword) {
const keyObj = {
key: key,
passphrase: priPassword
}
return crypto.privateDecrypt(keyObj, encrypted);
}

// 建立 KeyPair, 並單獨針對私鑰再進行一次加密
function makeKeyPair(priPassword) {
return new Promise((resolve, reject) => {
crypto.generateKeyPair('rsa', {
modulusLength: 4096,
publicKeyEncoding: {
type: 'spki',
format: 'pem',
},
privateKeyEncoding: {
type: 'pkcs8',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: priPassword
}
}, (err, publicKey, privateKey) => {
const keyPair = {
publicKey: publicKey,
privateKey: privateKey
}
err !== null ? reject(err) : resolve(keyPair);
});
});
}

這段程式主要是按照希望的格式產生公、私鑰之後,接著使用公鑰加密 message 變數的字串,最後在使用私鑰解密。

但因為私鑰的部分,我額外的上了一層加密,所以在進行解密的時候需要額外帶入私鑰的密碼才可以順利進行。

0%