在日常生活中,我们一定有许多文件是放在自己电脑里不想让别人看到的。就拿照片举例吧,现在有一个文件夹,里面全是我们拍过的照片,下面让我们一步一步来看看如何使用GPG对它们进行批量加密。
(想了解更详尽的GPG使用方法,可以参阅http://blog.csdn.net/puppylpg/article/details/50901779)
先把最终的成果放上来——
批量加密脚本EncryptionBatch:
# Usage : EncryptionBatch
# for i in `ls`
# gpg -e $i
for i in `ls $1 | sed "s:^:$1/:"`
do
gpg -r $2 -e $i
done
批量解密脚本DecryptionBatch:
# Usage : DecryptionBatch
# for i in `ls`
# gpg --passphrase -o `echo $i | sed 's/....$//'` -d $i
for i in `ls $1 | sed "s:^:$1/:"`
do
newFile=`echo $i | sed 's/....$//'`
gpg --passphrase $2 -o $newFile -d $i
done
以下是写批量加密脚本需要的小知识。
首先,我们要学会的是如何遍历一个文件夹下的所有文件,很简单,比如将所有本文件夹下的文件加密就是:
for i in `ls`
gpg -e $i
第一步是循环遍历,变量i用来遍历`ls`
的结果,注意这里ls命令用撇号包围,代表“运行ls命令所出现的结果”,即当前文件夹下所有的文件名。则变量i就变成了遍历当前文件夹下所有的文件名,便实现了对所有文件的遍历。
第二步就比较简单了,gpg -r
代表对一个文件加密,使用的是Recipient的公钥,则这个被加密的文件只能被Recipient的私钥解密了。接下来使用变量i的时候注意要加上$
符号。
具体的脚本文件在实现的时候用到了传参。首先看我们的脚本命令使用的格式:
EncryptionBatch <Folder> <Recipient>
即我们的EncryptionBatch
命令有两个参数,第一个是要加密的文件所在的文件夹,第二个是接收人,即用谁的公钥加密。
接下来在使用参数的时候,只需要知道$0
代表当前脚本的文件名,$n
代表传递给脚本或函数的参数,其中n是一个数字,表示第几个参数。例如,第一个参数是$1
,第二个参数是$2
。Shell的特殊变量还有很多,在这里只知道这一点儿就足够了。
在具体处理的时候,还有一个路径的问题,我们的做法是对于我们要处理的文件,我们要在文件名之前添上我们输入的文件夹的路径,这样就拼出了要处理的文件相对于我们输入的文件夹的路径。
这也就意味着,刚刚简单的
for i in `ls $1`
要被替换为
for i in `ls $1 | sed "s:^:$1/:"`
也就是说多了个文件名和我们输入的路径的拼接过程。
sed
命令在所有行之前/后拼接上myString
字符串:
sed 's/^/myString/g' file
sed 's/$/myString/g' file
实际上s
代表替换,之所以起到“拼接”的效果,我认为是因为拿字符串myString
和行首^
或行末$
去替换的结果,拿一个存在的东西去替换一个不存在的东西,实际上就相当于拼接了。
其中的正斜杠/
可以用冒号:
等效替代。所以在我们的脚本中,ls $1 | sed "s:^:$1/:"
的意思就是将$1/
这个字符串拼接在ls $1
所显出出来的文件名之前。注意这里的字符串中包含正斜杠/
,所以我们的sed
命令的分割符只能用冒号,而不能用正斜杠。
拓展延伸:显示文件的绝对路径
显示文件相对路径的方式为:
lgl@pArch ~/tmp/tmptmp $ ls
p08029.jpg p27080.jpg p27500.jpg p60873.jpg p95018.jpg p95309.jpg
显示文件的绝对路径的方式为:
ls | sed "s:^:`pwd`/:"
比如:
lgl@pArch ~/tmp/tmptmp $ ls | sed "s:^:`pwd`/:"
/home/lgl/tmp/tmptmp/p08029.jpg
/home/lgl/tmp/tmptmp/p27080.jpg
/home/lgl/tmp/tmptmp/p27500.jpg
/home/lgl/tmp/tmptmp/p60873.jpg
/home/lgl/tmp/tmptmp/p95018.jpg
/home/lgl/tmp/tmptmp/p95309.jpg
我们还可以写出它的等价命令ls | sed "s:^:$PWD/:"
,这是因为我们的系统变量$PWD
等价于pwd
命令:
lgl@pArch ~/tmp/tmptmp $ pwd
/home/lgl/tmp/tmptmp
lgl@pArch ~/tmp/tmptmp $ echo $PWD
/home/lgl/tmp/tmptmp
使用$PWD
的好处是可以用在出现了撇号嵌套的场合,比如:
for i in `ls $1 | sed "s:^:`pwd`/:"`
上面的写法显然会报错,因为撇号出现了嵌套,在解析命令时就出现了混乱。
而且,防止撇号嵌套还有一种很好的方法,就是用$(...)
代替撇号:
for i in `ls $1 | sed "s:^:$(pwd)/:"`
写到这里,脚本的内容已经完成了,但目前这还只是一个普通文件,并不能执行,使用chmod
命令为其添加可执行权限:
chmod u+x EncryptionBatch
现在这就是一个可执行的脚本文件了。
以上,批量加密脚本就完成了,相对还是比较简单的。比如我们要给Screenshots文件夹下的所有截图文件加密——
首先看看当前文件夹下的所有文件:
lgl@pArch ~/tmp/批量加密图片/Screenshots $ ls
Screenshot_2014-11-27-00-35-08.png Screenshot_2014-12-04-10-00-28.png Screenshot_2014-12-12-10-07-49.png
...
...
...
我把文件EncryptionBatch放到了Screenshots目录的外面,和Screenshots目录处于同一层。
下面对所有截图文件进行加密,用puppylpg的公钥加密:
lgl@pArch ~/tmp/批量加密图片/Screenshots $ ../EncryptionBatch . puppylpg
这里.
表示当前文件夹,因为我们的Shell所在的当前文件夹是Screenshots。
这样对于每一个.png
文件都会对应出现一个.png.gpg
格式的加密文件。到这里,实际上加密已经完成了!
现在我们就可以销毁掉原文件,只留下加密文件了:
lgl@pArch ~/tmp/批量加密图片/Screenshots $ rm *.png
批量解密脚本相对来说稍微复杂一点点。
我们想要达到的效果是,解密后的文件跟加密前的完全一样,比如之前有一个文件叫做Arch.png
,加密后出现了Arch.png.gpg
,之后我们删掉了原文件Arch.png
。现在我们想要在对Arch.png.gpg
文件解密后出现Arch.png
,所以要对文件名进行一下处理。
对文件名的处理我们用到了echo
和sed
命令。echo
的作用是显示其后面的内容,比如echo Hello,world!
的作用就是在shell中原封不动地输出Hello,world!
(记得第一次接触到这个命令的时候,我心里万马奔腾,分明感觉这条指令就是个逗比=.=。当时真是too young啊,其实这条命令还是挺好用的)。在这里,我们想要根据Arch.png.gpg
得到Arch.png
,只需要删掉最后的四个字符即可:
lgl@pArch ~ $ echo Arch.png.gpg | sed 's/....$//'
Arch.png
可以看到上面的命令正好可以达到这个效果。
注意中间的|
,它叫做管道,能够把前面的echo
命令和后面的sed
命令连接起来,作用是把前面命令的输出作为后面命令的输入。很显然,前面的echo
命令的结果是Arch.png.gpg
字符串,接着管道把这个字符串拿过来,交给了sed
。那么sed
对这个字符串做了什么处理呢?sed 's/....$//'
是替换命令,单引号中第一个s代表替换,格式为's/oldString/newString/global'
,是用newString替代oldString,后面的global代表有多少oldString就替换为多少处newString,而如果不带global,说明只用newString替换第一处oldString出现的地方。其中的字符串可以用正则表达式代替。因此,这里的's/....$//'
代表用“什么都没有”代替最后的四个字符,即“删除最后的四个字符”。
接下来要做的就跟批量加密脚本的内容比较像了。这里需要注意的是第8行的newFile变量在赋值时,等号两端不能有空格,否则会出现语法错误……
最后,使用解密命令gpg -o
可以实现解密,但是这样的话我们就要输入N遍passphrase来进行一一解密了,这样的话基本上也就失去了“脚本”的意义。为了解决这个问题,我们还可以加上--passphrase
参数,gpg --passphrase
,代表使用接收人的passphrase,调用私钥将秘密文件fileName解密,并将内容输出到newFilename。这个时候只需输入一次passphrase就可以了。
同样,DecryptionBatch文件也要被赋予可执行权限。我们也将他放在Screenshots的上层目录。
接下来,使用DecryptionBatch
命令对刚刚我们加了密的文件进行解密即可:
lgl@pArch ~/tmp/批量加密图片/Screenshots $ ../DecryptionBatch . xxxxxxxxxxxxx
gpg: 由 2048 位的 RSA 密钥加密,钥匙号为 83E2EBC9、生成于 2016-03-16
“puppylpg 163.com>”
gpg: 由 2048 位的 RSA 密钥加密,钥匙号为 83E2EBC9、生成于 2016-03-16
“puppylpg 163.com>”
...
...
...
可以看到,为了保护我的隐私,我的passphrase在这里就以xxxxxxxxx代替了,不再展示出来。这也是这个解密脚本目前最大的缺陷:虽然可以做到一次性解密,但是必须把自己的passphrase输出出来,并且是明文的=.=,理论上应该是执行了DecryptionBatch
命令之后,再提示我以标准的Unix密码的形式输入passphrase的……
不过目前这两个小脚本只是我心血来潮突然想写了,于是趁着兴奋劲儿熬夜赶出来的,至于这个小缺陷,日后可以再改进。再说了,本来就是解密一些自己的秘密文件,自然是别人不在的时候才这么做的,所以以明文的形式输入命令貌似问题也不大,只要及时清除shell执行命令的历史即可。(好吧,我承认我就是在为这个缺陷狡辩……不过鉴于我是毫无征兆地第一次在Linux上写脚本,就不要太吐槽我了呗……没办法,先睡觉吧,忙过这几天再改进吧……)
I love CS. Good night, world~
/*==========================================
2016.03.19 缺陷修复
==========================================*/
为了修复之前的passphrase为明文输入的缺陷,特意查了一下linux脚本读取密码的问题,其实还是挺简单的。
linux脚本输入密码,需要用到echo
和read
两条指令,先举一个例子:
#!/bin/bash
# Read Password
echo -n Password:
read -s password
echo
# Run Command
echo $password
对其执行的结果为:
lgl@pArch ~/tmp/UnixStylePassword $ ./psword
Password:
jkjk
第一行显示Password:,我输入了jkjk,shell没有任何反映,这就是Unix密码的风格,让旁人甚至无从得知密码的长度。回车之后,shell输出jkjk,说明其获取的密码正是我们刚刚输入的jkjk。
我们再一条一条看脚本。
echo -n Password:
在屏幕上输出Password:
这个字符串,即提醒我们该在shell中输入东西了,输入的是我们的密码。-n
参数的意思是不输出行末的换行符\n
。
read -s password
:这一句才是读取密码关键。read将我们的输入读入到了变量password
中,其中-s
为Do not display password on screen. It causes input coming from a terminal/keyboard to not be echoed.正是-s
命令使我们的输入不在Shell中显示。
echo
:接下来的这句echo
看起来比较突兀,那么它究竟有什么用途呢?
lgl@pArch ~ $ echo
lgl@pArch ~ $ echo -n
lgl@pArch ~ $
看上面的演示就可以明白,echo后面是有一个换行符的,所以单独的一个echo就是在Shell中显示一个换行符,即起到了换行的作用。如果我们使用echo -n
,正好验证了这一点,由于连行末的换行符都不输出,所以这一条指令就相当与什么都没做。
echo $password
:最后一句就是把我们刚刚存入变量password的内容显示出来,说明我们的确把密码存入了变量password中。
以上我们就完成了完整的Unix风格的密码读入,关键就两句:echo -n Password:
和read -s password
。
另外,我们还可以仅用一条read -s -p "Password: " password
实现和上面两条命令等价的效果。因为read -s -p “Password: ” VARIABLE
代表输出-p
后面的字符串,并且将输入内容存储到VARIABLE中。
因此我们的DecryptionBatch就可以修改成这样:
# Usage : DecryptionBatch
# for i in `ls`
# gpg --passphrase -o `echo $i | sed 's/....$//'` -d $i
# Read passphrase
echo -n passphrase:
read -s passphrase
echo
for i in `ls $1 | sed "s:^:$1/:"`
do
newFile=`echo $i | sed 's/....$//'`
gpg --passphrase $passphrase -o $newFile -d $i
done
最后,我们解密的应该是被加密过的文件,如果我们使用gpg
解密没有加密过的文件,会产生错误:”gpg: 找不到有效的 OpenPGP 数据。gpg: decrypt_message failed: 未知的系统错误”。所以我们要筛选出需要解密的文件,使用grep
命令即可:
# Usage : DecryptionBatch
# for i in `ls`
# gpg --passphrase -o `echo $i | sed 's/....$//'` -d $i
# Read passphrase
echo -n passphrase:
read -s passphrase
echo
# find all the .gpg file
for i in `ls $1 | sed "s:^:$1/:" | grep .gpg`
do
newFile=`echo $i | sed 's/.gpg$//'` # generate fileName without ".gpg"
gpg --passphrase $passphrase -o $newFile -d $i # decryption
done
这样缺陷就被修复了。当然,由于我们的passphrase无需明文输入,DecryptionBatch
的用法也做了一点儿修改:
DecryptionBatch <Folder>
只需要输入目录就好了。