密碼學看似一個無聊的學科,但是卻也不斷充滿著我們的生活中。
無論是比特幣或是區塊鏈,密碼學都是扮演著相當重要的角色。
以下將會先介紹密碼學最經典的Hash是什麼。

Why 密碼學?

密碼學簡單來說,就是一個藉由數學與邏輯進行加解密的一種學科,其實在無形之中,你已經在生活中用到了各種加解密了。
生活上,我們會用到暗號,可以視為雙方都有共通的解讀方式。
網路上,無論是登入登出, 信用卡消費, 貨幣交易……,都會用到。
密碼學是一個要學的廣簡單,學得深很難的學科,但是也因為在生活應用上非常常用到,所以近日才會逐漸推廣全民學資安。
事實上我蠻推薦任何背景的人去了解密碼學,儘管只是學個表面知識,對生活上的應用上,還是有一定的幫助的。

Hash

通常台灣比較會唸成雜湊,對岸則是翻譯成哈希。 Hash事實上是透過Hash function產生的。 無論輸入的長度為多少,經過Hash function執行之後,都會產生固定長度的hash值。 事實上雜湊又稱為one-way function,一旦翻譯成hash,即無法逆向翻譯成原先的值。

Hash 小整理

  1. 輸入的長度不管長短,產生的Hash值長度都是固定的。
  2. 兩者的Hash值不同,表示輸入的內容一定不同。
  3. 兩者的Hash值相同,表示輸入的內容可能相同,但是無法保證100%一樣。

Hash function 小整理

  1. 不需要像加解密演算法一樣需要Key。
  2. 計算的速度很快,即使檔案非常大。
  3. 相同的訊息輸入,總是產生一樣的Hash。
  4. 極難產生collision。
  5. 極難從Hash值,推回原本的值是多少。
  6. 些微的訊息輸入變化,Hash值會完全不一樣。

資安議題

  1. 攻擊者時常會利用相同的訊息輸入,總是產生一樣的Hash這個特性去做攻擊,先預先儲存常見的各種輸入訊息,當需要使用的時候就會拿出來做比對。如果一個系統的密碼是用Hash儲存的話,那麼攻擊者會儲存這些值,再慢慢的將輸入得到的值去做比對,最後取得真正的密碼。
  2. 極難從Hash值,推回原本的值是多少,不代表無法將值逆推回原本的值,只是代表沒有一個很有效的解決方案可以達到這個目的,也許在未來,Hash值是有這個可能有效逆推的。

不建議使用的雜湊演算法

  1. MD5
    • MD5在2004年已經被證實是無法抵擋碰撞攻擊的,以現在的科技來說,你甚至只要拿一個手機就能在30秒之內算出兩個不同的值卻產生同一個Hash值的碰撞。
  2. SHA-1
    • SHA-1則是在近期被Google發現了第一個產生碰撞的值,Google也為了這個事件建立一個網頁紀念此事 簡單來說就是,他們將兩個pdf檔拿去做SHA-1的計算的時候,會產生一樣的Hash值。 Google透過大量的計算去產生碰撞,不代表現階段就要把SHA-1停止使用。 Google只是要讓大家意識到,SHA-1事實上在現在甚至是未來,是不安全的,希望各位未來在開發上可以使用更安全的演算法。 有興趣的話,你可以把pdf下載下來去玩玩看。

建議使用的雜湊演算法

  1. SHA-2
    • SHA-256, SHA-512
    • Merkle-Damgard construction
    • Bitcoin

即便目前尚未發現有對SHA-2較有效的攻擊,但是都是基於早期的演算法進行實作的,所以難保以後不會找到弱點。
因此在2015年就有人宣告,希望能有更不容易被攻破的演算法,但是產生出來的Hash長度需要一致。
有任何問題的話,隨時可以從SHA-2轉換成SHA-3,而不會有任何影響。

  1. SHA-3
    • SHA3-256, SHA3-512
    • Keccak
    • Ethereum Blockchain

SHA-3目前比較常用到的是在區塊鏈上,相信有在關注的人,應該會知道這件事情。

Demo展示

以下將會用JS-SHA這個套件來展示一樣上面提到的雜湊演算法有什麼不一樣。
程式的使用方式請比照如下:

  1. 建立專案初始化
    1
    npm init
  2. 新增程式檔
  3. 將以下dependencies加入package.json
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "dependencies": {
    "aes-js": "^3.1.2",
    "argon2": "^0.26.2",
    "bcryptjs": "^2.4.3",
    "eccrypto": "^1.1.3",
    "fast-levenshtein": "^2.0.6",
    "jose": "^1.27.0",
    "jssha": "^3.1.0",
    "mersenne-twister": "^1.1.0",
    "node-forge": "^0.9.1",
    "otplib": "^12.0.1",
    "qrcode": "^1.4.4",
    "uuid": "^8.0.0"
    }
  4. 安裝dependencies
    1
    npm install
  5. 執行程式
    • JS請用
      1
      node main.js
    • TS請用
      1
      npx ts-node main.ts

Universally Unique Identifier - a.k.a. UUID

