微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

c – Endianness和C API:特别是OpenSSL

我有一个使用以下OpenSSL调用的算法:

HMAC_update() / HMAC_final() // ripe160
EVP_CipherUpdate() / EVP_CipherFinal() // cbc_blowfish

这些算法将unsigned char *转换为“纯文本”.我的输入数据来自C std :: string :: c_str(),它源自protocol buffer对象,作为编码的UTF-8字符串. UTF-8字符串意味着是endian neutrial.然而,我对OpenSSL如何对数据执行操作有点偏执.

我的理解是加密算法适用于8位数据块,如果在执行操作时使用unsigned char *进行指针运算,算法应该是endian中性的,我不需要担心任何事情.我的不确定性因为我正在使用小端机器并且从未进行任何真正的跨架构编程而更加复杂.

我的信念/推理是基于以下两个属性

> std :: string(不是wstring)在内部使用8位ptr,生成的c_str()ptr将以相同的方式迭代,无论cpu架构如何.
>加密算法可以是设计,也可以是实现,端点中立.

我知道获得明确答案的最佳方法是使用QEMU并进行一些跨平台单元测试(我打算这样做).我的问题是要求对我的推理进行评论,或许在面临类似问题时可以帮助其他程序员.

解决方法

一些加密算法,特别是散列函数(在HMAC中使用)被指定为在任意比特序列上操作.然而,在实际的物理计算机上,并且对于大多数协议,数据是八位字节序列:比特数是8的倍数,并且比特可以由8比特的组处理.一组8位名义上是“八位字节”,但更经常遇到术语“字节”.八位字节的数值介于0和255之间(包括0和255).在一些编程语言(例如Java)中,数值被签名(在-128和127之间),但这是相同的概念.

请注意,在C编程语言的上下文中(如ISO 9899:1999标准中所定义,又称“C标准”),字节被定义为基本可寻址存储器单元,由无符号字符类型表示. sizeof返回以字节为单位的大小(因此,sizeof(unsigned char)必须等于1). malloc()占用大小(以字节为单位).在C中,字节中的位数由CHAR_BIT宏(在< limits.h>中定义)指定,并且大于或等于8.在大多数计算机上,C字节中恰好有8位(即C字节是八位字节,每个人都称之为“字节”).有些系统具有更大的字节(通常是嵌入式DSP)但是如果你有这样的系统,你就会知道它.

因此,对任意比特序列起作用的每个加密算法实际上定义了如何将比特内部解释为八位字节(字节).即使在挑剔的数学家眼中,AESSHA规范仍然可以做到这一点.对于每种实际情况,您的数据已经是一个字节序列,并且假定已经将比特分组为字节;所以你只需将字节输入算法实现,一切都很好.

因此,实际上,加密算法实现期望将字节序列作为输入,并产生字节序列作为输出.

字节顺序(隐含地在字节级别)是关于如何将多字节值(需要几个字节要编码的值)布置成字节序列(即哪个字节首先出现)的约定. UTF-8是字节序中立的,因为它已经定义了这种布局:当一个字符被编码成几个字节时,UTF-8要求这些字节中的哪一个首先出现,哪个出现最后一个字节.这就是UTF-8“端点中立”的原因:将字符转换为字节是一种固定的约定,它不依赖于本地硬件最喜欢读或写字节的方式.字节顺序通常与整数值在内存中的写入方式有关.

关于跨平台编程:经验无可替代.因此,尝试几个平台是一个方法.通过使代码保持干净,即在32位和64位平台上正确运行相同的代码,您将学到很多东西.任何最近使用Linux的PC都符合要求.大端系统现在非常罕见;你需要一台较旧的Mac(一台带有PowerPC处理器),或者需要一种Unix工作站(Sparc系统或HP / UX下的Itanium系统).较新的设计倾向于采用little-endian惯例.

关于C中的字节顺序:如果你的程序必须担心字节顺序,那么很可能你做错了.字节序是指将整数(16位,32位或更多)转换为字节,然后返回.如果您的代码担心字节序,那么这意味着您的代码将数据写为整数并将其作为字节读取,反之亦然.无论哪种方式,你都在做一些“类型别名”:通过几个不同类型的指针访问内存的某些部分.这是不好的.它不仅会降低您的代码的可移植性,而且在要求编译器优化代码时也会出现问题.

在适当的C程序中,只有在要向文件或网络套接字写入或读取值时,才会为I / O处理字节序.该I / O遵循定义要使用的字节序的协议(例如,在TCP / IP中,经常使用大端规则). “正确”的方法是编写一些包装函数

uint32_t decode32le(const void *src)
{
    const unsigned char *buf = src;
    return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8)
        | ((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
}

uint32_t decode32be(const void *src)
{
    const unsigned char *buf = src;
    return (uint32_t)buf[3] | ((uint32_t)buf[2] << 8)
        | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[0] << 24);
}

void encode32le(void *dst,uint32_t val)
{
    unsigned char *buf = dst;
    buf[0] = val;
    buf[1] = val >> 8;
    buf[2] = val >> 16;
    buf[3] = val >> 24;
}

void encode32be(void *dst,uint32_t val)
{
    unsigned char *buf = dst;
    buf[3] = val;
    buf[2] = val >> 8;
    buf[1] = val >> 16;
    buf[0] = val >> 24;
}

可能,使这些函数“静态内联”并将它们放在头文件中,以便编译器可以在调用代码时随意内联它们.

然后,只要您想从新近从文件套接字中获取(或很快写入)的内存缓冲区中写入或读取32位整数,就可以使用这些函数.这将使您的代码以字节序中性(因此是可移植的)更清晰,从而更易于阅读,开发,调试和维护.在极少数情况下,这种编码和解码成为瓶颈(这可能只发生在你使用cpu非常弱且网络连接非常快的平台,即根本不是PC),你仍然可以取代实现某些体系结构特定宏的那些函数,可能具有内联汇编,而无需修改其余代码.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