在日常生活中,我们一定有许多文件是放在自己电脑里不想让别人看到的。就拿照片举例吧,现在有一个文件夹,里面全是我们拍过的照片,下面让我们一步一步来看看如何使用GPG对它们进行批量加密。
  (想了解更详尽的GPG使用方法,可以参阅)

  先把最终的成果放上来——
  批量加密脚本EncryptionBatch:

# Usage : EncryptionBatch <Folder> <Recipient>

# 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 <Folder> <passPhrase>

# for i in `ls`
# gpg --passphrase <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> -e <FileName>代表对一个文件加密,使用的是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拼接字符串

  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,所以要对文件名进行一下处理。
  对文件名的处理我们用到了echosed命令。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 <newFilename> -d <fileName>可以实现解密,但是这样的话我们就要输入N遍passphrase来进行一一解密了,这样的话基本上也就失去了“脚本”的意义。为了解决这个问题,我们还可以加上--passphrase参数,gpg --passphrase <passphrase> -o <newFilename> -d <fileName>,代表使用接收人的passphrase,调用私钥将秘密文件fileName解密,并将内容输出到newFilename。这个时候只需输入一次passphrase就可以了。

实例

  同样,DecryptionBatch文件也要被赋予可执行权限。我们也将他放在Screenshots的上层目录。
  接下来,使用DecryptionBatch <Folder> <passPhrase>命令对刚刚我们加了密的文件进行解密即可:

lgl@pArch ~/tmp/批量加密图片/Screenshots $ ../DecryptionBatch . xxxxxxxxxxxxx
gpg: 由 2048 位的 RSA 密钥加密,钥匙号为 83E2EBC9、生成于 2016-03-16
      “puppylpg <thingsregister@163.com>”
gpg: 由 2048 位的 RSA 密钥加密,钥匙号为 83E2EBC9、生成于 2016-03-16
      “puppylpg <thingsregister@163.com>”
...
...
...

缺陷

  可以看到,为了保护我的隐私,我的passphrase在这里就以xxxxxxxxx代替了,不再展示出来。这也是这个解密脚本目前最大的缺陷:虽然可以做到一次性解密,但是必须把自己的passphrase输出出来,并且是明文的=.=,理论上应该是执行了DecryptionBatch命令之后,再提示我以标准的Unix密码的形式输入passphrase的……
  不过目前这两个小脚本只是我心血来潮突然想写了,于是趁着兴奋劲儿熬夜赶出来的,至于这个小缺陷,日后可以再改进。再说了,本来就是解密一些自己的秘密文件,自然是别人不在的时候才这么做的,所以以明文的形式输入命令貌似问题也不大,只要及时清除shell执行命令的历史即可。(好吧,我承认我就是在为这个缺陷狡辩……不过鉴于我是毫无征兆地第一次在Linux上写脚本,就不要太吐槽我了呗……没办法,先睡觉吧,忙过这几天再改进吧……)
  I love CS. Good night, world~

/*==========================================
2016.03.19 缺陷修复
==========================================*/

缺陷修复

  为了修复之前的passphrase为明文输入的缺陷,特意查了一下linux脚本读取密码的问题,其实还是挺简单的。

echo & read

  linux脚本输入密码,需要用到echoread两条指令,先举一个例子:

#!/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 <Folder>

# for i in `ls`
# gpg --passphrase <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 <Folder>

# for i in `ls`
# gpg --passphrase <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>

  只需要输入目录就好了。