下面會用到UUID v4,所以先介紹一下是什麼。
UUID是128 bits的識別碼,因為編碼長度非常大,因此可以有好幾兆以上的不同組合,所以可以用來當作唯一編碼。
比較常用到的版本為下:

  • v1:透過MAC的地址和時間來產生。
  • v3:透過串接命名空間和名稱後,計算其MD5雜湊值來產生。
  • v4:亂數產生。
  • v5:透過串接命名空間和名稱後,計算其SHA1雜湊值來產生。
  • 上面已經有提到MD5與SHA-1的議題了,所以也不建議使用v3與v5。

主程式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import JsSHA from 'jssha';
import { v4 } from 'uuid';

const data = v4();
demo();

function demo() {
console.log(`Text : ${data}`);
console.log(`SHA-1 : ${sha1(data)}`);
console.log(`SHA2-256 : ${sha256(data)}`);
console.log(`SHA2-512 : ${sha512(data)}`);
console.log(`SHA3-256 : ${sha3_256(data)}`);
console.log(`SHA3-512 : ${sha3_512(data)}`);
}

各個雜湊演算法

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
function sha1(data) {
const shaObject = new JsSHA('SHA-1', 'TEXT');
shaObject.update(data);
return shaObject.getHash('HEX');
}

function sha256(data) {
const shaObject = new JsSHA('SHA-256', 'TEXT');
shaObject.update(data);
return shaObject.getHash('HEX');
}

function sha512(data) {
const shaObject = new JsSHA('SHA-512', 'TEXT');
shaObject.update(data);
return shaObject.getHash('HEX');
}

function sha3_256(data) {
const shaObject = new JsSHA('SHA3-256', 'TEXT');
shaObject.update(data);
return shaObject.getHash('HEX');
}

function sha3_512(data) {
const shaObject = new JsSHA('SHA3-512', 'TEXT');
shaObject.update(data);
return shaObject.getHash('HEX');
}

輸出結束

執行之後,大概會有這樣的輸出:

1
2
3
4
5
6
Text     : f6fd0b62-2cb4-4d11-9308-55f460a929c0
SHA-1 : f46d0920580f31d9fbb0555805d31fd5266db62f
SHA2-256 : eb5f52ff9d3b93c487345eecbe3e50596a51711270f6bbaffa94ab146950daee
SHA2-512 : cf5b6756a8f7cf1a2a21c10a179278980f49e7514603ab3ceeac29b9405b4bbfab791921c511190f5c15fabecd8d8f1e34f288055c154675c5be3df37bf92a01
SHA3-256 : 021abfeeb3e80970517d91f3c1432e4cb7eea9abcc60ff754f4193e02642e0fa
SHA3-512 : 4891301e6018a64a78b4bcc8da2bc0e53e40cc039127590bc769701dec3a287c5fe6fd6db837ca6627dce3a7f6262a239ef062dbd819588d0a61311c934c1b4d
  • 你會觀察到
    1. SHA-1和SHA-2長度不一樣
    2. SHA-2和SHA-3長度一樣,但是值卻不一樣,表示經過的計算也不同。

如果只改一個字元呢


上面有提到,一點點小差距產生出來的Hash會有極大的不同,那麼以下就來示範一下。

  1. 我們將Demo的程式換成以下,僅修改第一個字元:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function demo2() {
    const modifiedData = `#${data.substring(1)}`;
    console.log(`Text : ${data}`);
    console.log(`SHA2-256 : ${sha256(data)}`);
    console.log(`Modified Text : ${modifiedData}`);
    console.log(`SHA2-256 : ${sha256(modifiedData)}`);
    console.log(`Distance of Text : ${get(data, modifiedData)}`);
    console.log(`Distance of Hash : ${get(sha256(data), sha256(modifiedData))}`);
    }
  2. 為了要證明輸出的結果有多大的Distance,這邊會使用一個演算法去計算Levenshtein Distance,表示文字之間的距離。
    主要規則為,兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。
    操作包含:

    1. 將一個字符替換成另一個字符
    2. 插入一個字符
    3. 刪除一個字符

你不需要真的理解這個演算法的原理,你只需要知道這個是用來計算距離用的。
因此我們需要輸入這個方法:

1
import { get } from 'fast-levenshtein';
  1. 輸出結果
    1
    2
    3
    4
    5
    6
    Text             : f6fd0b62-2cb4-4d11-9308-55f460a929c0
    SHA2-256 : eb5f52ff9d3b93c487345eecbe3e50596a51711270f6bbaffa94ab146950daee
    Modified Text : @6fd0b62-2cb4-4d11-9308-55f460a929c0
    SHA2-256 : 92968316b5fc1995cb7bee2ff53148ba2976ce9ed336f4613bbb5fe0abbb8e37
    Distance of Text : 1
    Distance of Hash : 58
    你會發現:
  2. 原始資料的距離,輸出結果是1。
  3. 經過Hash之後,兩者的距離高達了58。

雜湊不等於加解密


有時候你會在一些Hash演算法的網站上看到加解密的字樣,例如這裡
但是事實上這並不是一個正確的資訊,雜湊事實上並非加解密。

  1. 加密通常伴隨著解密的過程,但是Hash是單向的。
  2. 加密通常會有Key,但Hash不需要Key。

小總結

  1. Hash值不同,輸入的值一定不同。
  2. 當Hash值相同,有很高的機率是一樣的值。
  3. 雜湊演算法可以拿來檢查訊息的完整性。
  4. 建議使用SHA-2, SHA-3。