给shell脚本加密,本加本质上是密解密场自欺欺人的游戏。脚本要跑起来,本加解释器就得读到明文,密解密只要机器能读,本加人就一定能读。密解密我见过有人把脚本用gzip压了再用base64套一层,本加写个自解压头,密解密结果ps aux里命令行参数直接暴露了密钥。本加折腾半天,密解密防的本加只是自己的记性。
真正在生产环境里干过运维的密解密人都知道,最常遇到的本加"加密"需求其实来自外包交付。甲方要求把部署脚本给乙方,密解密又不想让乙方看见里面的本加数据库密码和内部API地址。这时候大家心照不宣地用一个叫shc的工具,全称Shell Script Compiler。它把bash脚本转成C源码,编译出二进制,运行时解密到内存再交给shell执行。听起来挺像回事。
装shc很简单,apt install shc或者去官网下源码make一下。用法也直白:
shc -f deploy.sh -o deploy.bin 生成的deploy.bin可以直接跑,行为跟原脚本一模一样。但有个细节很多人第一次用会懵:shc默认设了个过期时间,不指定的话编译出来的二进制跑一段时间后会报"script has expired"。得加-e参数指定日期,比如-e 31/12/2099。这个设计挺莫名其妙,作者大概是防脚本被无限期传播。
shc的加密强度?别抱幻想。它用的RC4变体,密钥硬编码在二进制里,用strings命令扫一下就能看到蛛丝马迹。网上有现成的unshc脚本,原理就是hook掉解密函数,把明文dump出来。我亲自试过,一个下午就能还原出原始脚本。所以shc的定位从来不是安全工具,它只是提高一点阅读门槛,让非技术对接方没法直接cat。
那有没有更靠谱的路子?有,但都绕不开一个矛盾:密钥放哪。把脚本用openssl enc加密,运行时让用户输入密码解密到临时文件再执行,这是常见做法:
openssl enc -aes-256-cbc -salt -in deploy.sh -out deploy.enc openssl enc -d -aes-256-cbc -in deploy.enc | bash 问题在于,管道传给bash时,如果脚本里有交互式read,stdin会被占用导致行为异常。得先解密到临时文件,执行完立刻shred掉。临时文件在/tmp下,其他用户理论上能通过inotify抢读。更稳妥的是用内存文件系统,或者干脆解密到/dev/shm。
还有个思路是用Python或Perl写个wrapper,把脚本内容嵌在源码里,用编译工具打成字节码。比如用pyinstaller打包一个Python脚本,里面用subprocess调bash -c执行内嵌的脚本字符串。这比shc麻烦,但逆向成本高一些。不过Python字节码也不是不能反编译,uncompyle6照样能还原个七七八八。
我个人的判断是:如果脚本里真有敏感信息,别指望加密脚本本身。正确做法是把敏感配置抽出来,放到受权限控制的文件里,脚本运行时去读。比如数据库密码放在/etc/myapp/db.conf,权限600,属主是运行脚本的用户。脚本本身随便看,反正里面只有source /etc/myapp/db.conf。这样既解决了交付时的尴尬,也符合最小权限原则。
实在非要加密,那就接受它只是防君子不防小人。选shc图个省事,选openssl图个心理安慰。别花超过半小时折腾这事,有那时间不如把脚本里的日志和错误处理写扎实点。我见过太多人把精力花在怎么藏密码上,结果脚本一跑就报错,连个像样的报错信息都没有,那才叫真的尴尬。