本文章同時發佈於:
大家好,繼上次Week38 - 各種安全性演算法的應用 - 概念篇之後,這次要介紹實做,以下程式碼都會使用 Golang 撰寫。
以下實作大量參考Golang RSA encrypt and decrypt example與Golang: aes-256-cbc examples (with iv, blockSize)。
並且全部的範例都在此,請先 clone 下來會較好理解。
還記得上篇文章 - 「竊聽(eavesdrop)是什麼?」章節防範的方法嗎?就是
將傳送的資料加密,這樣就算壞蛋偷走了也不知道資料內容
接下來就要利用加密
訊息的方式來實作,常見的加密有 2 種:
小明
與早餐店阿姨
如果不夠信任彼此
,會採用公開金鑰加密
,因為採用對稱密鑰演算法
的話,早餐店阿姨
把鑰匙給小明
,小明
把這把鑰匙公開給別人就不好了,這樣大家都可以解開早餐店阿姨
的加密資料,
使用公開金鑰加密
加在原本的循序圖串起來就會如下:
看看程式碼,進入week39/eavesdrop/public
的資料夾:
$ cd week39/eavesdrop/public
裡頭有以下檔案:
.
├── key : 私鑰
├── key.pub: 公鑰
└── main.go: 程式碼
key
與key.pub
是透過ssh-keygen
這個軟體來產生的,使用以下指令可以產生一組RSA
公私鑰:
$ ssh-keygen -t rsa -f key -m pem
輸入後會詢問是否要設定passphrase
,這是一個安全密碼,如果設定了,以後使用此私鑰還要輸入此安全密碼才可使用,以增加安全性,此範例沒有設定。
code 主要可以看main
的部分,註解有解釋流程,搭配循序圖會較好理解:
// 大量參考: https://gist.github.com/mfridman/c0c5ece512f63d429c4589196a1d4242
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
)
// LoadFile load the file to bytes
func LoadFile(path string) []byte {
content, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
return content
}
// BytesToPrivateKey bytes to private key
func BytesToPrivateKey(priv []byte) *rsa.PrivateKey {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Fatal(err)
}
}
key, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
log.Fatal(err)
}
return key
}
// BytesToPublicKey bytes to public key
func BytesToPublicKey(pub []byte) *rsa.PublicKey {
block, _ := pem.Decode(pub)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Fatal(err)
}
}
ifc, err := x509.ParsePKIXPublicKey(b)
if err != nil {
log.Fatal(err)
}
key, ok := ifc.(*rsa.PublicKey)
if !ok {
log.Fatal("not ok")
}
return key
}
// EncryptWithPublicKey encrypts data with public key
func EncryptWithPublicKey(msg []byte, pub *rsa.PublicKey) []byte {
hash := sha512.New()
ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pub, msg, nil)
if err != nil {
log.Fatal(err)
}
return ciphertext
}
// DecryptWithPrivateKey decrypts data with private key
func DecryptWithPrivateKey(ciphertext []byte, priv *rsa.PrivateKey) []byte {
hash := sha512.New()
plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, priv, ciphertext, nil)
if err != nil {
log.Fatal(err)
}
return plaintext
}
func main() {
// 早餐店阿姨產生公私鑰
privateKey := BytesToPrivateKey(LoadFile("./key"))
// 公鑰可以透過私要來取得,所以這邊就不在載入公鑰檔案了
publicKey := &privateKey.PublicKey
// 小明獲得早餐店阿姨的公鑰,利用此公鑰加密
encryptedMsg := EncryptWithPublicKey([]byte("小明的付款密碼: 12345"), publicKey)
// 小明將 encryptedMsg 傳送給早餐店阿姨
// 早餐店阿姨使用私鑰解開此訊息
msg := DecryptWithPrivateKey(encryptedMsg, privateKey)
fmt.Println(string(msg))
}
小明
與早餐店阿姨
如果夠信任彼此
,甚至他們可能是同一個系統,那就不必擔心小明
拿早餐店阿姨
的鑰匙做壞事了,故可採用對稱密鑰演算法
,
使用對稱密鑰演算法
加在原本的循序圖串起來就會如下:
看看程式碼,進入week39/eavesdrop/symmetric
的資料夾:
$ cd week39/eavesdrop/symmetric
裡頭有以下檔案:
.
└── main.go: 程式碼
code 主要可以看main
的部分,註解有解釋流程,搭配循序圖會較好理解:
// 大量參考: https://gist.github.com/yingray/57fdc3264b1927ef0f984b533d63abab
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"log"
)
func Ecrypt(plaintext string, key []byte, iv []byte, blockSize int) string {
pad := func(ciphertext []byte, blockSize int, after int) []byte {
padding := (blockSize - len(ciphertext)%blockSize)
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
bPlaintext := pad([]byte(plaintext), blockSize, len(plaintext))
block, _ := aes.NewCipher(key)
ciphertext := make([]byte, len(bPlaintext))
mode := cipher.NewCBCEncrypter(block, iv)
mode.CryptBlocks(ciphertext, bPlaintext)
return hex.EncodeToString(ciphertext)
}
func Decrypt(ciphertext string, key []byte, iv []byte) string {
unpad := func(ciphertext []byte) []byte {
length := len(ciphertext)
unpadding := int(ciphertext[length-1])
return ciphertext[:(length - unpadding)]
}
decodeData, _ := hex.DecodeString(ciphertext)
block, _ := aes.NewCipher(key)
blockMode := cipher.NewCBCDecrypter(block, iv)
originData := make([]byte, len(decodeData))
blockMode.CryptBlocks(originData, decodeData)
return string(unpad(originData))
}
func main() {
// 小明與早餐店阿姨共同的鑰匙
key := []byte("di93bi39a^*(2i$2ajg9^ha9fj@hswe(")
// 這邊比較特別一點,由於是使用CBC演算法,所以在加密與解密時會多一個隨機數iv,
// 這可以讓「相同的明文加密後,會產生不同的加密訊息」,以避免壞人透過相同的加密訊息來推斷資訊
iv := make([]byte, aes.BlockSize)
if _, err := rand.Read(iv); err != nil {
log.Fatal(err)
}
// 小明透過鑰匙加密訊息
plaintext := "小明的付款密碼: 12345"
ecryptMsg := Ecrypt(plaintext, key, iv, aes.BlockSize)
// 早餐店阿姨透過鑰匙解密訊息
decryptMsg := Decrypt(ecryptMsg, key, iv)
fmt.Println("早餐店阿姨", decryptMsg)
}
還記得上篇文章 - 「電子欺騙(spoofing)是什麼?」章節防範的方法嗎?就是
傳輸人員在資料上產生一筆獨一無二的代碼供另一端驗證
接下來就要利用產生獨一無二的代碼
來實作,方法有 2 種:
小明
與早餐店阿姨
如果不夠信任彼此
,會採用數位簽章
,因為採用訊息識別碼
的話,小明
把鑰匙給早餐店阿姨
,早餐店阿姨
拿去冒名成小明
就不好了,
使用數位簽章
加在原本的循序圖串起來就會如下:
看看程式碼,進入week39/spoofing/signature
的資料夾:
$ cd week39/spoofing/signature
裡頭有以下檔案:
.
├── badGuyKey : 壞人的私鑰
├── badGuyKey.pub : 壞人的公鑰
├── goodGuykey : 小明的私鑰
├── goodGuykey.pub: 小明的公鑰
└── main.go : 程式碼
鑰匙都是透過ssh-keygen
產生,可以參考上方竊聽(eavesdrop)
章節的講解,就不贅述。
code 主要可以看main
的部分,註解有解釋流程,搭配循序圖會較好理解:
// 大量參考: https://gist.github.com/mfridman/c0c5ece512f63d429c4589196a1d4242
package main
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha512"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"log"
)
// LoadFile load the file to bytes
func LoadFile(path string) []byte {
content, err := ioutil.ReadFile(path)
if err != nil {
log.Fatal(err)
}
return content
}
// BytesToPrivateKey bytes to private key
func BytesToPrivateKey(priv []byte) *rsa.PrivateKey {
block, _ := pem.Decode(priv)
enc := x509.IsEncryptedPEMBlock(block)
b := block.Bytes
var err error
if enc {
log.Println("is encrypted pem block")
b, err = x509.DecryptPEMBlock(block, nil)
if err != nil {
log.Fatal(err)
}
}
key, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
log.Fatal(err)
}
return key
}
func main() {
// 壞人的私鑰
badGuyPrivateKey := BytesToPrivateKey(LoadFile("./badGuyKey"))
// 小明的公鑰,公鑰可以透過私要來取得,所以這邊就不在載入公鑰檔案了
goodGuyPublicKey := BytesToPrivateKey(LoadFile("./goodGuyKey")).PublicKey
// 壞人開始偽造小明的訊息
messageBytes := []byte("小明餐點: 大冰紅")
hash := sha512.New()
hash.Write(messageBytes)
hashed := hash.Sum(nil)
// 壞人用自己的私鑰簽名,並非小明的
signature, err := rsa.SignPKCS1v15(rand.Reader, badGuyPrivateKey, crypto.SHA512, hashed)
if err != nil {
panic(err)
}
// 早餐店阿姨取得小明的公鑰,利用此公鑰驗證之後發現不是小明傳的訊息
err = rsa.VerifyPKCS1v15(&goodGuyPublicKey, crypto.SHA512, hashed, signature)
if err != nil {
fmt.Println("Two signatures are not the same. Error: ", err)
return
}
}
小明
與早餐店阿姨
如果夠信任彼此
,甚至他們可能是同一個系統,那就不必擔心早餐店阿姨
拿小明
的鑰匙做壞事了,故可採用訊息識別碼
,
使用訊息識別碼
加在原本的循序圖串起來就會如下:
看看程式碼,進入week39/spoofing/HMAC
的資料夾:
$ cd week39/spoofing/HMAC
裡頭有以下檔案:
.
└── main.go : 程式碼
code 主要可以看main
的部分,註解有解釋流程,搭配循序圖會較好理解:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"fmt"
)
func hmacSha256(data string, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return hex.EncodeToString(h.Sum(nil))
}
func main() {
sharedSecret := "小明與早餐店阿姨的共同鑰匙"
badGuySecret := "壞人的鑰匙"
meals := "小明餐點: 大冰紅"
// 壞人利用自己的鑰匙產生HMAC
badGuyHMAC := hmacSha256(meals, badGuySecret)
// 早餐店阿姨利用與小明共同的鑰匙產生HMAC
trueHMAC := hmacSha256(meals, sharedSecret)
// 早餐店阿姨比對此兩個HMAC,發現不同,故此訊息不是小明傳送的
if badGuyHMAC != trueHMAC {
fmt.Println("Two HMACs are not the same.")
return
}
}