Asymmetric Encryption, or Public Key Encryption, is a fundamental part of security on modern systems. It assures the authentication and non-repudiation principles of cryptography.
In this tutorial, we are going to see how to implement it using Go.
Note: the version of Go used in this article is 1.15.3.
TL;DR: you can find the complete code here.
RSA is a public-key algorithm. It is named after its creators (_Rivest-Shamir-Adleman). _It was made public in 1977 and it is one of the most used algorithms today.
The public-key cryptography, also known as asymmetric cryptography, uses two different keys, one to encrypt and another one to decrypt:
Image by author.
This means we can share the public key with anyone we want, so they can encrypt any information they want to send us. The only way to access this information is to use the private key to decrypt. This is why it must be kept secret.
The public and private keys are generated together (the public key is derived from the private) and form a key pair.
Key pair generation process.
Note: if you want to know more about how the keys are generated or how the process of encryption works, you can check this video.
Let’s see how to implement it using Go.
The first thing we have to do is generate the public and private keys. If you already a pair, you can skip this process and read it from the file.
The package crypto/rsagenerates the keys, with the package
crypto/rand generating the random numbers.
package main
import (
"crypto/rand"
"crypto/rsa"
"fmt"
)
func generateKeyPair(bits int) (*rsa.PrivateKey, *rsa.PublicKey) {
// This method requires a random number of bits.
privateKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
fmt.Println("Error: ", err)
}
// The public key is part of the PrivateKey struct
return privateKey, &privateKey.PublicKey
}
func main() {
// Generate a 2048-bits key
privateKey, publicKey := generateKeyPair(2048)
fmt.Printf("Private key: %v\n", privateKey)
fmt.Printf("Public Key: %v", publicKey)
}
In order to use the key with another program, or at another time, you can save the keys into a file. The usual way is to save as a PEM format, using the package encoding/pem
.
The public key is part of the private key struct, so you don’t need to save them both. But remember to never share this file with anyone.
package main
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
)
// ommited function for simplicity
// Export public key as a string in PEM format
func exportPubKeyAsPEMStr(pubkey *rsa.PublicKey) string {
pubKeyPem := string(pem.EncodeToMemory(
&pem.Block{
Type: "RSA PUBLIC KEY",
Bytes: x509.MarshalPKCS1PublicKey(pubkey),
},
))
return pubKeyPem
}
// Export private key as a string in PEM format
func exportPrivKeyAsPEMStr(privkey *rsa.PrivateKey) string {
privKeyPem := string(pem.EncodeToMemory(
&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: x509.MarshalPKCS1PrivateKey(privkey),
},
))
return privKeyPem
}
// Save string to a file
func saveKeyToFile(keyPem, filename string) {
pemBytes := []byte(keyPem)
ioutil.WriteFile(filename, pemBytes, 0400)
}
func main() {
// Generate a 2048-bits key
privateKey, publicKey := generateKeyPair(2048)
fmt.Printf("Private key: %v\n", privateKey)
fmt.Printf("Public Key: %v", publicKey)
// Create PEM string
privKeyStr := exportPrivKeyAsPEMStr(privateKey)
pubKeyStr := exportPubKeyAsPEMStr(publicKey)
fmt.Println(privKeyStr)
fmt.Println(pubKeyStr)
saveKeyToFile(privKeyStr, "privkey.pem")
saveKeyToFile(pubKeyStr, "pubkey.pem")
}
Note: the error handling was omitted for simplicity. You should check them in real applications.
#go #security #progamming #developer