linux加密api中的scatterlist

时间:2017-01-23 09:09:17

标签: linux linux-kernel cryptography

我开始学习如何在linux中使用Crypto API。它提出使用分散列表结构将明文转换为分组密码功能。通过在memmory页面上存储明文的位置来对明文进行Scatterlist处理。 struct scatterlist的简单定义是:

struct scatterlist {
      unsigned long   page_link;      //number of virtual page in kernel space where data buffer is stored
      unsigned int    offset;         //offset from page start address to data buffer start address
      unsigned int    length;         //data buffer length
      dma_addr_t      dma_address;    //i don't know the purpose of this variable at the moment
};

要获得处理明文缓冲区的scatterlist变量,我们使用下一个函数:void sg_init_one(struct scatterlist *, const void *, unsigned int);。要从scatterlist变量获取缓冲区起始地址,我们使用下一个函数:void *sg_virt(struct scatterlist *sg)。 例如:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>

u8 plaintext_global[16]={0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};

static int __init simple_init (void){

u8 *ptr_to_local, *ptr_to_global;
u8 palintext_local[16]={0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
struct scatterlist sg[2];
sg_init_one(&sg[0], plaintext_local, 16);
sg_init_one(&sg[1], plaintext_global, 16);
printk("sg[0].page_link=%u\n", sg[0].page_link);
printk("sg[0].offset=%u\n", sg[0].offset);
printk("sg[0].length=%u\n", sg[0].length);
printk("sg[1].page_link=%u\n", sg[1].page_link);
printk("sg[1].offset=%u\n", sg[1].offset);
printk("sg[1].length=%u\n", sg[1].length);
ptr_to_local=sg_virt(&sg[0]);
ptr_to_global=sg_virt(&sg[1]);
printk("plaintext_local start address:%p\n", plaintext_local);
printk("sg_virt(&sg[0]):%p\n", ptr_to_local);
printk("plaintext_global start address:%p\n", plaintext_global);
printk("sg_virt(&sg[1]):%p\n", ptr_to_global);
}

在insmod这个模块之后输出dmesg:

sg[0].page_link=31209922
sg[0].offset=3168
sg[0].length=16
sg[1].page_link=16853378
sg[1].offset=0
sg[1].length=16
plaintext_local start address:ffff8800770e7c60
sg_virt(&sg[0]):ffff8800770e7c60
plaintext_global start address:ffffffffc04a6000
sg_virt(&sg[1]):ffff8800404a6000

第一个问题是为什么本地明文缓冲区sg_virt返回与本地缓冲区地址相同的值,但是使用全局明文缓冲区返回值sg_virt还有另一个前缀而不是全局缓冲区地址?

下一步。现在我使用crypto api:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/crypto.h>
#include <linux/scatterlist.h>
u8 aes_in[]={0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff};
u8 aes_key[]={0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f};
u8 aes_out[]={0x69, 0xc4, 0xe0, 0xd8, 0x6a, 0x7b, 0x04, 0x30, 0xd8, 0xcd, 0xb7, 0x80, 0x70, 0xb4, 0xc5, 0x5a};
static int __init simple_init (void){

struct crypto_blkcipher *blk;
struct blkcipher_desc desc;
struct scatterlist sg[3];
u8 encrypted[100];
u8 decrypted[100];
blk=crypto_alloc_blkcipher("ecb(aes)",0,0);
crypto_blkcipher_setkey(blk, aes_key, 16);
sg_init_one(&sg[0], aes_in, 16);
sg_init_one(&sg[1], encrypted, 16);
sg_init_one(&sg[2], decrypted, 16);
desc.tfm=blk;
desc.flags=0;
sg_copy_from_buffer(&sg[0],1,aes_128_in, 16);
crypto_blkcipher_encrypt(&desc, &sg[1], &sg[0], 16);
crypto_blkcipher_decrypt(&desc, &sg[2], &sg[1], 16);
crypto_free_blkcipher(blk);
}

加密数据:69 c4 e0 d8 6a 7b 04 30 d8 cd b7 80 70 b4 c5 5a

解密数据:00 11 22 33 44 55 66 77 88 99 aa bb cc dd ef ff

接下来的问题,sg_copy_from_buffer函数的详细内容是什么?没有这个功能加密数据不对:

没有sg_copy_from_buffer的加密数据:03 07 23 fc 20 11 42 c6 60 b3 36 07 eb c8 c9 62

没有sg_copy_from_buffer的加密数据:00 00 00 00 00 00 00 00 58 51 02 a0 f7 7f 00 00

1 个答案:

答案 0 :(得分:0)

对于第一个问题,散点列表在内部将您为其分配的缓冲区保存为struct page(“页面链接实际上是指向结构页面的指针”),您可以将其视为物理地址(不完全是,但是struct page确实代表唯一的物理页面)。

这意味着分散列表将首先通过sg_init_one将缓冲区的虚拟地址转换为相应的物理地址,最后调用宏函数___pa要做到这一点。调用sg_virt时,它将通过另一个宏___va将分散在分散列表中的物理地址转换回虚拟地址

实际上,___pa用于在线性映射地址范围内核图像地址范围中转换虚拟地址 >到其相应的物理地址___va用于在线性映射地址范围中将物理地址转换为其相应的虚拟地址。当转换地址超出上述地址范围时,它们可能会给出错误输出。

但是,您提供给分散列表的全局缓冲区位于内核模块地址范围之内,该范围位于内核映像地址范围的之后,而本地缓冲区位于< strong>内核堆栈地址范围,该范围之前内核映像地址范围。它们都不在,不在内核线性映射地址范围内,因此在通过“ ___pa”和“ ___va”转换后,它们可能错误

根据您的测试,本地缓冲区地址正确,但全局缓冲区地址错误,这是因为本地缓冲区地址在内核映像地址范围之前,而全局缓冲区地址在内核映像地址范围之后,所以它们在“ __pa”中以不同的方式转换,但在“ ___va”中以相同的方式转换。您可以从Linux内核源代码/arch/x86/include/asm/page.h中的以下代码片段中看到它,并且 /arch/x86/include/asm/page_64.h

// This function is the implementation of ___pa on x86-64
static inline unsigned long __phys_addr_nodebug(unsigned long x)
{
    // __START_KERNEL_map is the start address of the kernel image address range.
    unsigned long y = x - __START_KERNEL_map;

    // You can see that this function behaves differently depending on x and __START_KERNEL_map
    /* use the carry flag to determine if x was < __START_KERNEL_map */
    // phys_base is the start of system's physical address
    // PAGE_OFFSET is the start of linear mapping address range
    x = y + ((x > y) ? phys_base : (__START_KERNEL_map - PAGE_OFFSET));

    return x;
}
// This is the implementation of ___va on x86-64
#define __va(x)         ((void *)((unsigned long)(x)+PAGE_OFFSET))

对于内核映像地址范围之前的虚拟地址,___va仅添加一个偏移量,而___pa仅减去相同的偏移量,因此本地缓冲区地址正确。但是,对于内核映像地址之后的虚拟地址,___va执行相同的工作,但是___pa的行为不同,因此全局缓冲区地址是错误的。

有关x86-64 linux内核内存布局,请参考Documentation / x86 / x86_64 / mm.rst上的linux源代码

请注意,只有kmalloc分配的缓冲区将保留在内核线性映射地址范围内,因此您应始终使用kmalloc为linux内核加密操作分配内存。

对于第二个问题,散列表可以由单个struct scatterlist组成。但是,它实际上是为管理内存块列表而设计的,每个块都由struct scatterlist表示。使用sg_copy_from_buffer,您可以将连续缓冲区中存储的数据复制到由多个struct scatterlist管理的内存块列表中。简而言之,sg_copy_from_buffer与加密无关。

有关更多详细信息,请参考以下内核源代码文件。

/include/linux/scatterlist.h 
/lib/scatterlist.c
/arch/x86/include/asm/page.h
/arch/x86/include/asm/page_64.h