[原创] Linux下的cryptoloop的使用方法和算法分析
(2012-04-10 04:50:38)
标签:
算法
原创
linux
杂谈
[原创]
Linux下的cryptoloop的使用方法和算法分析最近的项目有一个如下的需求:需要在linux下加密优盘,在windows下要能读取优盘中的加密数据。但有个条件:必须对整个磁盘加密,不能只对文件加密(于是复杂度大大增加)。咋办呢?
最后用下面的方法实现了这个需求:对于linux下的优盘加密问题,直接用losetup生成一个加密的loop镜像文件,然后用fat32格式化这个镜像文件,再将整个镜像写入优盘。对于windows上的数据读取,最关键的问题是怎么解密镜像文件。(已解决)
其实网上类似的应用很多,例子也不少,但是有两个缺陷:
1.
封装的不好,使用起来有点烦。
2.
没有对解密算法的分析,像我这个项目需要知道解密loop镜像的算法就比较郁闷。
所以这篇小文就把网上流传的代码做了些补充,做了封装让它更好用,还有就是对加密算法做了分析,最后附上了相关代码,以后别人有类似的应用也可以方便些。
(一)
cryptoloop使用方法:
网上类似的代码太多了,这里不再螯述,我根据网上搜集的资料写了几个脚本,能很容易的实现对加密优盘的读写,使用方法见下文,源代码在后面的附录中。注意,文件镜像用的是fat32格式。
使用方法:
1.
数据复制到优盘:
用法:./data2imagesize(M) password filelist device
Size:是优盘的大小
Password:是优盘加密密码
Device:就是优盘设备名
filelist:一个文本文件,保存了要复制的文件或者目录列表。
例子:./ data2image100 mypassword /home/user001/filelist
/dev/sdc1
Filelist的例子:
/home/user001/mytest.txt
/home/user001/mytestdir
2.
优盘中的数据复制到指定文件夹:
用法:./image2datasize(M) password device destination
Size:是优盘的大小
Password:是镜像密码
Device:就是优盘设备名
Destination:目标文件夹
例子:./image2data100 mypassword /dev/sdc1 /tmp/data
使用前要先配置下环境,我的开发环境:RHEL5企业版,expect5.43,tcl8.5.4,tk8.5.4
(expect等三个开发包是为了密码输入方便)。如果用的是RHEL5企业版的话只要装expect,tcl和tk就行了,否则的话可能还要编译内核让它支持cryptoloop,另外可能还要还要编译安装util-linux-2.12r(该工具包中有losetup)。
(二)
解密算法的代码:
(这是全文最重要的部分但却是最简单的,呵呵 分析了半天发现加密算法竟这么简单,我也有点晕)
对于fat32来说,cryptoloop是按照扇区加密的,也就是每512字节作为一个块加密,并且以扇区号作为ivec参数,aes
key则是直接由用户密码生成。解密算法比较简单,我写了段代码来说明linux的解密算法,这样比较直观。具体代码参见附录。
注意:
1.如果在losetup中指定算法的时候指定了aes作为算法(而没有明确指定是cbc还是ecb),则默认使用cbc算法。
2.Aes-cbc全称是Advanced Encryption Standard (AES) Cipher Algorithm in
Cipher BlockChaining (CBC) Mode。该算法中还需要指定iv参数,可将其视为salt。
3.该算法其他具体信息参见rfc3602
4.代码中用了openssl库,所以要想运行代码必须先安装openssl。
(三)算法的分析过程:(没兴趣的请跳过这节:)
其实我觉得算法的分析过程才是最重要的,比得出的结论还要重要。我把这个过程写下来,以后遇到类似问题可以参考,也方便其他人查找cryptoloop解密算法。
分析解密算法前首先要查看加密的镜像文件的内容,说不定能找到一些重要的线索呢。于是创建了两个相同大小相同密码的loop镜像文件(为什么要密码相同呢?这是为了检查是否对不同密码使用了salt。另外,为了便于分析,创建了两个1M的镜像),然后比较这两个文件,发现除了前512字节不同外,其他加密数据一模一样,所以得出结论:
1.
有可能是以512字节的扇区作为数据块加密的(猜测1)。(事实上的确如此)
2.
可能aes没有加salt或者所有密码都用相同的salt(猜测2)。(事实上没有加salt)
继续,因为是用的cryptoloop模块,所以有必要找到cryptoloop的代码先。在内核代码中搜索loop找到了cryptoloop的源代码:linux-2.6.26.1\drivers\block\cryptoloop.c。
再继续,因为加密模块是aes,所以在内核源代码目录中搜索aes,找到了一大堆aes加密算法,不知道是哪个。最后确定是linux-2.6.26.1\linux-2.6.26.1\drivers\crypto\padlock-aes.c
为什么呢?因为文件最后的模块别名清楚的写着:MODULE_ALIAS("aes");
所以这就是传说中的aes模块。
但是这个模块里又有cbc_aes_alg和ecb_aes_alg两种加密算法,cryptoloop用的是哪种算法呢?是cbc_aes_alg算法。
为什么呢?请看cryptoloop.c中的cryptoloop_init函数,当中有段代码:
if(!mode_len) {
mode= "cbc";
mode_len= 3;
}
就是在用户没有指定具体算法的时候使用cbc-aes算法(猜测3)。好,具体算法也被确定了。
本来是想好好分析下cbc(aes)加密算法的,但是内核中的调用实在太复杂,层次又多,看了一上午,无语了 囧iptables转发到回环地址的问题
r />
后来转念一想,干脆试试openssl现成的cbc(aes)算法吧,查了一下:openssl的AES_cbc_encrypt函数除了两个重要参数没法确定外,另外几个参数都很容易确定。这两个参数一个是ivec参数,另外一个就是key了。
先确定key参数。我知道key肯定是由losetup创建设备时输入的密码生成的,所以干脆下载了一份losetup代码来分析(顺便说一句,losetup的代码包含在util-linux-2.12r开发包中,可在kernel.org上下载)。呵呵,这个思路是对的,我从用户输入的密码开始跟踪,发现密码传递的路径如下:
密码被复制进了loop_info64的lo_encrypt_key(注意:lo_encrypt_key的长度是32字节)中,然后在内核源代码目录中搜索lo_encrypt_key关键字(用grep搜索),发现cryptoloop.c中的函数cryptoloop_init调用了crypto_blkcipher_setkey并且以lo_encrypt_key为参数,由此确定key是由lo_encrypt_key生成的,换句话说就是直接用用户的密码生成key(猜测4)。(又顺便查看了padlock-aes.c的生成aeskey的函数,也就是aes_set_key,发现果然生成过程中没有用salt,这也正印证了前面分析镜像文件时的猜测2)
另一个参数ivec采用类似的方法从padlock-aes.c的cbc_aes_decrypt一层层倒推,最后发现是由cryptoloop.c中的函数cryptoloop_transfer用扇区号生成的(猜测5),具体过程如下:
cbc_aes_decrypt实际上是调用padlock_xcrypt_cbc函数解密的,而padlock_xcrypt_cbc也有一个iv参数,我根据这个参数一层层倒推(就是根据函数栈的调用顺序),发现cryptoloop_transfer中有如下代码
u32 iv = { 0http://www.faminorson.com, };
iv= cpu_to_le32(IV & 0xffffffff);
//这里的IV就是扇区号
好,iv参数也被确定了。
如此根据以上五个猜测结论写了一个小程序模拟内核加密解密,竟然一次成功!
呵呵,整个分析过程都是建立在推理和猜测的基础上,其实读源代码就是这样,要大胆猜测小心论证!还要有一点狗屎运!
注:
分析源代码的过程中还发现了下面这个函数,看了晕不晕?呵呵,网上查了下原来是在调用硬件版本的解密函数,0xf3,0x0f,0xa7,0xd0就是cpu指令repxcryptcbc,后面的S,D之类的东东是寄存器。
static inline u8 *padlock_xcrypt_cbc(constu8 *input, u8 *output,
void *key,
u8 *iv, void *control_word, u32 count)
{
asmvolatile (".byte
0xf3,0x0f,0xa7,0xd0"
: "+S" (input),
"+D"(output),
"+a" (iv)
: "d"
(control_word),"b" (key),
"c" (count));
returniv;
}
(四)附录:
一共有五个文件,前四个文件是读写优盘的脚本,最后一个文件是解密程序。
1.
linkwrapper
#!/usr/local/bin/expect
set password
spawn losetup -e aes /dev/loop0/tmp/cryptoloop.image
expect {
Password: {
send "$password\r"
exp_continue
}
}
2.
mountwrapper
#!/usr/local/bin/expect
set password
spawn mount -t vfat /tmp/cryptoloop.image/mnt/crypto/
-oencryption=aes
expect {
Password: {
send "$password\r"
exp_continue
}
}
3.
data2image
#!/bin/sh
#The script is used to copy files todevice.
if [ $# != 4 ]
then
echo"Usage: $0 size(M) password filelist
device"
exit1;
fi
DISKSIZE=$1
PASSWORD=$2
FILELIST=$3
DEVICE=$4
echo "checking
environment..."
modprobe aes > /dev/null
2>&1
if [ $? != 0 ]
then
echo"Errors checking aes module"
>&2
exit1
fi
modprobe cryptoloop >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors checking cryptoloop
module" >&2
exit1
fi
echo "initializing
cryptoloopimage..."
umount /mnt/crypto >
/dev/null2>&1
losetup -d /dev/loop0 >
/dev/null2>&1
rm -f /tmp/cryptoloop.image >
/dev/null2>&1
dd if=/dev/zero of=/tmp/cryptoloop.imagebs=1M count=$DISKSIZE
> /dev/null
2>&1
if [ $? != 0 ]
then
echo"Errors initializing cryptoloop
image" >&2
exit1
fi
echo "linking image to
device/dev/loop0..."
./linkwrapper $PASSWORD >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors linking image to device
/dev/loop0"
>&2
exit1
fi
echo "formatting
device/dev/loop0..."
mkfs -t vfat /dev/loop0 >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors formatting device
/dev/loop0"
>&2
exit1
fi
mkdir /mnt/crypto >
/dev/null2>&1
echo "mounting
device/dev/loop0..."
./mountwrapper $PASSWORD >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors mounting device
/dev/loop0"
>&2
exit1
fi
#do something here
cat $FILELIST | \
while read line;do
mkdir -p /mnt/crypto/data$line >
/dev/null2>&1
/bin/cp -dpR $line /mnt/crypto/data$line >/dev/null
2>&1
done
#clear
umount /mnt/crypto >
/dev/null2>&1
losetup -d /dev/loop0 >
/dev/null2>&1
echo "copying files to
$DEVICE..."
dd if=/tmp/cryptoloop.image of=$DEVICEbs=1M count=$DISKSIZE
> /dev/null
2>&1
if [ $? != 0 ]
then
echo"Errors copying files to
$DEVICE" >&2
exit1
fi
#delete the image file
rm -f /tmp/cryptoloop.image
exit 0
4.
image2data
#!/bin/sh
#The script is used to copy files fromdevice.
if [ $# != 4 ]
then
echo"Usage: $0 size(M) password device
destination"
exit1;
fi
DISKSIZE=$1
PASSWORD=$2
DEVICE=$3
DESTINATION=$4
echo "checking
environment..."
modprobe aes > /dev/null
2>&1
if [ $? != 0 ]
then
echo"Errors checking aes module"
>&2
exit1
fi
modprobe cryptoloop >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors checking cryptoloop
module" >&2
exit1
fi
echo "initializing
cryptoloopimage..."
umount /mnt/crypto >
/dev/null2>&1
losetup -d /dev/loop0 >
/dev/null2>&1
rm -f /tmp/cryptoloop.image >
/dev/null2>&1
dd if=$DEVICE of=/tmp/cryptoloop.imagebs=1M count=$DISKSIZE
> /dev/null
2>&1
if [ $? != 0 ]
then
echo"Errors initializing cryptoloop
image" >&2
exit1
fi
echo "linking image to
device/dev/loop0..."
./linkwrapper $PASSWORD >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors linking image to device
/dev/loop0"
>&2
exit1
fi
mkdir /mnt/crypto >
/dev/null2>&1
echo "mounting device
/dev/loop0..."
./mountwrapper $PASSWORD >
/dev/null2>&1
if [ $? != 0 ]
then
echo"Errors mounting device
/dev/loop0"
>&2
exit1
fi
#do something here
echo "copying files from
image..."
cp -dpR /mnt/crypto/data $DESTINATION >/dev/null
2>&1
#clear
umount /mnt/crypto >
/dev/null2>&1
losetup -d /dev/loop0 >
/dev/null2>&1
rm -f /tmp/cryptoloop.image
dd if=/dev/zero of=$DEVICE bs=1Mcount=$DISKSIZE >
/dev/null 2>&1
exit 0
5.
decrypt.c
#include
#include
#include
#include
#include
int decrypt( const char * passwordhttp://www.infooi.com/, constchar
* srcpath, const char * dstpath )
{
charkeybuf;
AES_KEY key;
unsigned char blank;
unsigned char endata;
FILE* psrcf = NULL;
FILE* pdstf = NULL;
longsrcfilelen = 0;
intres = 0;
inti=0;
//因为生成密钥的userkey是256位所以不能超过32个字符
if (strlen( password ) > 32 )
return 1;
memset( keybuf, '\0', 32 );
strncpy( keybuf, password, min( 32, strlen(password ) ) );
memset( blank, '\0', 512 );
memset(endata, '\0', 512 );
psrcf = fopen( srcpath, "rb"
);
pdstf = fopen( dstpath, "wb"
);
fseek( psrcf, 0, SEEK_END );
srcfilelen = ftell( psrcf );
if(srcfilelenQ2 != 0 ) {
res= 1;
goto end;
}
//使用aes256算法
AES_set_decrypt_key( (const unsigned char*)keybuf, 256,
&key );
fseek( psrcf, 0, SEEK_SET );
for(i=0; ; ++i ) {
//假设镜像文件的长度必须是512的整数倍,如果是不是的话就算出错
intcount = fread( endata, 1, 512, psrcf );
if(count != 512 || feof( psrcf ) ) {
res = 1;
goto end;
}
//如果读到的加密扇区全部为0的话,说明这个扇区没有加密
if(memcmp(endata,blank,512)!=0 ) {
long iv;
memset( iv, '\0', 16 );
iv = i;//扇区号被用作iv
//解密
AES_cbc_encrypt((const unsigned
char*)endata,endata,512,&key,(unsigned char
*)iv,AES_DECRYPT);
}
fwrite(endata,1,512,pdstf);
}
end:
fclose( psrcf );
fclose( pdstf );
return0;
}
int main(int argc, char* argv[])
{
decrypt ( argv, argv, argv );
return0;
}
[ 本帖最后由 tassard 于 2008-10-31 16:16 编辑
]不错支持原创,看的一个累啊,整理一下搞成论文发表算了:mrgreen:赞一个。支持原创:mrgreen:
分享:
喜欢
0
赠金笔
加载中,请稍候......
评论加载中,请稍候...
发评论
登录名: 密码: 找回密码 注册记住登录状态
昵 称:
评论并转载此博文
发评论
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。