154 lines
4.4 KiB
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)
|
||
|
|
}
|