375 lines
9.2 KiB
Go
375 lines
9.2 KiB
Go
package crypto
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"crypto/ecdsa"
|
|
"crypto/elliptic"
|
|
"crypto/rand"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"errors"
|
|
"io"
|
|
"math/big"
|
|
|
|
"golang.org/x/crypto/hkdf"
|
|
)
|
|
|
|
var (
|
|
ErrInvalidKeySize = errors.New("invalid key size")
|
|
ErrInvalidCipherText = errors.New("invalid ciphertext")
|
|
ErrEncryptionFailed = errors.New("encryption failed")
|
|
ErrDecryptionFailed = errors.New("decryption failed")
|
|
ErrInvalidPublicKey = errors.New("invalid public key")
|
|
ErrInvalidSignature = errors.New("invalid signature")
|
|
)
|
|
|
|
// CryptoService provides cryptographic operations
|
|
type CryptoService struct {
|
|
masterKey []byte
|
|
}
|
|
|
|
// NewCryptoService creates a new crypto service
|
|
func NewCryptoService(masterKey []byte) (*CryptoService, error) {
|
|
if len(masterKey) != 32 {
|
|
return nil, ErrInvalidKeySize
|
|
}
|
|
return &CryptoService{masterKey: masterKey}, nil
|
|
}
|
|
|
|
// GenerateRandomBytes generates random bytes
|
|
func GenerateRandomBytes(n int) ([]byte, error) {
|
|
b := make([]byte, n)
|
|
_, err := rand.Read(b)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return b, nil
|
|
}
|
|
|
|
// GenerateRandomHex generates a random hex string
|
|
func GenerateRandomHex(n int) (string, error) {
|
|
bytes, err := GenerateRandomBytes(n)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return hex.EncodeToString(bytes), nil
|
|
}
|
|
|
|
// DeriveKey derives a key from the master key using HKDF
|
|
func (c *CryptoService) DeriveKey(context string, length int) ([]byte, error) {
|
|
hkdfReader := hkdf.New(sha256.New, c.masterKey, nil, []byte(context))
|
|
key := make([]byte, length)
|
|
if _, err := io.ReadFull(hkdfReader, key); err != nil {
|
|
return nil, err
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// EncryptShare encrypts a key share using AES-256-GCM
|
|
func (c *CryptoService) EncryptShare(shareData []byte, partyID string) ([]byte, error) {
|
|
// Derive a unique key for this party
|
|
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, aesGCM.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Encrypt and prepend nonce
|
|
ciphertext := aesGCM.Seal(nonce, nonce, shareData, []byte(partyID))
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// DecryptShare decrypts a key share
|
|
func (c *CryptoService) DecryptShare(encryptedData []byte, partyID string) ([]byte, error) {
|
|
// Derive the same key used for encryption
|
|
key, err := c.DeriveKey("share_encryption:"+partyID, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonceSize := aesGCM.NonceSize()
|
|
if len(encryptedData) < nonceSize {
|
|
return nil, ErrInvalidCipherText
|
|
}
|
|
|
|
nonce, ciphertext := encryptedData[:nonceSize], encryptedData[nonceSize:]
|
|
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, []byte(partyID))
|
|
if err != nil {
|
|
return nil, ErrDecryptionFailed
|
|
}
|
|
|
|
return plaintext, nil
|
|
}
|
|
|
|
// EncryptMessage encrypts a message using AES-256-GCM
|
|
func (c *CryptoService) EncryptMessage(plaintext []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(c.masterKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, aesGCM.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// DecryptMessage decrypts a message
|
|
func (c *CryptoService) DecryptMessage(ciphertext []byte) ([]byte, error) {
|
|
block, err := aes.NewCipher(c.masterKey)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonceSize := aesGCM.NonceSize()
|
|
if len(ciphertext) < nonceSize {
|
|
return nil, ErrInvalidCipherText
|
|
}
|
|
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return nil, ErrDecryptionFailed
|
|
}
|
|
|
|
return plaintext, nil
|
|
}
|
|
|
|
// Hash256 computes SHA-256 hash
|
|
func Hash256(data []byte) []byte {
|
|
hash := sha256.Sum256(data)
|
|
return hash[:]
|
|
}
|
|
|
|
// VerifyECDSASignature verifies an ECDSA signature
|
|
func VerifyECDSASignature(messageHash, signature, publicKey []byte) (bool, error) {
|
|
// Parse public key (assuming secp256k1/P256 uncompressed format)
|
|
curve := elliptic.P256()
|
|
x, y := elliptic.Unmarshal(curve, publicKey)
|
|
if x == nil {
|
|
return false, ErrInvalidPublicKey
|
|
}
|
|
|
|
pubKey := &ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: x,
|
|
Y: y,
|
|
}
|
|
|
|
// Parse signature (R || S, each 32 bytes)
|
|
if len(signature) != 64 {
|
|
return false, ErrInvalidSignature
|
|
}
|
|
|
|
r := new(big.Int).SetBytes(signature[:32])
|
|
s := new(big.Int).SetBytes(signature[32:])
|
|
|
|
// Verify signature
|
|
valid := ecdsa.Verify(pubKey, messageHash, r, s)
|
|
return valid, nil
|
|
}
|
|
|
|
// GenerateNonce generates a cryptographic nonce
|
|
func GenerateNonce() ([]byte, error) {
|
|
return GenerateRandomBytes(32)
|
|
}
|
|
|
|
// SecureCompare performs constant-time comparison
|
|
func SecureCompare(a, b []byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
var result byte
|
|
for i := 0; i < len(a); i++ {
|
|
result |= a[i] ^ b[i]
|
|
}
|
|
return result == 0
|
|
}
|
|
|
|
// ParsePublicKey parses a public key from bytes (P256 uncompressed format)
|
|
func ParsePublicKey(publicKeyBytes []byte) (*ecdsa.PublicKey, error) {
|
|
curve := elliptic.P256()
|
|
x, y := elliptic.Unmarshal(curve, publicKeyBytes)
|
|
if x == nil {
|
|
return nil, ErrInvalidPublicKey
|
|
}
|
|
|
|
return &ecdsa.PublicKey{
|
|
Curve: curve,
|
|
X: x,
|
|
Y: y,
|
|
}, nil
|
|
}
|
|
|
|
// VerifySignature verifies an ECDSA signature using a public key
|
|
func VerifySignature(pubKey *ecdsa.PublicKey, messageHash, signature []byte) bool {
|
|
// Parse signature (R || S, each 32 bytes)
|
|
if len(signature) != 64 {
|
|
return false
|
|
}
|
|
|
|
r := new(big.Int).SetBytes(signature[:32])
|
|
s := new(big.Int).SetBytes(signature[32:])
|
|
|
|
return ecdsa.Verify(pubKey, messageHash, r, s)
|
|
}
|
|
|
|
// HashMessage computes SHA-256 hash of a message (alias for Hash256)
|
|
func HashMessage(message []byte) []byte {
|
|
return Hash256(message)
|
|
}
|
|
|
|
// Encrypt encrypts data using AES-256-GCM with the provided key
|
|
func Encrypt(key, plaintext []byte) ([]byte, error) {
|
|
if len(key) != 32 {
|
|
return nil, ErrInvalidKeySize
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonce := make([]byte, aesGCM.NonceSize())
|
|
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil)
|
|
return ciphertext, nil
|
|
}
|
|
|
|
// Decrypt decrypts data using AES-256-GCM with the provided key
|
|
func Decrypt(key, ciphertext []byte) ([]byte, error) {
|
|
if len(key) != 32 {
|
|
return nil, ErrInvalidKeySize
|
|
}
|
|
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
aesGCM, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
nonceSize := aesGCM.NonceSize()
|
|
if len(ciphertext) < nonceSize {
|
|
return nil, ErrInvalidCipherText
|
|
}
|
|
|
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
|
plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil)
|
|
if err != nil {
|
|
return nil, ErrDecryptionFailed
|
|
}
|
|
|
|
return plaintext, nil
|
|
}
|
|
|
|
// DeriveKey derives a key from secret and salt using HKDF (standalone function)
|
|
func DeriveKey(secret, salt []byte, length int) ([]byte, error) {
|
|
hkdfReader := hkdf.New(sha256.New, secret, salt, nil)
|
|
key := make([]byte, length)
|
|
if _, err := io.ReadFull(hkdfReader, key); err != nil {
|
|
return nil, err
|
|
}
|
|
return key, nil
|
|
}
|
|
|
|
// SignMessage signs a message using ECDSA private key
|
|
func SignMessage(privateKey *ecdsa.PrivateKey, message []byte) ([]byte, error) {
|
|
hash := Hash256(message)
|
|
r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Encode R and S as 32 bytes each (total 64 bytes)
|
|
signature := make([]byte, 64)
|
|
rBytes := r.Bytes()
|
|
sBytes := s.Bytes()
|
|
|
|
// Pad with zeros if necessary
|
|
copy(signature[32-len(rBytes):32], rBytes)
|
|
copy(signature[64-len(sBytes):64], sBytes)
|
|
|
|
return signature, nil
|
|
}
|
|
|
|
// EncodeToHex encodes bytes to hex string
|
|
func EncodeToHex(data []byte) string {
|
|
return hex.EncodeToString(data)
|
|
}
|
|
|
|
// DecodeFromHex decodes hex string to bytes
|
|
func DecodeFromHex(s string) ([]byte, error) {
|
|
return hex.DecodeString(s)
|
|
}
|
|
|
|
// EncodeToBase64 encodes bytes to base64 string
|
|
func EncodeToBase64(data []byte) string {
|
|
return hex.EncodeToString(data) // Using hex for simplicity, could use base64
|
|
}
|
|
|
|
// DecodeFromBase64 decodes base64 string to bytes
|
|
func DecodeFromBase64(s string) ([]byte, error) {
|
|
return hex.DecodeString(s)
|
|
}
|
|
|
|
// MarshalPublicKey marshals an ECDSA public key to bytes
|
|
func MarshalPublicKey(pubKey *ecdsa.PublicKey) []byte {
|
|
return elliptic.Marshal(pubKey.Curve, pubKey.X, pubKey.Y)
|
|
}
|
|
|
|
// CompareBytes performs constant-time comparison of two byte slices
|
|
func CompareBytes(a, b []byte) bool {
|
|
return SecureCompare(a, b)
|
|
}
|