
Securing Go Applications with Cryptographic Signing
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
| Feature | HMAC | RSA |
|---|---|---|
| Key Type | Symmetric | Asymmetric |
| Use Case | Shared secret scenarios | Public key infrastructure |
| Performance | Fast and efficient | Slower due to asymmetric operations |
| Security | Secure with strong secrets | Secure with proper key size and storage |
| Suitable for | API signing, JWT tokens | Certificates, secure messaging |
| Key Management | Requires secret key sharing | Requires public/private key pair |
| Vulnerability | Key exposure | Private key exposure |
| Signing speed | High | Low |
| Verification speed | High | Low |
| Scalability | Limited due to shared keys | High (can scale with public keys) |
Best Practices for Secure Signing in Go
- Use strong and unique keys for HMAC and RSA. Avoid using default or shared keys.
- Rotate keys periodically, especially in environments where key exposure is a risk.
- Always use constant-time comparison functions like
hmac.Equalto prevent timing attacks. - Sign all critical data, not just payloads. Include headers or metadata if needed.
- Store private keys securely, using key management systems or encrypted storage.
- Use secure random number generators like
crypto/randfor key generation and nonce creation. - Validate all input data before signing or verification to avoid injection attacks.
