Shell脚本如何解密配置文件并加载

wen 实用脚本 1

Shell脚本如何解密配置文件并加载:从加密存储到安全运行的最佳实践

目录导读


为什么需要加密配置文件?

在运维开发中,配置文件常包含数据库密码、API密钥、SSH私钥等敏感信息,如果直接以明文形式储存在脚本目录或Git仓库中,一旦服务器被入侵或代码库泄露,将导致严重安全事件。加密配置文件并让Shell脚本在运行时动态解密加载已成为企业级部署的标配。

Shell脚本如何解密配置文件并加载

典型的场景包括:

  • CI/CD流水线中动态传递密钥
  • 多环境配置(开发/测试/生产)无需手动替换明文
  • 敏感信息与代码分离,符合合规要求

加密算法选择与密钥管理

推荐加密工具

  1. OpenSSL:跨平台,支持对称加密(AES-256-CBC)和非对称加密
  2. GnuPG (gpg):适合非对称加密,密钥对管理更严格
  3. Vault工具:HashCorp Vault的CLI可直接配合Shell使用(但本文聚焦纯脚本方案)

密钥管理原则

  • 密钥不要硬编码在脚本中,应存入环境变量或密钥管理服务
  • 生产环境使用HSM或Vault存储根密钥,开发环境使用独立密钥文件
  • 定期轮换密钥并记录变更日志

Shell脚本解密流程详解

以下是一个基于AES-256-CBC加密的完整操作步骤:

步骤1:加密配置文件

# 加密前:config.ini 包含明文
# 加密命令(生成随机IV)
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in config.ini -out config.enc -pass pass:"YOUR_SECRET_KEY"
# 推荐使用-fd参数从文件读取密码
openssl enc -aes-256-cbc -salt -pbkdf2 -iter 100000 -in config.ini -out config.enc -pass file:/path/to/keyfile

步骤2:解密并加载到环境变量

#!/bin/bash
# 安全加载脚本:load_config.sh
# 从安全位置读取密钥(示例:从环境变量读取)
ENCRYPTION_KEY="${CONFIG_DECRYPT_KEY:?错误:缺少解密密钥}"
# 临时解密到内存文件系统(避免写入磁盘)
TEMP_DIR=$(mktemp -d) || { echo "无法创建临时目录"; exit 1; }
trap "rm -rf $TEMP_DIR" EXIT  # 脚本退出时自动清除
# 解密到临时文件
openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 100000 \
  -in /path/to/config.enc \
  -out "$TEMP_DIR/config.dec" \
  -pass pass:"$ENCRYPTION_KEY" 2>/dev/null
if [ $? -ne 0 ]; then
    echo "解密失败,密钥可能错误"
    exit 1
fi
# 加载配置文件(支持多种格式)
source "$TEMP_DIR/config.dec"  # 适用于键值对格式
# 或者逐行读取处理
while IFS='=' read -r key value; do
    if [ -n "$key" ] && [ -n "$value" ]; then
        export "$key"="$value"
    fi
done < "$TEMP_DIR/config.dec"

环境变量加载的多种实现方式

直接source解密后的文件

适合配置格式为 KEY=VALUE 的bash兼容文件,需注意防止变量注入,可在加载前用正则校验:

if grep -qE '^[A-Za-z_][A-Za-z0-9_]*=[^;]*$' "$TEMP_DIR/config.dec"; then
    source "$TEMP_DIR/config.dec"
else
    echo "配置文件包含非法格式"
    exit 1
fi

使用eval谨慎加载(不推荐)

eval $(cat "$TEMP_DIR/config.dec")  # 有命令注入风险

逐行安全导出

推荐用于生产环境,避免直接source的执行风险:

while IFS='=' read -r key value; do
    # 去除首尾空白
    key=$(echo "$key" | xargs)
    value=$(echo "$value" | xargs)
    if echo "$key" | grep -qE '^[a-zA-Z_][a-zA-Z0-9_]*$'; then
        export "$key=$value"
    fi
done < <(grep -v '^#' "$TEMP_DIR/config.dec")  # 跳过注释行

使用envsubst动态替换模板

如果配置文件中有占位符${DB_PASSWORD},可先解密然后替换:

export DB_PASSWORD=$(openssl ...)  # 解密单个值
envsubst < template.conf > /app/config.conf

安全加固:防止密钥泄露与日志审计

常见风险点与对策

