Create Digital Signature Using RSA in Go

You receive a letter from your grandmother to transfer money but the letter does not have her signature. Will you transfer the money? I wish you don’t.

Purnaresa Yuliartanto
Geek Culture
5 min readMar 21, 2021

--

Cover Photo by SkillScouter on Unsplash

A signature is the most common way to ensure the authenticity of the information. It’s been around since Rome Empire. And it’s ironic that in this digital age, there are many companies and applications that interact without proper digital signature.

Project Scope

Build a simple go application that creates a signature based on sample text. Then verify the signature if it is a valid signature.

Community Library

Finished Project Preview

Run the application in the command shell by calling the go build file.

go run main.go

First, the application print out the dummy plaintext. Then it creates a signature. If creating signature success, the signature will be printed out. The last part is the application verifying the signature to the plaintext. If the verification succeeds, it prints the success message.

Application Design

Example Application Design

Before continue, subscribe to our website so you get notified if we have new tutorial content.

Code

Main Application

package mainimport (
"log"
"github.com/purnaresa/bulwark/crypto"
)
func main() {
// provision key pair
privateKey, publicKey, err := crypto.GenerateKeyPair()
if err != nil {
log.Fatalln(err)
}
// create dummy plaintext
plaintext := []byte("hello, my name is plaintext")
log.Printf("plaintext : %s\n\n", string(plaintext))
// create signature
log.Println("creating signature...")
signature, err := crypto.SignDefault(plaintext, privateKey)
if err != nil {
log.Fatalln(err)
} else {
log.Printf("signature : %s\n\n", string(signature))
}
// verify signature
log.Println("verifying signature and plaintext...")
errVerify := crypto.VerifyDefault(plaintext, publicKey, signature)
if errVerify != nil {
log.Fatalln(errVerify)
} else {
log.Println("verification success!")
}
}

The code above is the full code of main.go. We will breakdown the code with an explanation below.

Section 1: provision key pair

privateKey, publicKey, err := crypto.GenerateKeyPair()
if err != nil {
log.Fatalln(err)
}

In a production environment, the RSA key pair is provisioned off the system. You must store the private key in a secure place. Holistically — if your private key is leaked, the signature is flawed because the signature cannot guarantee its authenticity. The public key should be shared with the partner that communicating with you. If your private key is leaked, generate new key pair!

The key pair in this project is generated on the fly for the sake of simplicity.

Section 2: create dummy plaintext

plaintext := []byte("hello, my name is plaintext")
log.Printf("plaintext : %s\n\n", string(plaintext))

You can encrypt anything as long as it is in byte format. The important thing is the size of the plaintext. RSA is only able to encrypt a limited size of plaintext. It will be enough for most cases when encrypting a text message. If you need to encrypt big-sized data like an image or pdf, use the double encryption approach. First, you encrypt the file with a randomly generated key using AES/symmetrical. Then you encrypt the generated key using RSA.

Section 3: create a signature

log.Println("creating signature...")
signature, err := crypto.SignDefault(plaintext, privateKey)
if err != nil {
log.Fatalln(err)
} else {
log.Printf("signature : %s\n\n", string(signature))
}

Sign the plaintext with a private key. The output is a signature that can be verified using the public key from the key pair. The signature is created using a randomizer. The signature will be different each time even the plaintext is the same.

The use case for creating a signature is right before sending the data to your partner along with the signature. If your application is handling Personally identifiable information (PII) you will need to encrypt the data. Create a signature based on plaintext before encryption. If you create the signature after encryption, an attacker may sabotage (modify) the signature so the verification failed, therefore the information is not processed. Protect the information by encrypting both ciphertext and signature.

Section 4: Verify the signature

log.Println("verifying signature and plaintext...")
errVerify := crypto.VerifyDefault(plaintext, publicKey, signature)
if errVerify != nil {
log.Fatalln(errVerify)
} else {
log.Println("verification success!")
}

The signature must be verified using the public key of the signature creator. The plaintext is required material to check whatever the signature is valid or not. If the verification failed, it will return with an error object.

In the production scenario, the one that verifies the signature is the partner that you are communicating with. So you must share your public key with them for them to verify the signature.

Library

The main application above is importing library from

"github.com/purnaresa/bulwark/crypto"

It’s a library for secure application architecture. Three functions that utilized in this application are:

  • GenerateKeyPair()
  • SignDefault(plaintext []byte, privateKey []byte) (signature string, err error)
  • VerifyDefault(plaintext []byte, publicKey []byte, signature string) (err error)

GenerateKeyPair is a function to generate RSA Keypair. The details and example of a function is available at this post Generate RSA Private-Public Key Pair in Go

SignDefault is a function to create a signature. The inputs is plaintext and private key. The output is signature in base64 string with the purpose of easy storing and delivery. The function is flexible for most use cases. Check this Github Code if you need to know under the hood.

VerifyDefault is a function to verify the signature. The input is plaintext, the public key, and the public key. The output is an empty error object if verification is successful. Check this Github Code if you need to know under the hood.

Summary

Creating a digital signature is easy. Many community libraries are available to use. You can use one that you trust. The important thing is to ensure the specification of those libraries is up to industry standard. If the specification does not follow the industry standard, the partner that you are communicating with will have a hard time verifying the signature

Example code available here:

https://github.com/purnaresa/bulwark/blob/master/crypto/example/create_signature/main.go

--

--

Purnaresa Yuliartanto
Geek Culture

IT architect at best cloud provider in the planet. Experience in cybersecurity and tech-fire-fighting.