- I’ve recently participated in a project of management among nodes in a cluster, it’s developed in golang.
- My part is to develop a package to ensure those messages of communication between schedulers and executors are well encrypted, only receivers can decrypt them.
All codes in this article are permitted by other members in the project team to share.
Idea: This module both utilize rsa public key/private key and symmetric key.
- symmetric key: generate a random symmetric key, and use the same key to encrypt and decrypt. In this module, we use package chacha20poly1305(https://godoc.org/golang.org/x/crypto/chacha20poly1305) and “crypto/rand” to finish this step. The symmetric key here is to encrypt/decrypt those message that nodes attempt to send or receive.
- rsa keypairs: generate a pair of rsa keys as public key and private key to encrypt and decrypt. In this module, we first generate a random rsa keypair strings in keys.go, and use functions in crypting.go to handle those strings. The rsa keypairs here are to encrypt/decrypt the same symmetric key above to keep that key from being cracked by unknown hackers.
- The sender and receiver are corresponding to “scheduler” or “executor” in the project, so that there are 2 pairs of rsa keys for both characters.
1. generate key pairs
- prepare 2 pairs (as scheduler or executor) of private/public keys as generateKeyPairs.go: ```go package main
import ( “fmt”
"ire.com/clustershell/crypting"
)
const ( // variable length of generated asymmetric keys(128, 256, 512, 2048…) asymmetricKeySize = 1024 )
func main() { fmt.Println(“package keypairs\n”)
pvtkeyScheduler, pubkeyScheduler := crypting.GenerateKeyPair(asymmetricKeySize)pvtkeyExecutor, pubkeyExecutor := crypting.GenerateKeyPair(asymmetricKeySize)pvtbyteScheduler := crypting.PrivateKeyToBytes(pvtkeyScheduler)pubbyteScheduler := crypting.PublicKeyToBytes(pubkeyScheduler)pvtbyteExecutor := crypting.PrivateKeyToBytes(pvtkeyExecutor)pubbyteExecutor := crypting.PublicKeyToBytes(pubkeyExecutor)fmt.Printf("var PvtkeyScheduler = `%s`\n", string(pvtbyteScheduler))fmt.Printf("var PubkeyScheduler = `%s`\n", string(pubbyteScheduler))fmt.Printf("var PvtkeyExecutor = `%s`\n", string(pvtbyteExecutor))fmt.Printf("var PubkeyExecutor = `%s`\n", string(pubbyteExecutor))
}
- In Makefile, add these lines to redirect generateKeyPairs.go and save key pairs in keys.go:```goall:@$(MAKE) --no-print-directory generatekeypairs//....PHONY: generatekeypairsgeneratekeypairs:go run ./cmd/keygen/generateKeyPairs.go > ./crypting/keypairs/keys.go
- 2 pairs of keys are all strings
2. crypting.go update
Because 2 pairs of keys in keys.go are strings instead of rsa.PrivateKey or rsa.PublicKey, here are two new functions to handle encrypting/decrypting in crypting.go: ```go // Encrypt with public key string instead of rsa.PublicKey func Encrypt(unencrypted []byte, pubkeystr string) ([]byte, error) { pubkey := BytesToPublicKey([]byte(pubkeystr))
hash := sha1.New() ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, unencrypted, nil) if err != nil {
logger.Error(err)
}
return ciphertext, err }
// Decrypt with private key string instead of rsa.PrivateKey func Decrypt(encrypted []byte, pvtkeystr string) ([]byte, error) { pvtkey := BytesToPrivateKey([]byte(pvtkeystr))
hash := sha1.New()plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, pvtkey, encrypted, nil)if err != nil {logger.Error(err)}return plaintext, err
}
---<a name="to6ip"></a>### Test file (only use rsa keypairs):```gopackage mainimport ("fmt""ire.com/clustershell/crypting""ire.com/clustershell/crypting/keypairs")func main() {fmt.Println("\n", "kengen_crypt", "\n")pvtkeystrScheduler := keypairs.PvtkeySchedulerpubkeystrScheduler := keypairs.PubkeySchedulerfmt.Printf("Scheduler_private_key_byte_str\n%v", pvtkeystrScheduler)fmt.Printf("Scheduler_public_key_byte_str\n%v", pubkeystrScheduler)pvtkeystrExecutor := keypairs.PvtkeyExecutorpubkeystrExecutor := keypairs.PubkeyExecutorfmt.Printf("Executor_private_key_byte_str\n%v", pvtkeystrScheduler)fmt.Printf("Executor_public_key_byte_str\n%v", pubkeystrScheduler)msgScheduler := []byte("Hello World scheduler")StrencryptedScheduler, _ := crypting.EncryptWithPublicKeyStr(msgScheduler, pubkeystrScheduler)fmt.Println(string(encryptedScheduler))unencryptedScheduler, _ := crypting.DecryptWithPrivateKeyStr(encryptedScheduler, pvtkeystrScheduler)fmt.Println(string(unencryptedScheduler))msgExecutor := []byte("Hello World executor")encryptedExecutor, _ := crypting.EncryptWithPublicKeyStr(msgExecutor, pubkeystrExecutor)fmt.Println(string(encryptedExecutor))unencryptedExecutor, _ := crypting.DecryptWithPrivateKeyStr(encryptedExecutor, pvtkeystrExecutor)fmt.Println(string(unencryptedExecutor))}
3. Extension of symmetric key
- Due to the situation that only pass with rsa keypairs cannot pass long messages (for example, keypairs with length of 4096 bytes can pass messages of 470 bytes), we use packs of symmetric keys instead.
- In specific, we apply golang package chacha20poly1305
First, we have to update crypting.go for new packs and constants ```go import ( “crypto/rand” “crypto/rsa” “crypto/sha1” “crypto/x509” “encoding/pem”
“ire.com/clustershell/logger”
cryptorand “crypto/rand”
chacha “golang.org/x/crypto/chacha20poly1305” )
const ( // KeySize is the size of the key used by this AEAD, in bytes. KeySize = 32
// NonceSize is the size of the nonce used with the standard variant of this// AEAD, in bytes.NonceSize = 12// NonceSizeX is the size of the nonce used with the XChaCha20-Poly1305// variant of this AEAD, in bytes.NonceSizeX = 24
)
- The idea of symmetric key is to pass messages and encrypt/decrypt with a generated symmetric key, while we encrypt/decrypt the same symmetric key with rsa keypairs.- With packs above, here is the set of new functions in crypting.go to do symmetric key crypting:```go// Encrypt with public key string instead of rsa.PublicKey, finally return ciphertext and errorfunc EncryptWithPublicKeyStr(unencrypted []byte, pubkeystr string) ([]byte, error) {pubkey := BytesToPublicKey([]byte(pubkeystr))hash := sha1.New()ciphertext, err := rsa.EncryptOAEP(hash, rand.Reader, pubkey, unencrypted, nil)if err != nil {logger.Error(err)}return ciphertext, err}// Decrypt with private key string instead of rsa.PrivateKey, finally return plaintext and errorfunc DecryptWithPrivateKeyStr(encrypted []byte, pvtkeystr string) ([]byte, error) {pvtkey := BytesToPrivateKey([]byte(pvtkeystr))hash := sha1.New()plaintext, err := rsa.DecryptOAEP(hash, rand.Reader, pvtkey, encrypted, nil)if err != nil {logger.Error(err)}return plaintext, err}// SymmKeyGen generates new symmetric key for senderfunc SymmKeyGen(length int) []byte {key := make([]byte, KeySize)if _, err := cryptorand.Read(key); err != nil {panic(err)}return key}// EncryptMsg - encryption with symmetric key, and use rsa public key string to hide that symmetric keyfunc EncryptMsg(msg []byte, pubkeystr string) ([]byte, []byte) {keySymm := SymmKeyGen(KeySize) //generate a random symmetric key as senderaead, err := chacha.NewX([]byte(keySymm)) //both sender and receiver do this stepif err != nil {panic(err)}nonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(msg)+aead.Overhead())if _, err := cryptorand.Read(nonce); err != nil {panic(err)}encryptedMsg := aead.Seal(nonce, nonce, msg, nil) //encrypt message with symmetric keyencryptedKey, err := EncryptWithPublicKeyStr(keySymm, pubkeystr) //encrypt symmetric key with provided public keyif err != nil {panic(err)}return encryptedMsg, encryptedKey}// DecryptMsg - symmetric decryption with symmetric key, while symmetric key is decrypted wih rsa private key stringfunc DecryptMsg(encryptedMsg []byte, encryptedKey []byte, pvtkeystr string) ([]byte, error) {keySymm, err := DecryptWithPrivateKeyStr(encryptedKey, pvtkeystr) //decrypt symmetric key with provided private keyif err != nil {logger.Error(err)}aead, err := chacha.NewX([]byte(keySymm)) //both sender and receiver do this stepif err != nil {panic(err)}if len(encryptedMsg) < aead.NonceSize() {panic("ciphertext too short")}nonce, ciphertext := encryptedMsg[:aead.NonceSize()], encryptedMsg[aead.NonceSize():]plaintext, err := aead.Open(nil, nonce, ciphertext, nil) //decrypt message with symmetric keyif err != nil {panic(err)}return plaintext, err}
Test file (use both rsa keypairs and symmetric keys)
package mainimport ("fmt""ire.com/clustershell/crypting""ire.com/clustershell/crypting/keypairs""ire.com/clustershell/logger"cryptorand "crypto/rand")// Test - pick up key pairs from keys.gotype Test struct {pvtkeystrScheduler stringpubkeystrScheduler stringpvtkeystrExecutor stringpubkeystrExecutor string}var teststrings = `HelloWorld`//rsa logic: length of passing is in crypting.GenerateKeyPair(asymmetricKeySize) from generateKeyPairs.gofunc (t *Test) cryptingSimulationBoth(msg string) bool {fmt.Println("message length: ", len(msg))msgScheduler := msg//fmt.Println("Scheduler: ", cryptingSimulation(msgScheduler, t.pubkeystrScheduler, t.pvtkeystrScheduler))msgExecutor := msg//fmt.Println("Executor: ", cryptingSimulation(msgExecutor, t.pubkeystrExecutor, t.pvtkeystrExecutor), "\n")//return cryptingSimulation(msgScheduler, t.pubkeystrScheduler, t.pvtkeystrScheduler) && cryptingSimulation(msgExecutor, t.pubkeystrExecutor, t.pvtkeystrExecutor)return cryptingSimulationSymm(msgScheduler, t.pubkeystrScheduler, t.pvtkeystrScheduler) && cryptingSimulationSymm(msgExecutor, t.pubkeystrExecutor, t.pvtkeystrExecutor)}//crypting with only rsa keypairsfunc cryptingSimulation(msg string, pubkeystr string, pvtkeystr string) bool {encrypted, err := crypting.EncryptWithPublicKeyStr([]byte(msg), pubkeystr)//fmt.Println(string(encrypted))if err != nil {logger.Error(err)}unencrypted, err := crypting.DecryptWithPrivateKeyStr(encrypted, pvtkeystr)//fmt.Println(string(unencrypted))if err != nil {logger.Error(err)}return string(unencrypted) == msg}//crypting with both rsa keypairs and symmetric keysfunc cryptingSimulationSymm(msg string, pubkeystr string, pvtkeystr string) bool {//pubkeystr and pvtkeystr here are rsa keys//pubkeystr is used to encrypt the symmetric key into encryptedKeyencryptedMsg, encryptedKey := crypting.EncryptMsg([]byte(msg), pubkeystr)//remember to pass encryptedKey and pvtkeystr to get the symmetric keyunencrypted, err := crypting.DecryptMsg(encryptedMsg, encryptedKey, pvtkeystr)if err != nil {logger.Error(err)}return string(unencrypted) == msg}func main() {fmt.Println("\n", "kengen_crypt", "\n")//fmt.Printf("Scheduler_private_key_byte_str\n%v", pvtkeystrScheduler)//fmt.Printf("Scheduler_public_key_byte_str\n%v", pubkeystrScheduler)//fmt.Printf("Executor_private_key_byte_str\n%v", pvtkeystrScheduler)//fmt.Printf("Executor_public_key_byte_str\n%v", pubkeystrScheduler)//get 2 keypairs in keys.gotest := Test{keypairs.PvtkeyScheduler, keypairs.PubkeyScheduler, keypairs.PvtkeyExecutor, keypairs.PubkeyExecutor}//add 1000 bytes every timestr := make([]byte, 1000)cryptorand.Read(str)for { //this loop can show the maximum length of the message passesteststrings = teststrings + string(str)if !test.cryptingSimulationBoth(teststrings) {break}}}
Function in crypting.go:
- GenerateKeyPair(int) (rsa.PrivateKey, rsa.PublicKey): return rsa privatekey and publickey according to provided length
- PrivateKeyToBytes(*rsa.PrivateKey) []byte: convert rsa.PrivateKey to keybytes
- PublicKeyToBytes(*rsa.PublicKey) []byte: convert rsa.PublicKey to keybytes
- BytesToPrivateKey([]byte) *rsa.PrivateKey: convert keybytes to rsa.PrivateKey
- BytesToPublicKey([]byte) *rsa.PublicKey: convert keybytes to rsa.PublicKey
- EncryptWithPublicKey([]byte, *rsa.PublicKey) []byte: encrypt message with rsa public key, return ciphertext
- DecryptWithPrivateKey([]byte, *rsa.PrivateKey) []byte: decrypt message with rsa private key, return plaintext
- EncryptWithPublicKeyStr([]byte, string) ([]byte, error): encrypt message with rsa public key string, return ciphertext and error
- DecryptWithPrivateKeyStr([]byte, string) ([]byte, error): encrypt message with rsa private key string, return plaintext and error
- SymmKeyGen(int) []byte: return symmetric key according to provided length
- EncryptMsg([]byte, string) ([]byte, []byte): encrypt message with randomly generated symmetric key, return encrypted message bytes and encrypted symmetric key that encrypted by provided rsa public key
- DecryptMsg([]byte, []byte, string) ([]byte, error): decrypt symmetric key with provided rsa private key, and use this symmetric key to decrypt encrypted message, return plaintext and error
