隨筆經典的密碼學-Hash篇
密碼學看似一個無聊的學科,但是卻也不斷充滿著我們的生活中。
無論是比特幣或是區塊鏈,密碼學都是扮演著相當重要的角色。
以下將會先介紹密碼學最經典的Hash是什麼。
Why 密碼學?
密碼學簡單來說,就是一個藉由數學與邏輯進行加解密的一種學科,其實在無形之中,你已經在生活中用到了各種加解密了。
生活上,我們會用到暗號,可以視為雙方都有共通的解讀方式。
網路上,無論是登入登出, 信用卡消費, 貨幣交易……,都會用到。
密碼學是一個要學的廣簡單,學得深很難的學科,但是也因為在生活應用上非常常用到,所以近日才會逐漸推廣全民學資安。
事實上我蠻推薦任何背景的人去了解密碼學,儘管只是學個表面知識,對生活上的應用上,還是有一定的幫助的。
Hash
通常台灣比較會唸成雜湊,對岸則是翻譯成哈希。 Hash事實上是透過Hash function產生的。 無論輸入的長度為多少,經過Hash function執行之後,都會產生固定長度的hash值。 事實上雜湊又稱為one-way function,一旦翻譯成hash,即無法逆向翻譯成原先的值。Hash 小整理
- 輸入的長度不管長短,產生的Hash值長度都是固定的。
- 兩者的Hash值不同,表示輸入的內容一定不同。
- 兩者的Hash值相同,表示輸入的內容可能相同,但是無法保證100%一樣。
Hash function 小整理
- 不需要像加解密演算法一樣需要Key。
- 計算的速度很快,即使檔案非常大。
- 相同的訊息輸入,總是產生一樣的Hash。
- 極難產生collision。
- 極難從Hash值,推回原本的值是多少。
- 些微的訊息輸入變化,Hash值會完全不一樣。
資安議題
- 攻擊者時常會利用相同的訊息輸入,總是產生一樣的Hash這個特性去做攻擊,先預先儲存常見的各種輸入訊息,當需要使用的時候就會拿出來做比對。如果一個系統的密碼是用Hash儲存的話,那麼攻擊者會儲存這些值,再慢慢的將輸入得到的值去做比對,最後取得真正的密碼。
- 極難從Hash值,推回原本的值是多少,不代表無法將值逆推回原本的值,只是代表沒有一個很有效的解決方案可以達到這個目的,也許在未來,Hash值是有這個可能有效逆推的。
不建議使用的雜湊演算法
- MD5
- MD5在2004年已經被證實是無法抵擋碰撞攻擊的,以現在的科技來說,你甚至只要拿一個手機就能在30秒之內算出兩個不同的值卻產生同一個Hash值的碰撞。
- SHA-1
- SHA-1則是在近期被Google發現了第一個產生碰撞的值,Google也為了這個事件建立一個網頁紀念此事。 簡單來說就是,他們將兩個pdf檔拿去做SHA-1的計算的時候,會產生一樣的Hash值。 Google透過大量的計算去產生碰撞,不代表現階段就要把SHA-1停止使用。 Google只是要讓大家意識到,SHA-1事實上在現在甚至是未來,是不安全的,希望各位未來在開發上可以使用更安全的演算法。 有興趣的話,你可以把pdf下載下來去玩玩看。
建議使用的雜湊演算法
- SHA-2
- SHA-256, SHA-512
- Merkle-Damgard construction
- Bitcoin
即便目前尚未發現有對SHA-2較有效的攻擊,但是都是基於早期的演算法進行實作的,所以難保以後不會找到弱點。
因此在2015年就有人宣告,希望能有更不容易被攻破的演算法,但是產生出來的Hash長度需要一致。
有任何問題的話,隨時可以從SHA-2轉換成SHA-3,而不會有任何影響。
- SHA-3
- SHA3-256, SHA3-512
- Keccak
- Ethereum Blockchain
SHA-3目前比較常用到的是在區塊鏈上,相信有在關注的人,應該會知道這件事情。
Demo展示
以下將會用JS-SHA這個套件來展示一樣上面提到的雜湊演算法有什麼不一樣。
程式的使用方式請比照如下:
- 建立專案初始化
1
npm init
- 新增程式檔
- 將以下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"
} - 安裝dependencies
1
npm install
- 執行程式
- JS請用
1
node main.js
- TS請用
1
npx ts-node main.ts
- JS請用
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 | import JsSHA from 'jssha'; |
各個雜湊演算法
1 | function sha1(data) { |
輸出結束
執行之後,大概會有這樣的輸出:
1 | Text : f6fd0b62-2cb4-4d11-9308-55f460a929c0 |
- 你會觀察到
- SHA-1和SHA-2長度不一樣
- SHA-2和SHA-3長度一樣,但是值卻不一樣,表示經過的計算也不同。
如果只改一個字元呢
上面有提到,一點點小差距產生出來的Hash會有極大的不同,那麼以下就來示範一下。
我們將Demo的程式換成以下,僅修改第一個字元:
1
2
3
4
5
6
7
8
9function 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))}`);
}為了要證明輸出的結果有多大的Distance,這邊會使用一個演算法去計算Levenshtein Distance,表示文字之間的距離。
主要規則為,兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。
操作包含:- 將一個字符替換成另一個字符
- 插入一個字符
- 刪除一個字符
你不需要真的理解這個演算法的原理,你只需要知道這個是用來計算距離用的。
因此我們需要輸入這個方法:
1 | import { get } from 'fast-levenshtein'; |
- 輸出結果你會發現:
1
2
3
4
5
6Text : 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 - 原始資料的距離,輸出結果是1。
- 經過Hash之後,兩者的距離高達了58。
雜湊不等於加解密
有時候你會在一些Hash演算法的網站上看到加解密的字樣,例如這裡。
但是事實上這並不是一個正確的資訊,雜湊事實上並非加解密。
- 加密通常伴隨著解密的過程,但是Hash是單向的。
- 加密通常會有Key,但Hash不需要Key。
小總結
- Hash值不同,輸入的值一定不同。
- 當Hash值相同,有很高的機率是一樣的值。
- 雜湊演算法可以拿來檢查訊息的完整性。
- 建議使用SHA-2, SHA-3。