Pushover는 서버에 메시지를 저장하지 않는다고 한다. 내용이 유출될 가능성은 낮다는 뜻이다. 그럼에도 불구하고 보다 안전한 것은 좋은 것이다. 그래서 E2EE를 지원하도록 해보았다. Gemini 의 도움을 받았다.
공식 홈페이지 내용은 다음과 같다.
#!/bin/sh
KEY="your-64-char-hex-key-here"
encrypt() {
IV=$(openssl rand -hex 16)
CT=$(echo -n "$1" | gzip -9 | \
openssl enc -aes-256-cbc -K "$KEY" -iv "$IV" | xxd -p | tr -d '\n')
HMAC=$(echo -n "${IV}${CT}" | xxd -r -p | \
openssl dgst -sha256 -mac HMAC -macopt hexkey:"$KEY" | awk '{print $NF}')
echo -n "${IV}${CT}${HMAC}" | xxd -r -p | openssl base64 -A
}
curl -s https://api.pushover.net/1/messages.json \
-F "token=APP_TOKEN" \
-F "user=USER_KEY" \
-F "title=$(encrypt "hellorld")" \
-F "message=$(encrypt "This has been encrypted")" \
-F "encrypted=1"
Gemini 에 물어 봤더니 다음과 같은 형식이라고 한다.
Pushover 홈페이지에서 제공하는 쉘 스크립트 예제는 AES-256-CBC 암호화와 HMAC-SHA256 무결성 검증을 결합한 방식입니다.
AES-256은 32바이트(256비트)의 키를 사용한다. 하나의 키를 임의로 생성한다.
import secrets
random_hex = secrets.token_hex(32)
token, user, 그리고 E2EE에 사용할 키를 json 파일로 만들어 둔다. 보안을 신경쓴다면 하드 코딩을 할 수 없다. 다음과 같은 식으로 불러온다.
def load_pushover_key():
with open('pushover.json', 'r') as f:
return json.load(f)
Gemini가 E2EE 코드를 다음처럼 구현했다.
import json
import requests
import gzip
import binascii
from Crypto.Cipher import AES
from Crypto.Hash import HMAC, SHA256
from Crypto.Util.Padding import pad
from Crypto.Random import get_random_bytes
import base64
def pushover_encrypt(message, hex_key):
# 1. 키 준비 (64자 Hex를 바이트로 변환)
key_bytes = binascii.unhexlify(hex_key)
# 2. Gzip 압축 (쉘의 gzip -9와 동일)
compressed_data = gzip.compress(message.encode('utf-8'), compresslevel=9)
# 3. IV 생성 및 AES-256-CBC 암호화
iv = get_random_bytes(16)
cipher = AES.new(key_bytes, AES.MODE_CBC, iv)
# PKCS7 패딩 적용
encrypted_data = cipher.encrypt(pad(compressed_data, AES.block_size))
# 4. HMAC-SHA256 생성 (IV + Ciphertext)
# 쉘 스크립트의 IV(hex)+CT(hex)를 바이너리로 바꾼 후 HMAC 계산하는 로직 재현
hmac_obj = HMAC.new(key_bytes, iv + encrypted_data, digestmod=SHA256)
hmac_signature = hmac_obj.digest()
# 5. 최종 결합 (IV + Ciphertext + HMAC) 및 Base64 인코딩
final_payload = iv + encrypted_data + hmac_signature
return base64.b64encode(final_payload).decode('utf-8')
메시지를 어떻게 보내는지에 따라 메시지 발송 방법이 차이 날 수 있을 것이다. 난 보통 특정 프로그램이 실행될 경우 1번 사용한다. 편하게 함수로 처리했다.
def send_message_e2ee(msg, title=None):
key = load_pushover_key()
url = "https://api.pushover.net/1/messages.json"
payload = {
"token": key['pushover_token'],
"user": key['pushover_user'],
"message": pushover_encrypt(msg, key['secret_key']),
"encrypted": "1"
}
if title:
payload["title"] = pushover_encrypt(title, key['secret_key'])
response = requests.post(url, data=payload)
return response.status_code
참.. 필요한 패키지는 설치해야한다.
pip install pycryptodome