Skip to main content Link Search Menu Expand Document (external link)
package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"math/big"
)

// GenerateECDHKeyPair generates a new ECDH key pair using the P-256 curve.
func GenerateECDHKeyPair() (*ecdsa.PrivateKey, error) {
	priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
	if err != nil {
		return nil, err
	}
	return priv, nil
}

// DeriveAESKey derives an AES key from the shared secret.
func DeriveAESKey(privateKey *ecdsa.PrivateKey, publicKey *ecdsa.PublicKey) ([]byte, error) {
	// Calculate the shared secret
	x, _ := publicKey.Curve.ScalarMult(publicKey.X, publicKey.Y, privateKey.D.Bytes())
	return x.Bytes(), nil
}

// EncryptAESGCM encrypts plaintext using AES-GCM.
func EncryptAESGCM(plaintext string, aesKey []byte) (string, error) {
	block, err := aes.NewCipher(aesKey[:32]) // Use 256 bits for AES-256
	if err != nil {
		return "", err
	}

	// Generate a random 12-byte IV
	iv := make([]byte, 12)
	if _, err := rand.Read(iv); err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	ciphertext := gcm.Seal(nil, iv, []byte(plaintext), nil)

	// Combine IV and ciphertext
	result := append(iv, ciphertext...)
	return base64.StdEncoding.EncodeToString(result), nil
}

// DecryptAESGCM decrypts ciphertext using AES-GCM.
func DecryptAESGCM(encryptedData string, aesKey []byte) (string, error) {
	data, err := base64.StdEncoding.DecodeString(encryptedData)
	if err != nil {
		return "", err
	}

	// Extract IV and ciphertext
	iv := data[:12]
	ciphertext := data[12:]

	block, err := aes.NewCipher(aesKey[:32]) // Use 256 bits for AES-256
	if err != nil {
		return "", err
	}

	gcm, err := cipher.NewGCM(block)
	if err != nil {
		return "", err
	}

	plaintext, err := gcm.Open(nil, iv, ciphertext, nil)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}

// EncodePublicKey encodes an ECDSA public key to PEM format.
func EncodePublicKey(pub *ecdsa.PublicKey) (string, error) {
	pubASN1, err := x509.MarshalPKIXPublicKey(pub)
	if err != nil {
		return "", err
	}
	block := &pem.Block{
		Type:  "PUBLIC KEY",
		Bytes: pubASN1,
	}
	return string(pem.EncodeToMemory(block)), nil
}

// EncodePrivateKey encodes an ECDSA private key to PEM format.
func EncodePrivateKey(priv *ecdsa.PrivateKey) (string, error) {
	privASN1, err := x509.MarshalECPrivateKey(priv)
	if err != nil {
		return "", err
	}
	block := &pem.Block{
		Type:  "EC PRIVATE KEY",
		Bytes: privASN1,
	}
	return string(pem.EncodeToMemory(block)), nil
}

func main() {
	// Generate ECDH key pair
	privKey, err := GenerateECDHKeyPair()
	if err != nil {
		fmt.Println("Error generating ECDH key pair:", err)
		return
	}

	// Encode keys to PEM format for display
	privPEM, _ := EncodePrivateKey(privKey)
	pubPEM, _ := EncodePublicKey(&privKey.PublicKey)

	fmt.Println("Private Key:\n", privPEM)
	fmt.Println("Public Key:\n", pubPEM)

	// Example usage for deriving AES key and encrypting
	// In a real application, you would exchange public keys with another party
	aesKey, err := DeriveAESKey(privKey, &privKey.PublicKey) // For demonstration, using own public key
	if err != nil {
		fmt.Println("Error deriving AES key:", err)
		return
	}

	// Encrypt a message
	plaintext := "Hello, this is a secret message!"
	encrypted, err := EncryptAESGCM(plaintext, aesKey)
	if err != nil {
		fmt.Println("Error encrypting:", err)
		return
	}
	fmt.Println("Encrypted:", encrypted)

	// Decrypt the message
	decrypted, err := DecryptAESGCM(encrypted, aesKey)
	if err != nil {
		fmt.Println("Error decrypting:", err)
		return
	}
	fmt.Println("Decrypted:", decrypted)
}