117 lines
3.1 KiB
Diff
117 lines
3.1 KiB
Diff
--- /dev/null
|
|
+++ b/protocol/obfhttp/crypto.go
|
|
@@ -0,0 +1,113 @@
|
|
+// OMV
|
|
+package obfhttp
|
|
+
|
|
+import (
|
|
+ "crypto/aes"
|
|
+ "crypto/cipher"
|
|
+ "crypto/md5"
|
|
+ "crypto/rand"
|
|
+ "io"
|
|
+
|
|
+ E "github.com/sagernet/sing/common/exceptions"
|
|
+ "golang.org/x/crypto/chacha20poly1305"
|
|
+)
|
|
+
|
|
+// Encryptor provides per-chunk AEAD encryption. Each call to Encrypt generates a random nonce.
|
|
+type Encryptor interface {
|
|
+ Encrypt(plaintext []byte) ([]byte, error)
|
|
+ Decrypt(data []byte) ([]byte, error)
|
|
+}
|
|
+
|
|
+type aeadEncryptor struct {
|
|
+ aead cipher.AEAD
|
|
+}
|
|
+
|
|
+func (e *aeadEncryptor) Encrypt(plaintext []byte) ([]byte, error) {
|
|
+ nonce := make([]byte, e.aead.NonceSize())
|
|
+ if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
|
|
+ return nil, E.Cause(err, "generate nonce")
|
|
+ }
|
|
+ // nonce || ciphertext || tag
|
|
+ ciphertext := e.aead.Seal(nonce, nonce, plaintext, nil)
|
|
+ return ciphertext, nil
|
|
+}
|
|
+
|
|
+func (e *aeadEncryptor) Decrypt(data []byte) ([]byte, error) {
|
|
+ nonceSize := e.aead.NonceSize()
|
|
+ if len(data) < nonceSize {
|
|
+ return nil, E.New("ciphertext too short")
|
|
+ }
|
|
+ nonce := data[:nonceSize]
|
|
+ ciphertext := data[nonceSize:]
|
|
+ plaintext, err := e.aead.Open(nil, nonce, ciphertext, nil)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "decrypt")
|
|
+ }
|
|
+ return plaintext, nil
|
|
+}
|
|
+
|
|
+// noneEncryptor passes data through without encryption.
|
|
+type noneEncryptor struct{}
|
|
+
|
|
+func (e *noneEncryptor) Encrypt(plaintext []byte) ([]byte, error) {
|
|
+ return plaintext, nil
|
|
+}
|
|
+
|
|
+func (e *noneEncryptor) Decrypt(data []byte) ([]byte, error) {
|
|
+ return data, nil
|
|
+}
|
|
+
|
|
+// NewEncryptor creates an Encryptor using the given method and password.
|
|
+// Supported methods: aes-128-gcm, aes-256-gcm, chacha20-ietf-poly1305, none.
|
|
+func NewEncryptor(method, password string) (Encryptor, error) {
|
|
+ switch method {
|
|
+ case "", "none":
|
|
+ return &noneEncryptor{}, nil
|
|
+ case "aes-128-gcm":
|
|
+ key := evpBytesToKey(password, 16)
|
|
+ block, err := aes.NewCipher(key)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "create aes cipher")
|
|
+ }
|
|
+ aead, err := cipher.NewGCM(block)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "create gcm")
|
|
+ }
|
|
+ return &aeadEncryptor{aead: aead}, nil
|
|
+ case "aes-256-gcm":
|
|
+ key := evpBytesToKey(password, 32)
|
|
+ block, err := aes.NewCipher(key)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "create aes cipher")
|
|
+ }
|
|
+ aead, err := cipher.NewGCM(block)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "create gcm")
|
|
+ }
|
|
+ return &aeadEncryptor{aead: aead}, nil
|
|
+ case "chacha20-ietf-poly1305":
|
|
+ key := evpBytesToKey(password, 32)
|
|
+ aead, err := chacha20poly1305.New(key)
|
|
+ if err != nil {
|
|
+ return nil, E.Cause(err, "create chacha20-poly1305")
|
|
+ }
|
|
+ return &aeadEncryptor{aead: aead}, nil
|
|
+ default:
|
|
+ return nil, E.New("unsupported encryption method: ", method)
|
|
+ }
|
|
+}
|
|
+
|
|
+// evpBytesToKey derives a key from a password using the OpenSSL EVP_BytesToKey method (MD5-based).
|
|
+func evpBytesToKey(password string, keyLen int) []byte {
|
|
+ var key []byte
|
|
+ var prev []byte
|
|
+ pass := []byte(password)
|
|
+ for len(key) < keyLen {
|
|
+ h := md5.New()
|
|
+ h.Write(prev)
|
|
+ h.Write(pass)
|
|
+ prev = h.Sum(nil)
|
|
+ key = append(key, prev...)
|
|
+ }
|
|
+ return key[:keyLen]
|
|
+}
|