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) 加密算法的,但是内核中的调用实在太复杂,层次又多,看了一上午,无语了  

后来转念一想,干脆试试 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[4] = { 0, };

iv[0]= 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)
{

/*rep xcryptcbc */

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 [lindex $argv 0]

spawn losetup -e aes /dev/loop0/tmp/cryptoloop.image

expect {

Password: {

send "$password\r"

exp_continue

}
}

2.
mountwrapper
#!/usr/local/bin/expect 
set password [lindex $argv 0]

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 <stdlib.h>
#include <stdio.h>

#include <openssl/rand.h>
#include <openssl/sha.h>
#include <openssl/aes.h>

int decrypt( const char * password, constchar * srcpath, const char * dstpath )
{

charkeybuf[32];

AES_KEY key;

unsigned char blank[512];

unsigned char endata[512];

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(srcfilelen%512 != 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[4];

memset( iv, '\0', 16 );

iv[0] = 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[3], argv[1], argv[2] );

return0;
}

你可能感兴趣的:(加密,crytoloop)