Skip to content
加密配置文件字段
  • 流程
text
+-----------------------------+
|   程序启动 (main.py)         |
+-----------------------------+
                |
                v
+-----------------------------+
|   加载 YAML (config_loader) |
+-----------------------------+
                |
                v
+----------------------------------------------+
| 递归遍历配置树:发现 !enc 字段 → 进入解密流程      |
+----------------------------------------------+
                |
                v
+----------------------------------------------------+
| 根据 key_id 找到 master key(环境变量/KMS/Vault)     |
+----------------------------------------------------+
                |
                v
+-----------------------------+
|       执行解密(AES-GCM)    |
+-----------------------------+
                |
                v
+--------------------------------------------------------+
|   将该字段替换成解密后的明文,例如 "ProdDB_Password123!"    |
+--------------------------------------------------------+
                |
                v
+----------------------------+
|   返回完整配置 dict 给应用    |
+----------------------------+
                |
                v
      应用像平常一样使用配置
  • 实现
    • 依赖: uv add PyYAML cryptography

    • 配置文件
    yaml
    # 配置 config/app.yaml
    database:
      host: "10.0.0.1"
      port: 5432
      username: "appuser"
      password:
        alg: AES-256-GCM
        ciphertext: QeegGkeZ60ZHzLgD7bebbKwO7A==
        key_id: local-master-v1
        nonce: 2GKhoAPzWIL4pYqL
        tag: 88Ufi1Slxv8Itg3OoYax4g==
        version: 1
    • 生成加密文字段内容,将输出配置到config/app.yaml
    python
    # scripts/encrypt_field.py
    import os, base64, yaml
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    
    def b64(x): return base64.b64encode(x).decode()
    def unb64(x): return base64.b64decode(x)
    
    # master key(必须通过环境变量提供)
    MASTER_KEY = unb64(os.environ["MASTER_KEY_B64"])
    
    def encrypt_value(plaintext: str):
        aes = AESGCM(MASTER_KEY)
        nonce = os.urandom(12)
        ct = aes.encrypt(nonce, plaintext.encode(), None)
    
        return {
            "alg": "AES-256-GCM",
            "nonce": b64(nonce),
            "ciphertext": b64(ct[:-16]),  # cryptography 把 tag 拼在 ct 末尾
            "tag": b64(ct[-16:]),
            "key_id": "local-master-v1",
            "version": 1
        }
    
    if __name__ == "__main__":
        plaintext = input("请输入要加密的字段内容: ").strip()
        encrypted = encrypt_value(plaintext)
        print(yaml.dump({"password": encrypted}, allow_unicode=True))
    
    
    # 运行脚本 scripts/encrypt_field.py 将输出的内容替换config/app.yaml需要加密的字段
    # export MASTER_KEY_B64=$(openssl rand -base64 32)  这个环境变量解密时需要用到
    # python scripts/encrypt_field.py
    # 请输入要加密的字段内容: ProdDB_Password123!
    # password:
    #   alg: AES-256-GCM
    #   ciphertext: ZKjSvsbKstcP7dXqyq12nTgGXg==
    #   key_id: local-master-v1
    #   nonce: Ev3Ccy6E5yZ7MdhO7rVllQ==
    #   tag: SSgmrduEqKbvfj9t2XTMFQ==
    #   version: 1
    
    ## 这里的 password 字段为配置中需要加密的字段
    • 程序中解密配置文件中的加密字段
    python
    # 这是生产级的“读取加密配置”逻辑,启动时运行解密
    # app/config_loader.py
    import os, base64, yaml
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    
    def unb64(s): return base64.b64decode(s)
    
    # 这里的环境变量与上面生成加密文本时使用的环境变量一致
    # master key 必须来自运行时环境变量(不能写在代码里)
    MASTER_KEY = unb64(os.environ["MASTER_KEY_B64"])
    
    def decrypt_field(node: dict):
        nonce = unb64(node["nonce"])
        ct = unb64(node["ciphertext"])
        tag = unb64(node["tag"])
    
        aes = AESGCM(MASTER_KEY)
        plaintext = aes.decrypt(nonce, ct + tag, None)
        return plaintext.decode()
    
    def traverse(obj):
        """遍历配置树,遇到 !enc 节点时执行解密"""
        if isinstance(obj, dict):
            # 是否为加密字段
            if set(obj.keys()) >= {"alg", "nonce", "ciphertext", "tag"}:
                return decrypt_field(obj)
    
            # 遍历普通 dict
            return {k: traverse(v) for k, v in obj.items()}
    
        if isinstance(obj, list):
            return [traverse(v) for v in obj]
    
        return obj
    
    def load_config(path):
        with open(path, "r", encoding="utf8") as f:
            raw = yaml.safe_load(f)
        return traverse(raw)
    • 主程序使用加密配置
    python
    from app.config_loader import load_config
    
    config = load_config("config/app.yaml")
    
    print("最终配置:")
    print(config)
    
    print("数据库密码 = ", config["database"]["password"])
    
    # 输出如下
    
    ## 最终配置:
    ## {'database': {'host': '10.0.0.1', 'port': 5432, 'username': 'appuser', 'password': 'ProdDB_Password123!'}}
    ## 数据库密码 = ProdDB_Password123!

欢迎来到 Laazua 的站点