本文章同时发布于:
MediumiT 邦帮忙大家好,继上次Week38 - 各种安全性演算法的应用 - 概念篇之后,这次要介绍实做,以下程式码都会使用 Golang 撰写。
实作
以下实作大量参考Golang RSA encrypt and decrypt example与Golang: aes-256-cbc examples (with iv, blockSize)。
并且全部的範例都在此,请先 clone 下来会较好理解。
窃听(eavesdrop)
还记得上篇文章 - 「窃听(eavesdrop)是什么?」章节防範的方法吗?就是
将传送的资料加密,这样就算坏蛋偷走了也不知道资料内容
接下来就要利用加密
讯息的方式来实作,常见的加密有 2 种:
公开金钥加密(Public-key cryptography),又称非对称加密:
拥有私钥与公钥 2 把钥匙公钥可以给任何人,私钥不可以给自己以外的人加密使用公钥,解密使用私钥对称密钥演算法(Symmetric-key algorithm):
拥有 1 把钥匙这把钥匙不可以给不够信任的人加密跟解密都用此钥匙小明
与早餐店阿姨
如果不够信任彼此
,会採用公开金钥加密
,因为採用对称密钥演算法
的话,早餐店阿姨
把钥匙给小明
,小明
把这把钥匙公开给别人就不好了,这样大家都可以解开早餐店阿姨
的加密资料,
使用公开金钥加密
加在原本的循序图串起来就会如下:
看看程式码,进入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/c0c5ece512f63d429c4589196a1d4242package mainimport ("crypto/rand""crypto/rsa""crypto/sha512""crypto/x509""encoding/pem""fmt""io/ioutil""log")// LoadFile load the file to bytesfunc LoadFile(path string) []byte {content, err := ioutil.ReadFile(path)if err != nil {log.Fatal(err)}return content}// BytesToPrivateKey bytes to private keyfunc BytesToPrivateKey(priv []byte) *rsa.PrivateKey {block, _ := pem.Decode(priv)enc := x509.IsEncryptedPEMBlock(block)b := block.Bytesvar err errorif 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 keyfunc BytesToPublicKey(pub []byte) *rsa.PublicKey {block, _ := pem.Decode(pub)enc := x509.IsEncryptedPEMBlock(block)b := block.Bytesvar err errorif 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 keyfunc 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 keyfunc 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/57fdc3264b1927ef0f984b533d63ababpackage mainimport ("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)
还记得上篇文章 - 「电子欺骗(spoofing)是什么?」章节防範的方法吗?就是
传输人员在资料上产生一笔独一无二的代码供另一端验证
接下来就要利用产生独一无二的代码
来实作,方法有 2 种:
数位签章(Digital Signature):
拥有私钥与公钥 2 把钥匙公钥可以给任何人,私钥不可以给自己以外的人使用私钥产生代码,公钥验证代码讯息识别码(Message authentication code):
拥有 1 把钥匙这把钥匙不可以给不够信任的人使用此钥匙来产生代码小明
与早餐店阿姨
如果不够信任彼此
,会採用数位签章
,因为採用讯息识别码
的话,小明
把钥匙给早餐店阿姨
,早餐店阿姨
拿去冒名成小明
就不好了,
使用数位签章
加在原本的循序图串起来就会如下:
看看程式码,进入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/c0c5ece512f63d429c4589196a1d4242package mainimport ("crypto""crypto/rand""crypto/rsa""crypto/sha512""crypto/x509""encoding/pem""fmt""io/ioutil""log")// LoadFile load the file to bytesfunc LoadFile(path string) []byte {content, err := ioutil.ReadFile(path)if err != nil {log.Fatal(err)}return content}// BytesToPrivateKey bytes to private keyfunc BytesToPrivateKey(priv []byte) *rsa.PrivateKey {block, _ := pem.Decode(priv)enc := x509.IsEncryptedPEMBlock(block)b := block.Bytesvar err errorif 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 mainimport ("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 := "小明餐点: 大冰红"// 坏人利用自己的钥匙产生HMACbadGuyHMAC := hmacSha256(meals, badGuySecret)// 早餐店阿姨利用与小明共同的钥匙产生HMACtrueHMAC := hmacSha256(meals, sharedSecret)// 早餐店阿姨比对此两个HMAC,发现不同,故此讯息不是小明传送的if badGuyHMAC != trueHMAC {fmt.Println("Two HMACs are not the same.")return}}