Implementing cryptographic signing in Go involves choosing the right algorithm based on your use case. HMAC is ideal for symmetric key signing, while RSA is used for asymmetric signing where public and private keys are involved. Both are commonly used in token-based systems, API authentication, and secure data storage.

This article will walk through practical examples of signing and verifying data using HMAC-SHA256 and RSA-SHA256 in Go. You will learn how to generate keys, sign data, and verify signatures using the standard Go libraries.


HMAC Signing in Go

HMAC (Hash-based Message Authentication Code) is a symmetric cryptographic algorithm that uses a shared secret to generate and verify a message digest. It is commonly used in scenarios like JWT tokens or API request signing.

Example: HMAC-SHA256 Signing and Verification

package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "fmt"
)

func generateHMAC(message, secret []byte) string {
    h := hmac.New(sha256.New, secret)
    h.Write(message)
    return hex.EncodeToString(h.Sum(nil))
}

func verifyHMAC(message, secret, signature string) bool {
    expected := generateHMAC([]byte(message), []byte(secret))
    return hmac.Equal([]byte(expected), []byte(signature))
}

func main() {
    message := "secure_message"
    secret := "my_secure_secret"

    signature := generateHMAC([]byte(message), []byte(secret))
    fmt.Printf("HMAC Signature: %s\n", signature)

    isValid := verifyHMAC(message, secret, signature)
    fmt.Printf("Signature Valid: %t\n", isValid)
}

This example demonstrates how to generate and verify an HMAC signature. The hmac.Equal function is used to safely compare the computed and expected signatures, preventing timing attacks.


RSA Signing in Go

RSA is an asymmetric cryptographic algorithm that uses a private key for signing and a public key for verification. It is used in more secure environments where shared secrets are not viable, such as in certificate-based authentication or secure messaging protocols.

Example: RSA Signing and Verification

package main

import (
    "crypto"
    "crypto/rand"
    "crypto/rsa"
    "crypto/sha256"
    "encoding/asn1"
    "encoding/base64"
    "fmt"
)

func generateRSAKey() (*rsa.PrivateKey, error) {
    return rsa.GenerateKey(rand.Reader, 2048)
}

func signRSA(message string, privateKey *rsa.PrivateKey) (string, error) {
    hashed := sha256.Sum256([]byte(message))
    signature, err := privateKey.Sign(rand.Reader, hashed[:], crypto.SHA256)
    if err != nil {
        return "", err
    }
    return base64.StdEncoding.EncodeToString(signature), nil
}

func verifyRSA(message string, signature string, publicKey *rsa.PublicKey) (bool, error) {
    sig, err := base64.StdEncoding.DecodeString(signature)
    if err != nil {
        return false, err
    }

    hashed := sha256.Sum256([]byte(message))
    return publicKey.Verify(crypto.SHA256, hashed[:], sig) == nil
}

func main() {
    privateKey, err := generateRSAKey()
    if err != nil {
        panic(err)
    }

    message := "secure_message"
    signature, err := signRSA(message, privateKey)
    if err != nil {
        panic(err)
    }
    fmt.Printf("RSA Signature: %s\n", signature)

    isValid, err := verifyRSA(message, signature, &privateKey.PublicKey)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Signature Valid: %t\n", isValid)
}

In this example, we generate an RSA key pair, sign a message using the private key, and verify the signature using the public key. The Sign and Verify functions from the crypto/rsa package handle the cryptographic operations securely.


Comparison of HMAC vs. RSA Signing

FeatureHMACRSA
Key TypeSymmetricAsymmetric
Use CaseShared secret scenariosPublic key infrastructure
PerformanceFast and efficientSlower due to asymmetric operations
SecuritySecure with strong secretsSecure with proper key size and storage
Suitable forAPI signing, JWT tokensCertificates, secure messaging
Key ManagementRequires secret key sharingRequires public/private key pair
VulnerabilityKey exposurePrivate key exposure
Signing speedHighLow
Verification speedHighLow
ScalabilityLimited due to shared keysHigh (can scale with public keys)

Best Practices for Secure Signing in Go

  1. Use strong and unique keys for HMAC and RSA. Avoid using default or shared keys.
  2. Rotate keys periodically, especially in environments where key exposure is a risk.
  3. Always use constant-time comparison functions like hmac.Equal to prevent timing attacks.
  4. Sign all critical data, not just payloads. Include headers or metadata if needed.
  5. Store private keys securely, using key management systems or encrypted storage.
  6. Use secure random number generators like crypto/rand for key generation and nonce creation.
  7. Validate all input data before signing or verification to avoid injection attacks.

Learn more with useful resources