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.
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
- Bulwark: https://github.com/purnaresa/bulwark
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
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