风险点 解决方案
脚本中硬编码密钥 从环境变量、Vault或密钥服务读取
解密后临时文件残留 使用mktemp并配合trap自动清理
命令行参数泄露 避免在ps命令中暴露密码(用-pass file:
日志包含敏感内容 在脚本开头set +x关闭命令追踪,并过滤日志输出

高级安全措施

  • 使用gpg非对称加密:配置由管理员的公钥加密,只有持有私钥的服务器能解密
  • 密钥拆分:将密钥分成多份,分发给不同管理员,使用shamir算法重建
  • 集成HSM:通过pkcs11-tool从硬件安全模块获取密钥

常见问题问答

Q1:脚本运行完毕后,解密后的配置文件还在内存中吗?

A:是的,如果使用sourceexport,配置会存在当前Shell的环境变量中,建议在脚本最后显式清除敏感变量:

unset DB_PASSWORD DB_USER API_KEY

或让加载脚本作为子进程运行,父Shell不受影响。

Q2:如何判断加密文件是否被篡改?

A:在加密时加入HMAC完整性校验(OpenSSL不支持,可改用openssl dgst -sha256生成独立校验值),或使用gpg --verify,更简单的方式是加密时使用相同密钥,解密失败即为篡改。

Q3:加密配置文件能放在Git仓库中吗?

A:可以,但绝对不要提交密钥文件,在.gitignore中添加*.enc的密钥文件路径,使用git-crypt等工具管理加密存储。

Q4:多环境如何管理不同密钥?

A:建议方案:

  • 开发环境:密钥存放在~/.env_secret文件中(不提交Git)
  • 测试环境:通过CI/CD环境变量注入
  • 生产环境:使用Vault或密钥管理服务自动分发

Q5:性能如何?每次加载都要解密数百次?

A:AES-256-CBC解密约1MB数据仅需0.1秒左右,建议将解密结果缓存到环境变量,或使用一次性加载(如系统启动时只解密一次)。

Q6:如果加密密钥泄露怎么办?

A:立即执行紧急响应:

  1. 生成新密钥
  2. 重新加密所有配置文件
  3. 更新所有服务器的环境变量
  4. 撤销旧密钥的访问权限
  5. 审计日志找出泄露源

实际案例:完整解密加载脚本

以下是一个生产可用的脚本,整合了加密、解密、加载和清理功能:

#!/usr/bin/env bash
# 文件名:secure_config_loader.sh
# 功能:解密AES-256-CBC加密的配置文件并加载到环境变量
set -euo pipefail
IFS=$'\n\t'
# 配置
ENCRYPTED_FILE="${CONFIG_ENCRYPTED_PATH:-/etc/secrets/config.enc}"
KEY_FILE="${DECRYPT_KEY_FILE:-/etc/secrets/key}"
# 检查必要文件
if [ ! -f "$ENCRYPTED_FILE" ]; then
    echo "错误:加密配置文件不存在: $ENCRYPTED_FILE"
    exit 2
fi
if [ ! -f "$KEY_FILE" ]; then
    echo "错误:密钥文件不存在: $KEY_FILE"
    exit 2
fi
# 创建临时目录并设置清理陷阱
TEMP_DIR=$(mktemp -d -t config_dec_XXXXXX)
trap 'rm -rf "$TEMP_DIR"; echo "$(date) 清理临时文件完成"' EXIT
# 解密(静默模式,不打印密码)
DECRYPTED_FILE="$TEMP_DIR/decrypted.conf"
if ! openssl enc -aes-256-cbc -d -salt -pbkdf2 -iter 100000 \
    -in "$ENCRYPTED_FILE" \
    -out "$DECRYPTED_FILE" \
    -pass file:"$KEY_FILE" 2>/dev/null; then
    echo "错误:解密失败,检查密钥或文件完整性"
    exit 3
fi
# 验证解密后的内容(防止空文件)
if [ ! -s "$DECRYPTED_FILE" ]; then
    echo "错误:解密后文件为空"
    exit 4
fi
# 安全加载(跳过注释和空行,校验变量名格式)
while IFS='=' read -r key value; do
    # 清理首尾空格和引号
    key=$(echo "$key" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
    value=$(echo "$value" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/^"//;s/"$//')
    # 跳过无效行
    [ -z "$key" ] && continue
    [[ "$key" =~ ^#.* ]] && continue
    # 严格校验变量名格式
    if [[ "$key" =~ ^[a-zA-Z_][a-zA-Z0-9_]*$ ]]; then
        export "$key=$value"
        echo "已加载: $key"   # 仅输出键名,不输出值
    else
        echo "警告:跳过非法变量名: $key"
    fi
done < "$DECRYPTED_FILE"
echo "配置加载完成,共加载 $(env | grep -c '^[A-Z_]') 个变量"

使用方式

  1. 将密钥放在/etc/secrets/key(仅root可读:chmod 600
  2. 加密配置文件放在/etc/secrets/config.enc
  3. 在目标脚本中调用:
    source /path/to/secure_config_loader.sh
    # 现在可以使用DB_PASSWORD等变量

通过以上方案,你可以在不暴露敏感信息的前提下,让Shell脚本安全地读取配置文件并加载到运行环境,核心要点是密钥与配置分离内存即用即清格式严格校验,如果需要更高的安全性,推荐结合TCM/TPM模块或专业的密钥管理服务(如AWS KMS、Azure Key Vault),但本文的纯脚本方案已能满足绝大多数中小规模部署需求。

抱歉,评论功能暂时关闭!