问题描述
这是 BCrypt 文件加密实用程序源代码的一部分。不变,除了我添加的一些评论。
uLong BFEncrypt(char **input,char *key,uLong sz,Bcoptions *options) {
uInt32 L,R;
uLong i;
BLOWFISH_CTX ctx;
int j;
unsigned char *myEndian = NULL;
j = sizeof(uInt32);
getEndian(&myEndian);
// makes space 2 bytes
memmove(*input+2,*input,sz);
// add endian and compresssion option
memcpy(*input,myEndian,1);
memcpy(*input+1,&options->compression,1);
sz += 2; /* add room for endian and compress flags */ // total size increased
Blowfish_Init (&ctx,key,MAXKEYBYTES); // initialize
// encrypt the file
for (i = 2; i < sz; i+=(j*2)) { /* start just after tags */
memcpy(&L,*input+i,j);// copy j bytes from input to L
memcpy(&R,*input+i+j,j); // copy second j byte to R
Blowfish_Encrypt(&ctx,&L,&R); // encrypt
memcpy(*input+i,j); // copy everything back
memcpy(*input+i+j,&R,j);
}
if (options->compression == 1) {
if ((*input = realloc(*input,sz + j + 1)) == NULL)
memerror();
memset(*input+sz,j + 1);
memcpy(*input+sz,&options->origsize,j);
sz += j; /* make room for the original size */
}
free(myEndian);
return(sz);
}
在循环中,我们首先将文件缓冲区逐字节复制到新变量,然后应用河豚加密。然后再次将字节复制到缓冲区。为什么我不能直接将字节传递给加密函数?为什么甚至需要 memcpy()
?
解决方法
为什么我不能直接将字节传递给加密函数?
有两条规则反对它,或者至少不支持它。
第一个是,如果 char
的对齐不正确,则将指向 int
的指针转换为指向 int
的指针具有未定义的行为,并且即使对齐是正确,结果的值没有完全定义。相关规则在 C 2018 6.3.2.3 中,涵盖了指针转换。
通常,int
之类的对象需要位于四个字节的倍数处。这是因为计算机内存和数据总线的组织方式;所涉及的各种“电线”被设置为以特定大小和排列的组传输事物。当此类系统的编译器生成处理 int
对象的指令时,它会生成加载对齐字的指令。如果将一个未对齐的 char
指针转换为指向 int
的指针,当加载对齐字指令尝试使用未对齐的地址时,某些处理器将生成陷阱。其他处理器可能会忽略地址的低位并从不同的地址加载对齐的字。
即使地址对齐,C 标准也不能保证将 char *
转换为 int *
的结果实际上指向与原始相同的位置。这是因为在一些系统中,现在大多是过时的,指向不同类型的指针以不同的方式表示。一些系统只以多个字节的字访问内存,因此,为了实现char *
,编译器必须合成不同于硬件地址的地址,而对于int *
,编译器可能直接使用硬件地址.
第二条规则是指定用于一种类型的内存,例如 char
的数组,不能随意用作另一种类型,例如 int
。此规则在 C 2018 6.5 7. 它有特定的情况是允许的,例如任何类型,例如 int
或 float
,都可以作为 char
访问,但不是副-相反。此规则的目的是使通过 int *i
和 float *f
的例程可以在如下代码中知道这一点:
for (int j = 0; j < 1024; ++j)
f[j] += *i;
f[j]
总是访问一个 float
而从不访问一个 int
,所以这个循环的主体永远不会改变 *i
的值。这意味着编译器可以优化代码:
int t = *i;
for (int j = 0; j < 1024; ++j)
f[j] += t;
节省了从内存中重复加载 *i
的工作,因为临时对象 t
可以保存在处理器寄存器中。 (最重要的是,编译器实际上可以使用 float t = *i;
,节省从内存中重复加载 *i
和重复转换为 float
以进行添加。)
您可以查看该动机,然后查看 Blowfish_Encrypt
并看到 BlowFish_Encrypt
永远不会从这种潜在的优化中受益,这可能是因为它永远不会对受此规则影响的混合类型进行操作。然而,随着编译器在转换中变得越来越先进和激进,编译器优化的复杂性变得越来越难以看到,因此很容易错过编译器从将一种类型别名为另一种类型的规则中获得的一些优势。在任何情况下,由于规则存在,您不能保证如果您违反它,您的程序会运行。