git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/crypto/curve25519.go (about) 1 package crypto 2 3 import ( 4 "golang.org/x/crypto/blake2b" 5 "golang.org/x/crypto/curve25519" 6 ) 7 8 const ( 9 // Curve25519PublicKeySize is the size, in bytes, of public keys as used in this package. 10 Curve25519PublicKeySize = curve25519.PointSize 11 12 // Curve25519PrivateKeySize is the size, in bytes, of private keys as used in this package. 13 Curve25519PrivateKeySize = curve25519.ScalarSize 14 15 // X25519SharedSecretSize is the size, in bytes of the shared secret of a x25519 key exchange 16 X25519SharedSecretSize = 32 17 ) 18 19 // GenerateCurve25519KeyPair generates a public/private Curve25519 key pair 20 func GenerateCurve25519KeyPair() (publicKey Curve25519PublicKey, privateKey Curve25519PrivateKey, err error) { 21 privateKey, err = RandBytes(Curve25519PrivateKeySize) 22 if err != nil { 23 return 24 } 25 26 publicKey, err = privateKey.Public() 27 return 28 } 29 30 // Curve25519PublicKey is the type of Curve25519 public keys. 31 type Curve25519PublicKey []byte 32 33 // KeyExchange performs a x25519 key exchange with the given private key 34 func (publicKey Curve25519PublicKey) KeyExchange(privateKey Curve25519PrivateKey) (sharedSecret []byte, err error) { 35 sharedSecret, err = curve25519.X25519(privateKey, publicKey) 36 return 37 } 38 39 // Encrypt performs a x25519 key exchange, and encrypt the message using `XChaCha20-Poly1305` with 40 // the shared secret as key and nonce as nonce. 41 func (publicKey Curve25519PublicKey) Encrypt(fromPrivateKey Curve25519PrivateKey, nonce []byte, 42 message []byte) (ciphertext []byte, err error) { 43 sharedSecret, err := publicKey.KeyExchange(fromPrivateKey) 44 defer Zeroize(sharedSecret) 45 if err != nil { 46 return 47 } 48 49 cipher, err := NewAEAD(sharedSecret) 50 if err != nil { 51 return 52 } 53 54 ciphertext = cipher.Seal(nil, nonce, message, nil) 55 return 56 } 57 58 // EncryptEphemeral generates an ephemeral Curve25519KeyPair and `Encrypt` message using the public key, 59 // the ephemeral privateKey and `blake2b(size=AEADNonceSize, message=ephemeralPublicKey || publicKey)` as nonce 60 func (publicKey Curve25519PublicKey) EncryptEphemeral(message []byte) (ciphertext []byte, ephemeralPublicKey Curve25519PublicKey, err error) { 61 ephemeralPublicKey, ephemeralPrivateKey, err := GenerateCurve25519KeyPair() 62 defer Zeroize(ephemeralPrivateKey) 63 if err != nil { 64 return 65 } 66 67 nonce, err := generateNonce(ephemeralPublicKey, publicKey) 68 if err != nil { 69 return 70 } 71 72 ciphertext, err = publicKey.Encrypt(ephemeralPrivateKey, nonce, message) 73 return 74 } 75 76 func generateNonce(ephemeralPublicKey, publicKey Curve25519PublicKey) (nonce []byte, err error) { 77 var nonceMessage []byte 78 79 nonceMessage = append(nonceMessage, []byte(ephemeralPublicKey)...) 80 nonceMessage = append(nonceMessage, []byte(publicKey)...) 81 hash, err := blake2b.New(AEADNonceSize, nil) 82 if err != nil { 83 return 84 } 85 hash.Write(nonceMessage) 86 nonce = hash.Sum(nil) 87 return 88 } 89 90 // Curve25519PrivateKey is the type of Curve25519 private keys. 91 type Curve25519PrivateKey []byte 92 93 // Public returns the Curve25519PublicKey corresponding to privateKey. 94 func (privateKey Curve25519PrivateKey) Public() (publicKey Curve25519PublicKey, err error) { 95 publicKey, err = curve25519.X25519(privateKey, curve25519.Basepoint) 96 return 97 } 98 99 // KeyExchange performs a x25519 key exchange with the given public key 100 func (privateKey Curve25519PrivateKey) KeyExchange(publicKey Curve25519PublicKey) (sharedSecret []byte, err error) { 101 sharedSecret, err = curve25519.X25519(privateKey, publicKey) 102 return 103 } 104 105 // Decrypt performs a x25519 key exchange, and decrypt the ciphertext using `XChaCha20-Poly1305` with 106 // the shared secret as key and nonce as nonce. 107 func (privateKey Curve25519PrivateKey) Decrypt(fromPublicKey Curve25519PublicKey, nonce []byte, ciphertext []byte) (plaintext []byte, err error) { 108 sharedSecret, err := privateKey.KeyExchange(fromPublicKey) 109 defer Zeroize(sharedSecret) 110 if err != nil { 111 return 112 } 113 114 cipher, err := NewAEAD(sharedSecret) 115 if err != nil { 116 return 117 } 118 119 plaintext, err = cipher.Open(nil, nonce, ciphertext, nil) 120 return 121 } 122 123 // DecryptEphemeral generates a noce with `blake2b(size=AEADNonceSize, message=ephemeralPublicKey || privateKey.PublicKey())` 124 // and decrypt the `ciphertext` using `Decrypt` 125 func (privateKey Curve25519PrivateKey) DecryptEphemeral(ephemeralPublicKey Curve25519PublicKey, ciphertext []byte) (plaintext []byte, err error) { 126 myPublicKey, err := privateKey.Public() 127 if err != nil { 128 return 129 } 130 131 nonce, err := generateNonce(ephemeralPublicKey, myPublicKey) 132 if err != nil { 133 return 134 } 135 136 return privateKey.Decrypt(ephemeralPublicKey, nonce, ciphertext) 137 }