go-playground:
Last active
August 23, 2024 11:09
-
-
Save CarsonSlovoka/1876a3ae7cd821a201d39aa96beccffe to your computer and use it in GitHub Desktop.
GPG(GNU Privacy Guard)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"crypto/aes" | |
"crypto/cipher" | |
"crypto/rand" | |
"crypto/rsa" | |
"crypto/sha256" | |
"encoding/binary" | |
"fmt" | |
"io" | |
"testing" | |
) | |
func aesEncrypt(key, plaintext []byte) ([]byte, error) { | |
block, err := aes.NewCipher(key) // key長度有限制必須為16, 24, 32. 對應 AES-128, AES-192, or AES-256 | |
if err != nil { | |
return nil, err | |
} | |
// 整個密文的長度 = blockSize + len(plaintext) | |
ciphertext := make([]byte, aes.BlockSize+len(plaintext)) | |
// 生成block的內容 | |
iv := ciphertext[:aes.BlockSize] | |
if _, err = io.ReadFull(rand.Reader, iv); err != nil { | |
return nil, err | |
} | |
stream := cipher.NewCFBEncrypter(block, iv) | |
stream.XORKeyStream( // 會執行cfb.XORKeyStream, 以plaintext和提供的iv, 計算結果存到ciphertext[aes.BlockSize:]去 | |
ciphertext[aes.BlockSize:], // 這是輸出。此輸出搭配真實的密鑰,才可以反算回來 | |
plaintext, | |
) | |
return ciphertext, nil | |
} | |
func aesDecrypt(key, ciphertext []byte) ([]byte, error) { | |
block, err := aes.NewCipher(key) | |
if err != nil { | |
return nil, err | |
} | |
if len(ciphertext) < aes.BlockSize { | |
return nil, fmt.Errorf("ciphertext too short") | |
} | |
iv := ciphertext[:aes.BlockSize] | |
ciphertext = ciphertext[aes.BlockSize:] | |
stream := cipher.NewCFBDecrypter(block, iv) | |
stream.XORKeyStream(ciphertext, ciphertext) | |
return ciphertext, nil | |
} | |
func GPGEncrypt(data []byte, publicKey *rsa.PublicKey, symmetricKey []byte) ([]byte, error) { | |
// 使用一個對稱式加密演算法來生成會話的密鑰,假設我們使用AES來加密 | |
// 被加密起來的訊息 | |
encryptData, err := aesEncrypt(symmetricKey, data) | |
if err != nil { | |
return nil, err | |
} | |
// 使用公鑰加密訊息 | |
iHash := sha256.New() | |
// 用對方的公鑰對會話密鑰加簽, 對方可以透過此公鑰的對應的私鑰將其還原 | |
sessionKeyBytes, err := rsa.EncryptOAEP(iHash, rand.Reader, publicKey, symmetricKey, nil) | |
if err != nil { | |
return nil, err | |
} | |
// 以下只是隨便模擬一個簡單的過程,主要是接收方有辦法識別加簽起來的內容與加簽起來的密鑰 | |
type Header struct { | |
DataSize uint32 | |
KeySize uint32 | |
} | |
encryptedMessageBuf := bytes.NewBuffer(nil) | |
_ = binary.Write(encryptedMessageBuf, binary.BigEndian, &Header{ | |
DataSize: uint32(len(encryptData)), | |
KeySize: uint32(len(sessionKeyBytes)), | |
}) | |
encryptedMessageBuf.Write(encryptData) | |
encryptedMessageBuf.Write(sessionKeyBytes) | |
return encryptedMessageBuf.Bytes(), nil | |
} | |
func GPGDecrypt(privateKey *rsa.PrivateKey, encryptedMessage []byte) (orgData []byte, sessionKey []byte, err error) { | |
// 解析接收的資料內容 | |
// 這邊只是模擬接收到的資料結構,實際上的資料結構更為複雜 | |
type ReceiveData struct { | |
MsgSize uint32 | |
KeySize uint32 | |
Msg []byte | |
Key []byte | |
} | |
var msgSize uint32 | |
msgSize = binary.BigEndian.Uint32(encryptedMessage) // 這是我們自己訂的結構,其一開始4byte表示訊息長度 | |
var receive ReceiveData | |
receive.Msg = encryptedMessage[8 : 8+msgSize] // 8為表頭前8byte(msgSize, keySize) | |
receive.Key = encryptedMessage[8+msgSize:] | |
// 解出會話用的真實密鑰pairKey | |
sessionKey, err = rsa.DecryptOAEP(sha256.New(), rand.Reader, // 此為OAEP的特色,對方已經用己方提供的公鑰加密,用自己的私鑰能解密 | |
privateKey, // 接收方的私鑰理論上只有自己才會有,因此只要沒有這個私鑰,就沒有辦法把會話密鑰還原 | |
receive.Key, nil, | |
) | |
if err != nil { | |
return nil, nil, err | |
} | |
// 使用會話密鑰將內容還原 | |
orgData, err = aesDecrypt(sessionKey, receive.Msg) | |
return orgData, sessionKey, nil | |
} | |
func Test_rsaOAEP(t *testing.T) { | |
// 模擬取得接收方的公鑰 | |
receiverKey, err := rsa.GenerateKey(rand.Reader, 2048) | |
if err != nil { | |
t.Fatal(err) | |
} | |
receiverPublicKey := &receiverKey.PublicKey | |
// 加簽過程: 用公鑰加密 | |
realMessage := []byte("這是一個秘密訊息") | |
// 模擬隨機生成對稱式密鑰 | |
const defaultKeySize = 16 // 由於我們採用AES演算法,他的私鑰長度有限制,對應的長度分別為16, 24, 32對應AES-128, AES-192, or AES-256. | |
symmetricKey := make([]byte, defaultKeySize) | |
if _, err = rand.Read(symmetricKey); err != nil { | |
t.Fatal(err) | |
} | |
encryptedMessage, err := GPGEncrypt(realMessage, receiverPublicKey, symmetricKey) | |
if err != nil { | |
t.Fatal(err) | |
} | |
expectedMsg, symmetricKey2, err := GPGDecrypt(receiverKey, encryptedMessage) | |
if err != nil { | |
t.Fatal(err) | |
} | |
// 驗證 | |
if !bytes.Equal(symmetricKey, symmetricKey2) { | |
t.Fatal("密鑰不同", err) | |
} | |
if !bytes.Equal(expectedMsg, realMessage) { | |
t.Fatal("內容不一致") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment