Co-authored-by: Claude <developers@lethean.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
101 lines
2.7 KiB
Go
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
|
|
}
|