crypto/x25519.go

154 lines
4.4 KiB
Go

package crypto
import (
"crypto"
"crypto/rand"
"errors"
"io"
"apigo.cc/go/safe"
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
)
type X25519Algorithm struct {
UseGCM bool
KdfInfo []byte
KdfSalt []byte
Hash crypto.Hash
}
var (
X25519GCM = &X25519Algorithm{UseGCM: true, Hash: crypto.SHA256}
X25519CBC = &X25519Algorithm{UseGCM: false, Hash: crypto.SHA256}
)
func NewX25519(safePrivateKeyBuf, safePublicKeyBuf *safe.SafeBuf) (*Asymmetric, error) {
return NewAsymmetric(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf)
}
func NewX25519AndEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
return NewAsymmetricAndEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf)
}
func NewX25519WithOutEraseKey(safePrivateKeyBuf, safePublicKeyBuf []byte) (*Asymmetric, error) {
return NewAsymmetricWithoutEraseKey(X25519GCM, safePrivateKeyBuf, safePublicKeyBuf, false)
}
func NewX25519Algorithm(useGCM bool, hash crypto.Hash, kdfInfo, kdfSalt []byte) *X25519Algorithm {
return &X25519Algorithm{UseGCM: useGCM, Hash: hash, KdfInfo: kdfInfo, KdfSalt: kdfSalt}
}
func GenerateX25519KeyPair() ([]byte, []byte, error) {
privKey := make([]byte, curve25519.ScalarSize)
if _, err := rand.Read(privKey); err != nil {
return nil, nil, err
}
pubKey, err := curve25519.X25519(privKey, curve25519.Basepoint)
if err != nil {
return nil, nil, err
}
return privKey, pubKey, nil
}
func (x *X25519Algorithm) ParsePrivateKey(der []byte) (any, error) {
if len(der) != curve25519.ScalarSize {
return nil, errors.New("invalid X25519 private key size")
}
key := make([]byte, curve25519.ScalarSize)
copy(key, der)
return key, nil
}
func (x *X25519Algorithm) ParsePublicKey(der []byte) (any, error) {
if len(der) != curve25519.PointSize {
return nil, errors.New("invalid X25519 public key size")
}
key := make([]byte, curve25519.PointSize)
copy(key, der)
return key, nil
}
func (x *X25519Algorithm) Sign(privateKeyObj any, data []byte, hash ...crypto.Hash) ([]byte, error) {
return nil, errors.New("X25519 does not support signing, use Ed25519 instead")
}
func (x *X25519Algorithm) Verify(publicKeyObj any, data []byte, signature []byte, hash ...crypto.Hash) (bool, error) {
return false, errors.New("X25519 does not support verification, use Ed25519 instead")
}
func (x *X25519Algorithm) Encrypt(publicKeyObj any, data []byte) ([]byte, error) {
targetPub, ok := publicKeyObj.([]byte)
if !ok || len(targetPub) != curve25519.PointSize {
return nil, errors.New("invalid public key type/size for X25519")
}
ephemeralPriv, ephemeralPub, err := GenerateX25519KeyPair()
if err != nil {
return nil, err
}
defer safe.ZeroMemory(ephemeralPriv)
sharedSecret, err := curve25519.X25519(ephemeralPriv, targetPub)
if err != nil {
return nil, err
}
defer safe.ZeroMemory(sharedSecret)
hFunc := x.Hash
if hFunc == 0 {
hFunc = crypto.SHA256
}
hkdfReader := hkdf.New(hFunc.New, sharedSecret, x.KdfSalt, x.KdfInfo)
aesKey := make([]byte, 32)
defer safe.ZeroMemory(aesKey)
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
return nil, err
}
cipherAlgo := &AESCipher{useGCM: x.UseGCM}
ivLen := 16
if x.UseGCM {
ivLen = 12
}
iv := safe.MakeSafeToken(ivLen)
cipherText, err := cipherAlgo.Encrypt(data, aesKey, iv)
if err != nil {
return nil, err
}
out := make([]byte, 0, len(ephemeralPub)+len(iv)+len(cipherText))
out = append(out, ephemeralPub...)
out = append(out, iv...)
out = append(out, cipherText...)
return out, nil
}
func (x *X25519Algorithm) Decrypt(privateKeyObj any, data []byte) ([]byte, error) {
myPriv, ok := privateKeyObj.([]byte)
if !ok || len(myPriv) != curve25519.ScalarSize {
return nil, errors.New("invalid private key type/size for X25519")
}
pubKeyLen := curve25519.PointSize
ivLen := 16
if x.UseGCM {
ivLen = 12
}
if len(data) < pubKeyLen+ivLen {
return nil, errors.New("invalid ciphertext package size")
}
ephemeralPub := data[:pubKeyLen]
iv := data[pubKeyLen : pubKeyLen+ivLen]
cipherText := data[pubKeyLen+ivLen:]
sharedSecret, err := curve25519.X25519(myPriv, ephemeralPub)
if err != nil {
return nil, err
}
defer safe.ZeroMemory(sharedSecret)
hFunc := x.Hash
if hFunc == 0 {
hFunc = crypto.SHA256
}
hkdfReader := hkdf.New(hFunc.New, sharedSecret, x.KdfSalt, x.KdfInfo)
aesKey := make([]byte, 32)
defer safe.ZeroMemory(aesKey)
if _, err := io.ReadFull(hkdfReader, aesKey); err != nil {
return nil, err
}
cipherAlgo := &AESCipher{useGCM: x.UseGCM}
return cipherAlgo.Decrypt(cipherText, aesKey, iv)
}