v1.2.1
This commit is contained in:
78
package.py
78
package.py
@@ -5,11 +5,17 @@
|
||||
根据 app.yaml 中列出的文件,打包成 zip 文件
|
||||
版本号从 version.py 中读取
|
||||
"""
|
||||
import argparse
|
||||
import os
|
||||
import yaml
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
import sys
|
||||
import secrets
|
||||
|
||||
MAGIC = b"AROTAE1" # 7 bytes: Archery OTA Encrypted v1
|
||||
GCM_NONCE_LEN = 12
|
||||
GCM_TAG_LEN = 16
|
||||
|
||||
# 添加当前目录到路径,以便导入 version 模块
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -91,8 +97,63 @@ def create_zip_package(app_info, files, output_dir='.', base_dir='.'):
|
||||
return None
|
||||
|
||||
|
||||
def _validate_key_hex(key_hex: str) -> bytes:
|
||||
if not isinstance(key_hex, str):
|
||||
raise ValueError("aead key must be hex string")
|
||||
key_hex = key_hex.strip().lower()
|
||||
if key_hex.startswith("0x"):
|
||||
key_hex = key_hex[2:]
|
||||
if len(key_hex) != 64:
|
||||
raise ValueError("aead key must be 64 hex chars (32 bytes)")
|
||||
try:
|
||||
key = bytes.fromhex(key_hex)
|
||||
except Exception as e:
|
||||
raise ValueError(f"invalid hex key: {e}")
|
||||
if len(key) != 32:
|
||||
raise ValueError("aead key must be 32 bytes")
|
||||
return key
|
||||
|
||||
|
||||
def encrypt_zip_aead(zip_path: str, key_hex: str, out_ext: str = ".enc") -> str:
|
||||
"""
|
||||
Encrypt the whole zip file as one blob:
|
||||
output format: MAGIC(7) | nonce(12) | ciphertext(N) | tag(16)
|
||||
using AES-256-GCM (AEAD).
|
||||
"""
|
||||
# Lazy import: packaging-only dependency
|
||||
try:
|
||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
|
||||
except Exception as e:
|
||||
raise RuntimeError(
|
||||
"Missing dependency: cryptography. Install with: pip install cryptography. "
|
||||
f"Import error: {e}"
|
||||
)
|
||||
|
||||
key = _validate_key_hex(key_hex)
|
||||
with open(zip_path, "rb") as f:
|
||||
plain = f.read()
|
||||
|
||||
nonce = secrets.token_bytes(GCM_NONCE_LEN)
|
||||
aesgcm = AESGCM(key)
|
||||
ct_and_tag = aesgcm.encrypt(nonce, plain, None) # ciphertext || tag (16 bytes)
|
||||
|
||||
enc_path = zip_path + out_ext if out_ext else (zip_path + ".enc")
|
||||
with open(enc_path, "wb") as f:
|
||||
f.write(MAGIC)
|
||||
f.write(nonce)
|
||||
f.write(ct_and_tag)
|
||||
|
||||
return enc_path
|
||||
|
||||
|
||||
def main():
|
||||
"""主函数"""
|
||||
parser = argparse.ArgumentParser(description="打包 app.yaml 文件列表到 zip,并可选进行 AES-256-GCM 加密输出 .enc")
|
||||
parser.add_argument("--aead-key-hex", default=None, help="AES-256-GCM key (64 hex chars = 32 bytes). If set, output encrypted file.")
|
||||
parser.add_argument("--keep-zip", action="store_true", help="Keep the plaintext zip when encryption is enabled.")
|
||||
parser.add_argument("--out-ext", default=".enc", help="Encrypted output extension appended to zip path. Default: .enc (produces *.zip.enc)")
|
||||
args = parser.parse_args()
|
||||
|
||||
print("=" * 60)
|
||||
print("应用打包脚本")
|
||||
print("=" * 60)
|
||||
@@ -139,6 +200,23 @@ def main():
|
||||
zip_path = create_zip_package(app_info, existing_files)
|
||||
|
||||
if zip_path:
|
||||
enc_path = None
|
||||
if args.aead_key_hex:
|
||||
try:
|
||||
enc_path = encrypt_zip_aead(zip_path, args.aead_key_hex, out_ext=args.out_ext)
|
||||
enc_size = os.path.getsize(enc_path)
|
||||
print(f"\n[SUCCESS] AEAD加密完成: {os.path.basename(enc_path)} ({enc_size:,} bytes)")
|
||||
print(f" 文件路径: {os.path.abspath(enc_path)}")
|
||||
if not args.keep_zip:
|
||||
try:
|
||||
os.remove(zip_path)
|
||||
print(f"[INFO] 已删除明文zip: {os.path.basename(zip_path)}")
|
||||
except Exception as e:
|
||||
print(f"[WARNING] 删除明文zip失败(可忽略): {e}")
|
||||
except Exception as e:
|
||||
print(f"\n[ERROR] AEAD加密失败: {e}")
|
||||
print("[ERROR] 保留明文zip用于排查。")
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print("打包成功完成!")
|
||||
print("=" * 60)
|
||||
|
||||
Reference in New Issue
Block a user