http://www.2cto.com/Article/201309/243900.html
近分析rc.sysinit启动脚本,正遇磁盘加密之部分,经一番细研,非三言二意可所得。故,捋整究探,分二段于下:挪步接踵设建为先,启动方法分析佐后。
使用crypto可以对多数类unix系统中的块设备进行底层数据加密,即在文件系统之下加密。对块设备加密完成后,再对该设备进行文件系统的格式化处理。对加密后的设备进行使用,需通过打开设备(这是需要输入密码)、挂载设备两步来实现。加密设备的退卸,反之即可。
若需开机自动挂载(即在/etc/fstab里指明),密码读取的方式可以通过两种方法:手动输入、使用/etc/crypttab指定设备对应的密码文件,具体实现可见第二部分的启动方法分析。接下来首先看如何一步一步实现设备加密。
一、 块设备加密
a) 创建新的块设备(磁盘、分区、块设备的文件等);
b) 使用cryptsetup luksFormat命令把块设备格式化为加密设备,当需要输入yes时记得是大写;
c) 使用cryptsetup luksAddKey给加密设备添加密码文件;
所谓的密钥文件没有任何变化;
d) 使用cryptsetup luksOpen打开设备;
e) 格式化打开的设备;
f) 修改/etc/fstab使其开机自动挂载;
g) 修改/etc/crypttab使其开机时使用密码文件,自动解密;
h) 开机测试(reboot)
二、 存在于/etc/init.d/function文件中,有关于crypto的方法分析
a) 方法init_crypto();
通过文件重定向来使用 read 注意,这里”read”命令将会产生一种不直观的行为. 1) 重新从文件的开头开始读入变量. 2) 每个变量都设置成了以空白分割的字符串, 而不是之前的以整行的内容作为变量的值. 3) 而最后一个变量将会取得第一行剩余的全部部分(不管是否以空白分割). 4) 如果需要赋值的变量的个数比文件中第一行一空白分割的字符串的个数多的话, 那么这些变量将会被赋空值. |
使用read命令读取Linux系统上的文件。 |
局部变量 如果变量用 local 来声明,那么它只能在该变量声明的代码块(block of code)中可见. 这个代码块就是局部”范围”. 在一个函数内,局部变量意味着只能在函数代码块内它才 有意义. |
Expression |
Meaning |
${#string} |
Length of $string |
|
|
${string:position} |
Extract substring from $string at $position |
${string:position:length} |
Extract $length characters substring from $string at $position |
|
|
${string#substring} |
Strip shortest match of $substring from front of $string |
${string##substring} |
Strip longest match of $substring from front of $string |
${string%substring} |
Strip shortest match of $substring from back of $string |
${string%%substring} |
Strip longest match of $substring from back of $string |
|
|
${string/substring/replacement} |
Replace first match of $substring with $replacement |
${string//substring/replacement} |
Replace all matches of $substring with $replacement |
${string/#substring/replacement} |
If $substring matches front end of $string, substitute $replacement for $substring |
${string/%substring/replacement} |
If $substring matches back end of $string, substitute $replacement for $substring |
|
|
expr match ”$string” ’$substring’ |
Length of matching $substring* at beginning of $string |
expr ”$string” : ’$substring’ |
Length of matching $substring* at beginning of $string |
expr index ”$string” $substring |
Numerical position in $string of first character in $substring that matches |
expr substr $string $position $length |
Extract $length characters from $string starting at $position |
expr match ”$string” ’\($substring\)’ |
Extract $substring* at beginning of $string |
expr ”$string” : ’\($substring\)’ |
Extract $substring* at beginning of $string |
expr match ”$string” ’.*\($substring\)’ |
Extract $substring* at end of $string |
expr ”$string” : ’.*\($substring\)’ |
Extract $substring* at end of $string |
图(1)
图(2)
图(3)
图(4)
1. __readlink() { 2. ls -bl ”$@” 2>/dev/null| awk ’{ print $NF }’ 3. } #参见图(5); |
1. key_is_random() { 2. [ "$1" = "/dev/urandom" -o "$1" = "/dev/hw_random" \ 3. -o "$1" = "/dev/random" ] 4. } |
1. find_crypto_mount_point() { 2. local fs_spec fs_file fs_vfstype remaining_fields 3. local fs 4. while read fs_spec fs_file remaining_fields; do #读取/etc/fstab文件中的值,赋值给它们三个; 5. if [ "$fs_spec" = "/dev/mapper/$1" ]; then 6. echo $fs_file 7. break; #当带”/dev/mapper/”加上进来的值,等于fs_file从fstab中取的变量时,则退出; 8. fi 9. done < /etc/fstab 10. } |
1. # Because of a chicken/egg problem, init_crypto must be run twice. /var may be 2. # encrypted but /var/lib/random-seed is needed to initialize swap. 3. init_crypto() { 4. local have_random dst src key opt mode owner params makeswap skip arg opt #声明局部变量; 5. local param value rc ret mke2fs mdir prompt mount_point #声明局部变量; 6. 7. ret=0 #设定返回值默认为真; 8. have_random=$1 #方法的第一个参数值赋给have_random; 9. while read dst src key opt; do #参考141行,把/etc/crypttab文件里面的值,按段分别赋于dst src key opt这四个变量; 10. [ -z "$dst" -o "${dst#\#}" != "$dst" ] && continue #若变量dst的值为空,或者变量dst里面有注释符“#”存在“${dst#\#}表示若dst变量中的第一个字符为#则去除该字符,若dst变量的第一个字符不是#符号,那么${dst#\#}将等于${dst}”,则跳过下面语句,直接进入下次循环(因为变量dst、src、key、opt的值都来自文件/etc/crypttab,所以当文件中不能提供合适的值时,该程序将会进入死循环,直接退出); 11. [ -b "/dev/mapper/$dst" ] && continue; #目标若为块设备,则直接退出(参考上一条语句); 12. if [ "$have_random" = 0 ] && key_is_random ”$key”; then 13. continue 14. fi #若have_random的值等于0(即调用init_crypto函数时的第一个参数),则判断变量key的值是否等于“/dev/urandom、/dev/hw_random、/dev/random”中的任意一个,若为真则直接退出; 15. if [ -n "$key" -a "x$key" != "xnone" ]; then #变量key不为空,以及key的值不为none则执行下面语句(变量前面加x不知为何); 16. if test -e ”$key” ; then #该文件存在则执行下面语句; 17. owner=$(ls -l $key | (read a b owner rest; echo $owner)) #赋值给owner,见图(3); 18. if ! key_is_random ”$key”; then #若key的值不为“/dev/urandom、/dev/hw_random、/dev/random”中的任意一个,则执行下面语句; 19. mode=$(ls -l ”$key” | cut -c 5-10) #截取key值中,文件的所属组及其他用户的权限描述符,并把值赋给mode变量; 20. if [ "$mode" != "------" ]; then 21. echo $”INSECURE MODE FOR $key” 22. fi #若mode的值不等于“——”,则回显“ INSECURE MODE FOR”提示; 23. fi 24. if [ "$owner" != root ]; then 25. echo $”INSECURE OWNER FOR $key” 26. fi #若owner的值不为root,则提示该文件的拥有者不够安全; 27. else 28. echo $”Key file for $dst not found, skipping” 29. ret=1 30. continue #若16行判断不成了(即key变量中的文件不存在)则输出“ Key file for $dst not found, skipping”,并直接退出; 31. fi 32. else 33. key=”" #若15行判断不成立(即变量key的值为空),则给它再赋个空值,该句在这里没多大意义,只是一个动作而已; 34. fi 35. params=”" 36. makeswap=”" 37. mke2fs=”" 38. skip=”" #初始化“ params、 makeswap、 mke2fs、 skip”这四个变量的值为空; 39. # Parse the src field for UUID= and convert to real device names 40. if [ "${src%%=*}" == "UUID" ]; then #通过从尾至首的最长匹配去除符“%%”,去掉src变量中等号后面的所有字符,并与UUID比较是否相等,若为真则执行下面命令,参见图(4); 41. src=$(/sbin/blkid -t ”$src” -l -o device) #只显示设备全路径,如“/dev/sdb5”等; 42. elif [ "${src/^\/dev\/disk\/by-uuid\/}" != "$src" ]; then #不理解,括号中是猜想(若src的值为”/dev/disk/by-uuid/616d4fda-fae4-41eb-8b58-42cbe49d1f0d”,也许脚本编写人员是想通过‘${src/^\/dev\/disk\/by-uuid\/}’使 src中起始部分为‘/dev/disk/by-uuid/’的值替换为空,故而”616d4fda-fae4-41eb-8b58-42cbe49d1f0d”不等于”/dev/disk/by-uuid/616d4fda-fae4-41eb-8b58-42cbe49d1f0d”,所以可以执行接下来的语句了,但在测试当中,发现变量中的^并不能指明所谓的起始位置,反而使得整个语句无法达到预期的目标——即替换不成功); 43. src=$(__readlink $src) #获取设备全路径,并给src重赋值,参见图(5),获取的值可能是“../../sdb5”,在使用中可能有问题,因为不是绝对路径; 44. fi 45. # Is it a block device? 46. [ -b "$src" ] || continue #若不是块设备则直接退出; 47. # Is it already a device mapper slave? (this is gross) 48. devesc=${src##/dev/} #把src值中的“/dev/”替换成空,使devesc变量值为设备名,如sdb5; 49. devesc=${devesc//\//!} #把devesc中的所有“/”替换为“!”; 50. for d in /sys/block/dm-*/slaves ; do 51. [ -e $d/$devesc ] && continue 2 52. done #若该设备存在于dm中,则直接跳出该循环,continue 2表示跳过本次循环后面所有循环; 53. # Parse the options field, convert to cryptsetup parameters and 54. # contruct the command line 55. while [ -n "$opt" ]; do #当opt变量不为空时执行下面操作; 56. arg=${opt%%,*} #从尾至首删除opt中“,”及“,”后面的所有值,并把结果赋给arg(即第一个“,”前的参数); 57. opt=${opt##$arg} #即把第一个“,”及“,”后面的所有值赋给opt; 58. opt=${opt##,} #去掉其中所有的“,”; 59. param=${arg%%=*} #把等号前面的值赋给param; 60. value=${arg##$param=} #把等号后面的值赋给value; 61. 62. case ”$param” in #case语句; 63. cipher) #指定密码的加密算法; 64. params=”$params -c $value” #由于params的初始值为空,当param值等于cipher时,params的值为“-c $value”; 65. if [ -z "$value" ]; then 66. echo $”$dst: no value for cipher option, skipping” 67. skip=”yes” 68. fi #如果value的值为空,则输出以上字符串,并把skip的值设为yes; 69. ;; 70. size) #指定密码长度,一般是128、192或256位; 71. params=”$params -s $value” #简单复制语句; 72. if [ -z "$value" ]; then 73. echo $”$dst: no value for size option, skipping” 74. skip=”yes” #若value为空,则产生提示,并设skip为yes; 75. fi 76. ;; 77. hash) #密码hash算法; 78. params=”$params -h $value” 79. if [ -z "$value" ]; then 80. echo $”$dst: no value for hash option, skipping” 81. skip=”yes” 82. fi #若value为空,则产生提示,并设skip为yes; 83. ;; 84. verify) #当从控制台输入密码时,要求输入两次; 85. params=”$params -y” 86. ;; 87. swap) #指定该分区为交换分区,当指定后,程序会把加密过的swap设备摧毁,并重新生成新的加密设备(swap); 88. makeswap=yes 89. ;; 90. tmp) #该加密后的设备将被tmp使用,并会把权限模式设为01777; 91. mke2fs=yes 92. esac 93. done 94. if [ "$skip" = "yes" ]; then 95. ret=1 96. continue 97. fi #如果skip的值为yes ,那么返回值为1(错误),并退出; 98. if [ -z "$makeswap" ] && cryptsetup isLuks ”$src” 2>/dev/null ; then #如果makeswap变量的值为空,且该驱动器属于加密设备,则执行下面语句; 99. if key_is_random ”$key”; then 100. echo $”$dst: LUKS requires non-random key, skipping” 101. ret=1 102. continue 103. fi #当key的值属于“/dev/urandom、/dev/hw_random、/dev/random”中的任意一个,则直接退出; 104. if [ -n "$params" ]; then 105. echo ”$dst: options are invalid for LUKS partitions,” \ 106. ”ignoring them” 107. fi #当params的值不为空时,提示该分区有问题,将被忽略; 108. if [ -n "$key" ]; then 109. /sbin/cryptsetup -d $key luksOpen ”$src” ”$dst” <&1 2>/dev/null && success || failure #通过-d参数来指明,使用一文件作为密钥参数来解密,并打开设备,名称为“/dev/mapper/$dst”,执行成功则输出[ OK ]字样,失败输出[ FAILED ]字样; 110. rc=$? #命令执行结果; 111. else 112. mount_point=”$(find_crypto_mount_point $dst)” #确认挂载点在fstab中被声明,若被声明,则把值赋给mount_point,否则mount_point值为空; 113. [ -n "$mount_point" ] || mount_point=${src##*/} #当mount_point值为空时,跳过该语句;否则把src变量中,最后一个/后面的值赋给mount_point; 114. prompt=$(printf $”%s is password protected” ”$mount_point”) #通过printf内置工具格式化“挂载点受密码保护”的信息,赋值给prompt; 115. plymouth ask-for-password –prompt ”$prompt” –command=”/sbin/cryptsetup luksOpen -T1 $src $dst” <&1 应该是通过plymouth弹出图形化的密码提示框,要求输入密码; 116. rc=$? #退出值; 117. fi 118. else 119. [ -z "$key" ] && plymouth –hide-splash #若key的值为空,则隐藏plymouth的主题; 120. /sbin/cryptsetup $params ${key:+-d $key} create ”$dst” ”$src” <&1 2>/dev/null && success || failure #用cryptsetup通过驱动器、目标名称创建驱动器映射(中间’${key:+-d $key}’的key设置或没设置,都将加-d参数到原先值前面),执行成功输出ok字样,失败输出failed字样; 121. rc=$? #返回值给rc; 122. [ -z "$key" ] && plymouth –show-splash #若key值为空,则显示主题; 123. fi 124. if [ $rc -ne 0 ]; then #当rc的值不为零时执行下面语句; 125. ret=1 #设置ret为1; 126. continue 127. fi 128. if [ -b "/dev/mapper/$dst" ]; then #映射过来的目标为块设备,则执行下面语句; 129. if [ "$makeswap" = "yes" ]; then #当makeswap的值为yes时,执行里面语句; 130. mkswap ”/dev/mapper/$dst” 2>/dev/null >/dev/null #格式化为交换分区; 131. fi 132. if [ "$mke2fs" = "yes" ]; then #当mke2fs值为yes,执行里面语句; 133. if mke2fs ”/dev/mapper/$dst” 2>/dev/null >/dev/null \ 134. && mdir=$(mktemp -d /tmp/mountXXXXXX); then #格式化目标为ext2文件系统,并创建暂存目录/tmp/mountXXXXXX; 135. mount ”/dev/mapper/$dst” ”$mdir” && chmod 1777 ”$mdir” #挂载设备到暂存目录,并设权限为1777; 136. umount ”$mdir” #卸载暂存目录; 137. rmdir ”$mdir” #删除暂存目录; 138. fi 139. fi 140. fi 141. done < /etc/crypttab #从/etc/crypttab文件读入值做为第9行变量的参数; 142. return $ret #返回值; 143. } |