yyy

Bark 加密服务

背景

用 Bark 推送通知很方便,但如果内容包含密码、API 密钥这类敏感信息,直接明文发送总会有些不放心。

Bark 本身不支持加密,但在自动化脚本或集成的第三方服务中,实现这一加密步骤通常较为复杂。为了解决这个痛点,用 Golang 写了个加密转发器,只需要把消息发给它,它就会自动帮你加密好再发给 Bark,省心又安全。

工作原理

这个服务本质上是一个反向代理,它会拦截发往特定路径(如 /push-ciphertext)的请求,提取其中的 JSON 数据,使用预设的密钥(BARK_AES_KEY)和请求中提供的初始化向量(iv)对 body 字段进行 AES-CBC 加密,然后将加密后的数据包重新组装,发送给真正的 Bark 服务器。

代码实现:

39 collapsed lines
package main
import (
"bytes"
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"encoding/json"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
)
func PKCS7Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(ciphertext, padtext...)
}
func encrypt(plainText, key, iv []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
plainText = PKCS7Padding(plainText, block.BlockSize())
blockMode := cipher.NewCBCEncrypter(block, iv)
encrypted := make([]byte, len(plainText))
blockMode.CryptBlocks(encrypted, plainText)
return encrypted, nil
}
type RequestData struct {
DeviceKey string `json:"device_key"`
Iv string `json:"iv"`
}
func handler(w http.ResponseWriter, r *http.Request, aesKey []byte, barkDomain string) {
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(r.Body)
if r.Method != http.MethodPost {
http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed)
return
}
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "Failed to read request body", http.StatusInternalServerError)
return
}
var data RequestData
if err := json.Unmarshal(body, &data); err != nil {
http.Error(w, "Invalid JSON", http.StatusBadRequest)
return
64 collapsed lines
}
deviceKey := data.DeviceKey
iv := data.Iv
encrypted, err := encrypt(body, aesKey, []byte(iv))
if err != nil {
http.Error(w, "Encryption failed", http.StatusInternalServerError)
return
}
ciphertextBase64 := base64.StdEncoding.EncodeToString(encrypted)
if len(barkDomain) <= 0 {
data := map[string]interface{}{
"ciphertext": ciphertextBase64,
"iv": iv,
}
err := json.NewEncoder(w).Encode(data)
if err != nil {
http.Error(w, "Error encoding JSON response", http.StatusInternalServerError)
return
}
return
}
formData := url.Values{}
formData.Set("ciphertext", ciphertextBase64)
formData.Set("iv", iv)
targetURL := barkDomain + "/" + deviceKey
resp, err := http.Post(targetURL, "application/x-www-form-urlencoded", strings.NewReader(formData.Encode()))
if err != nil {
http.Error(w, "Failed to forward request to Bark API", http.StatusBadGateway)
return
}
defer func(Body io.ReadCloser) {
_ = Body.Close()
}(resp.Body)
log.Printf("Forwarded request for deviceKey ending in ...%s, status: %d", deviceKey[len(deviceKey)-4:], resp.StatusCode)
w.WriteHeader(resp.StatusCode)
_, _ = io.Copy(w, resp.Body)
}
func main() {
aesKeyStr := os.Getenv("BARK_AES_KEY")
if len(aesKeyStr) != 16 {
log.Fatal("Fatal: BARK_AES_KEY environment variable not set or not 16 characters long.")
}
barkDomain := os.Getenv("BARK_DOMAIN")
aesKeyBytes := []byte(aesKeyStr)
http.HandleFunc("/push-ciphertext", func(w http.ResponseWriter, r *http.Request) {
handler(w, r, aesKeyBytes, barkDomain)
})
log.Println("Starting encryption service on :9090")
if err := http.ListenAndServe(":9090", nil); err != nil {
log.Fatalf("Failed to start server: %v", err)
}
}

反向代理加密服务

Caddyfile
bark.example.com {
encode zstd gzip
# (可选) 为你的服务增加一层基础认证,防止被滥用
basic_auth {
user xxxx
}
# 处理加密请求的路由, 所有发往 /push-ciphertext 的请求,都转发给加密服务
handle /push-ciphertext* {
reverse_proxy bark-encrypt:9090
}
# 处理普通未加密请求的路由 (可选), 这样你的域名也能兼容普通的 Bark 请求
handle {
reverse_proxy bark-server:8080
}
}

使用

配置环境变量

Terminal window
BARK_AES_KEY=1234567890123456 # 自行替换
BARK_DOMAIN=bark.example.com

路径 push 改为 push-ciphertext 参数加上iv 则可加密发送

Terminal window
curl -X POST --location "https://user:[email protected]/push-ciphertext" \
-H "Content-Type: application/json" \
-d '{
"title": "test",
"device_key": "xxx",
"sound": "newsflash",
"iv": "1234567890123456"
}'

注:这里假设你启用了 basic_auth,所以 URL 中包含了 user:password@。如果没启用,可以去掉。

参考

感谢 Bark 开源,也欢迎大家试用我的小工具!

#study