cli/pkg/crypt/rsa/rsa.go
Vi dfd7c3ab2d feat(crypt): add LTHN, ChaCha20, RSA, PGP primitives (port from Enchantrix) (#346) (#354)
Co-authored-by: Claude <developers@lethean.io>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-05 20:30:28 +00:00

101 lines
2.7 KiB
Go

// Package rsa provides RSA key generation, encryption, and decryption
// using OAEP with SHA-256.
//
// Ported from Enchantrix (github.com/Snider/Enchantrix/pkg/crypt/std/rsa).
package rsa
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"fmt"
)
// KeyPair holds PEM-encoded RSA public and private keys.
type KeyPair struct {
PublicKey string
PrivateKey string
}
// GenerateKeyPair creates a new RSA key pair of the given bit size.
// The minimum accepted key size is 2048 bits.
// Returns a KeyPair with PEM-encoded public and private keys.
func GenerateKeyPair(bits int) (*KeyPair, error) {
if bits < 2048 {
return nil, fmt.Errorf("rsa: key size too small: %d (minimum 2048)", bits)
}
privKey, err := rsa.GenerateKey(rand.Reader, bits)
if err != nil {
return nil, fmt.Errorf("rsa: failed to generate private key: %w", err)
}
privKeyBytes := x509.MarshalPKCS1PrivateKey(privKey)
privKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: privKeyBytes,
})
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return nil, fmt.Errorf("rsa: failed to marshal public key: %w", err)
}
pubKeyPEM := pem.EncodeToMemory(&pem.Block{
Type: "PUBLIC KEY",
Bytes: pubKeyBytes,
})
return &KeyPair{
PublicKey: string(pubKeyPEM),
PrivateKey: string(privKeyPEM),
}, nil
}
// Encrypt encrypts data with the given PEM-encoded public key using RSA-OAEP
// with SHA-256.
func Encrypt(data []byte, publicKeyPEM string) ([]byte, error) {
block, _ := pem.Decode([]byte(publicKeyPEM))
if block == nil {
return nil, fmt.Errorf("rsa: failed to decode public key PEM")
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("rsa: failed to parse public key: %w", err)
}
rsaPub, ok := pub.(*rsa.PublicKey)
if !ok {
return nil, fmt.Errorf("rsa: not an RSA public key")
}
ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaPub, data, nil)
if err != nil {
return nil, fmt.Errorf("rsa: failed to encrypt data: %w", err)
}
return ciphertext, nil
}
// Decrypt decrypts data with the given PEM-encoded private key using RSA-OAEP
// with SHA-256.
func Decrypt(data []byte, privateKeyPEM string) ([]byte, error) {
block, _ := pem.Decode([]byte(privateKeyPEM))
if block == nil {
return nil, fmt.Errorf("rsa: failed to decode private key PEM")
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("rsa: failed to parse private key: %w", err)
}
plaintext, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, priv, data, nil)
if err != nil {
return nil, fmt.Errorf("rsa: failed to decrypt data: %w", err)
}
return plaintext, nil
}