PAYATHON 2026

Bash 调用 OpenSSL 解密密码时报“error reading input file”

数据小许

结论

这段代码首先需要修正密文的保存方式:$(...) 会删除输出末尾的换行,外层未加引号的展开可能把 Base64 内部换行变成空格,最后 echo -n 写入文件时也不会补回换行。默认的 openssl enc -d -a 读取这样的内容时,可能报出 error reading input file。

建议在加密和解密两端都使用 -a -A,按单行 Base64 处理,并去掉包裹 OpenSSL 命令的 echo -n $(...)。加密时传入的变量也必须加双引号,否则空主口令会导致参数错位。

仅凭当前代码无法确认运行时的主口令值和文件内容,不能直接认定报错是因为 $MASTER_PASSWORD 未被读取。

密文是怎样被改变的

原来的加密函数包含:

echo -n $(echo -n "$2" | openssl enc -aes-256-cbc -pbkdf2 -a -iter 10000 -pass "pass:$1")

这段写法会从三处影响输出:

  • $(...) 删除命令输出末尾的换行。
  • 未加引号的 $(...) 会进行单词分割。在通常的 IFS 设置下,多行 Base64 输出可能被拆成多个参数,再由 echo 用空格连接。
  • echo -n 不会补回末尾换行,保存文件时再次使用 echo -n 也不会补回。

OpenSSL 默认的 Base64 解码模式对换行有处理要求。-A 按单行 Base64 处理输入输出,在两端配套使用可以避免依赖已被命令替换删除的换行。

下面的写法还会掩盖 OpenSSL 的失败状态:

echo -n $(openssl enc ...)

函数最后执行的是 echo。即使 OpenSSL 失败,函数仍可能返回成功,而调用方只得到一个空字符串。

修改加密和解密函数

保留原来的算法、PBKDF2 和迭代次数,让函数直接返回 OpenSSL 的输出和退出状态:

encrypt_password() {
    printf '%s' "$2" |
        openssl enc -aes-256-cbc -pbkdf2 -a -A \
            -iter 10000 -pass "pass:$1"
}

decrypt_password() {
    openssl enc -d -aes-256-cbc -pbkdf2 -a -A \
        -iter 10000 -pass "pass:$1" -in "$2"
}

printf '%s' 按原样输出变量内容,可以避免 echo 对选项或反斜杠的不同处理方式影响密码。

在 new_password 中,将生成并保存密码的部分改为:

pw=$(generate_passwords)

if ! enc_pw=$(encrypt_password "$MASTER_PASSWORD" "$pw"); then
    printf '%s\n' 'Encryption failed' >&2
    return 1
fi

mkdir -p ./data/passwords || return 1
printf '%s' "$enc_pw" > "./data/passwords/${account_name}" || return 1

这段代码先检查加密是否成功,再写入密文。原来的 touch 可以删除,因为重定向本身会创建文件。

在 retrieve_password 中,将调用解密函数的部分改为:

pm_path="./data/passwords/${ac_name}"

if dec_pw=$(decrypt_password "$MASTER_PASSWORD" "$pm_path"); then
    printf '%s\n' "$dec_pw"
else
    printf '%s\n' 'Decryption failed; check the file and master password' >&2
    return 1
fi

空主口令为什么会导致参数错位

原代码调用函数时没有给变量加引号:

enc_pw=$(encrypt_password $MASTER_PASSWORD $pw)

当 MASTER_PASSWORD 为空时,它通常不会作为空参数保留下来。假设 pw 是一个不含空白的字符串,函数实际收到的参数会变成:

$1 = pw 的值
$2 = 未提供

加密使用的口令因此成了账户密码,待加密内容则为空。解密调用已经加了双引号,会保留空主口令,所以两次操作使用的口令不一致。

正确的传参方式是:

enc_pw=$(encrypt_password "$MASTER_PASSWORD" "$pw")

-pass "pass:$1" 可以表达空口令,但空字符串必须作为第一个参数传入,加密和解密两端也必须使用相同的值。主口令含有空格或通配符时,同样不能省略双引号。

如何继续定位已有文件的问题

先用修改后的函数新建一个测试账户,再立即读取,验证当前加密、保存和解密流程是否一致。旧文件可能已经受到原代码的影响。

如果仍然失败,先确认路径和文件状态:

printf 'Ciphertext path: %s\n' "$pm_path"

if [ ! -f "$pm_path" ] || [ ! -r "$pm_path" ] || [ ! -s "$pm_path" ]; then
    printf '%s\n' 'Ciphertext file is missing, unreadable, or empty' >&2
    return 1
fi

然后直接运行解密命令,查看 OpenSSL 的完整错误输出:

openssl enc -d -aes-256-cbc -pbkdf2 -a -A \
    -iter 10000 -pass "pass:$MASTER_PASSWORD" -in "$pm_path"

文件存在且可读,只能排除部分路径和权限问题,不能证明 Base64 内容完整或主口令正确。如果已有文件仅缺少末尾换行,使用 -A 可能解决问题。如果内部换行已经被改成空格、内容被截断,或最初加密时参数已经错位,就不能保证只修改解密参数即可恢复。

还有一处独立问题:原来的账户存在性判断 -f ... && ! -s ... 只会拦截已经存在的空文件。如果目的是禁止覆盖已有账户,可以改为检查 [ -e "./data/passwords/${account_name}" ]。

备注:内容仅供参考。