4

长话短说(真的很长)——我使用 Ted Krovetz 的实现来计算UMACUMAC AE加密(http://www.fastcrypto.org/)。

当我用 编译我的代码(和/或 中的测试umac.c)时-std=c99,计算UMAC的结果与预期的完全不同(并且是错误的)。当我删除此选项时,一切都像魅力一样。

有什么想法可能导致这种情况吗?我可以做些什么来检查发生了什么以及产生不同结果的原因?


$ gcc --version
gcc (Ubuntu 4.8.2-19ubuntu1) 4.8.2

$ uname -a
xxx 3.13.0-43-generic #72-Ubuntu SMP .. x86_64 x86_64 x86_64 GNU/Linux

我不使用任何其他选项 - 仅使用和不使用-std=c99.


再说几句:

我会尝试联系 Ted Krovetz 并询问他(这可能是一些错误或其他东西),但这不是重点。这个问题有点笼统,这个具体问题可以看作是例子。

我跑了valgrind——没什么特别的。添加-Wall-Wextra-什么都没有。听起来像 UB,但valgrind不会抱怨任何事情。

这种情况非常有趣,我花了很多天和头疼的时间才明白,问题不在于我的代码(我使用这个实现来实现一个复杂的协议),而在于算法,尤其是这个选项。所以我决定征求意见。

这个在 C 和 C++ 中都有效的代码在用每种语言编译时会产生不同的行为吗?根本不相关,因为我们在这里谈论的是同一种语言。
没有“-std=c99”的这种巨大的 fprintf 速度差异很接近,但还不够。


编辑

这是我的测试结果和我所做的(源/标题只是下载,我没有更改任何内容):

$ ll
total 176K
-rw-r----- 1 kk kk  63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk  76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h

$ gcc -c *.c

$ gcc *.o

$ ./a.out 
AES Test :::
Digest is       : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34

UMAC Test :::
Msg           Should be        Is
---           ---------        --
'a' *     0 : 4D61E4F5AAB959C8 4D61E4F5AAB959C8
'a' *     3 : 67C1700CA30B532D 67C1700CA30B532D
'a' *  1024 : 05CB9405EC38D9F0 05CB9405EC38D9F0
'a' * 32768 : 048C543CB72443A4 048C543CB72443A4

Verifying consistancy of single- and multiple-call interfaces.
Done.

Authenticating       44 byte messages:  6.45 cpb.
Authenticating       64 byte messages:  4.18 cpb.
Authenticating      256 byte messages:  1.63 cpb.
Authenticating      512 byte messages:  1.20 cpb.
Authenticating      552 byte messages:  1.22 cpb.
Authenticating     1024 byte messages:  1.00 cpb.
Authenticating     1500 byte messages:  1.04 cpb.
Authenticating     8192 byte messages:  0.90 cpb.
Authenticating   262144 byte messages:  0.89 cpb.

UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : E8D1DAC3EA21E56D
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 6BEDBA31E074E2A4
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : A3F6069B913969DA
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : C5B7F3822179FC36
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : EE7F50FDDA60AA04
  16 bytes, 38.12 cpb
  32 bytes, 25.04 cpb
  64 bytes, 19.39 cpb
 128 bytes, 16.41 cpb
 256 bytes, 14.79 cpb
 512 bytes, 13.96 cpb
1024 bytes, 13.79 cpb
2048 bytes, 13.46 cpb
4096 bytes, 13.47 cpb










$ ll
total 176K
-rw-r----- 1 kk kk  63K Jan 20 11:00 rijndael-alg-fst.c
-rw-r----- 1 kk kk 2.0K Jan 20 11:00 rijndael-alg-fst.h
-rw-r----- 1 kk kk 3.4K Jan 20 11:00 umac_ae.h
-rw-r----- 1 kk kk  76K Jan 20 11:00 umac.c
-rw-r----- 1 kk kk 4.2K Jan 20 11:00 umac.h

$ gcc -std=c99 -c *.c 

$ gcc -std=c99 *.o

$ ./a.out 
AES Test :::
Digest is       : 3AD78E726C1EC02B7EBFE92B23D9EC34
Digest should be: 3AD78E726C1EC02B7EBFE92B23D9EC34

UMAC Test :::
Msg           Should be        Is
---           ---------        --
'a' *     0 : 4D61E4F5AAB959C8 9492DE86794C9F2B
'a' *     3 : 67C1700CA30B532D CF9505F52928360E
'a' *  1024 : 05CB9405EC38D9F0 9C48C0D4EFAFAA37
'a' * 32768 : 048C543CB72443A4 7F63C29BB54BB141

Verifying consistancy of single- and multiple-call interfaces.
Done.

Authenticating       44 byte messages:  7.91 cpb.
Authenticating       64 byte messages:  5.20 cpb.
Authenticating      256 byte messages:  3.03 cpb.
Authenticating      512 byte messages:  2.60 cpb.
Authenticating      552 byte messages:  2.71 cpb.
Authenticating     1024 byte messages:  2.41 cpb.
Authenticating     1500 byte messages:  2.43 cpb.
Authenticating     8192 byte messages:  2.27 cpb.
Authenticating   262144 byte messages:  2.23 cpb.

UMAC-AE Tests :::
0 bytes ('abc' * 0):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 899C50FD244BBA83
3 bytes ('abc' * 1):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 892D14F581A3A4DD
48 bytes ('abc' * 16):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 621AB4A63383F3C5
300 bytes ('abc' * 100):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 324BEF6489F57787
3000000 bytes ('abc' * 1000000):
Encrypt/decrypt match, tags match
Should be: 0000000000000000
Is       : 1A25FE3714C9345A
  16 bytes, 40.80 cpb
  32 bytes, 25.87 cpb
  64 bytes, 20.50 cpb
 128 bytes, 17.72 cpb
 256 bytes, 15.93 cpb
 512 bytes, 15.33 cpb
1024 bytes, 14.88 cpb
2048 bytes, 14.71 cpb
4096 bytes, 14.48 cpb

我刚刚在另一台机器上测试过,和我的一样。

4

3 回答 3

5

嗯,我想通了。仍然不确定如何修复它以使其完美/便携,但我会继续挖掘。

长话短说 - 它似乎是特定于平台的,这就是为什么你们大多数人都没有这个问题。
问题在于确定字节顺序。


细节:

在比较了汇编输出之后,有一些显着的差异,这(几乎自动地)排除了一些具有大常数解释的问题和像这样的小问题。

然后我尝试了更高级别 - 预处理器输出。

最后,一切都导致了这段代码umac.c

/* Message "words" are read from memory in an endian-specific manner.     */
/* For this implementation to behave correctly, __LITTLE_ENDIAN__ must    */
/* be set true if the host computer is little-endian.                     */

#ifndef __LITTLE_ENDIAN__
#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif
#endif

在我的平台上__i386____alpha___M_IX86没有定义。关键在__LITTLE_ENDIAN.

编译时:

  • with -std=c99:未定义__LITTLE_ENDIAN= > 。 #define __LITTLE_ENDIAN__ 0
  • 没有-std=c99:__LITTLE_ENDIAN 定义 => #define __LITTLE_ENDIAN__ 1

硬编码,无论有无#define __LITTLE_ENDIAN__ 1,一切都开始完美运行-std=c99


结论:__LITTLE_ENDIAN是一个gcc特有的宏,这里用来判断字节序;看来,这-std=c99会影响这个宏(如果使用该选项,则未定义),这会导致不同(错误)的结果。


编辑
我当前的(“临时”)解决方案是更新有问题的预处理器 if 语句。我知道这远非解决这个问题的最佳方法,但检测字节序似乎并不容易,而且远非微不足道。

运行时检查似乎更可靠,但这会导致代码发生更多变化,这是我想避免的。看起来,最“无害”的“解决方案”是更新和“修复”当前的解决方案。

所以,因为我只需要它(现在)与 GCC 一起工作,我做了以下修改:

#ifndef __LITTLE_ENDIAN__
    #if __GNUC__
        #include <endian.h>
        #if __BYTE_ORDER == __LITTLE_ENDIAN
            #define __LITTLE_ENDIAN__ 1
        #elif __BYTE_ORDER == __BIG_ENDIAN
            #define __LITTLE_ENDIAN__ 0
        #else
            #error "Cannot determine endianness! Please update this macro!"
        #endif
    #elif __i386__ || __alpha__ || _M_IX86
        #define __LITTLE_ENDIAN__ 1
    #else
        #warning "Endianness cannot be determined for this platform; using big endian by default! Please be aware and update this macro!"
        #define __LITTLE_ENDIAN__ 0
    #endif
#endif
于 2015-01-20T11:32:30.823 回答
4

听起来像UB,

它不一定是。有几个已知的差异会导致同一程序被不同地解释为 C99 和 C90。

但 valgrind 不会抱怨任何事情。

Valgrind 甚至没有对任何一个标准中的所有未定义行为发出警告。

想到的第一个区别(但一个好的编译器会发出警告)是整数常量的类型3000000000。对于 32 位int和 64 位long,C90 编译器类型3000000000unsigned long. 在 C99 中,unsigned long不在不带后缀的整数常量可以具有的类型列表中,因此3000000000键入为long long(signed)。

不用看,密码代码可能有很多大整数常量,所以这是一种可能性。

当然,在解释为 C90 或 C99 的代码中可能存在未定义的行为,然后编译器在 C90 和 C99 模式下产生不同的结果是可以原谅的。我只是说不需要。

于 2015-01-19T17:41:38.777 回答
2

这个答案建立在 Kiril Kirov 的现有答案之上

Kiril 发现了预处理器检查系统无法识别现有平台的问题:

#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#else
#define __LITTLE_ENDIAN__ 0
#endif

尽管平台是 little-endian,但这些标识符都没有在gcc -std=c99mode 中定义。所以这是这里引用的代码的问题;代码需要更新以更好地识别它是否是小端平台。

我要做的第一件事是停止使用默认情况,这样如果平台无法识别,则会生成错误,而不是静默错误地运行:

#if __i386__ || __alpha__ || _M_IX86 || __LITTLE_ENDIAN
#define __LITTLE_ENDIAN__ 1
#elif __arm__  // etc.
#define __LITTLE_ENDIAN__ 0
#else
#error Unrecognized platform, please update this macro
#endif

下一步将是实际正确检测您所在的系统。这是关于这个主题的另一个线程。

您可以做的一件事是发出gcc -std=c99 -dM -E - <<<''将导致gcc在该模式下输出所有预定义宏的列表;然后你可以寻找有用的东西。就我而言,它具有:

#define __i386 1
#define __i686 1

所以其中任何一个都可以使用。

另一种专门用于__LITTLE_ENDIAN__检测它的方法是通过预处理器算术来检测它,描述here - 尽管该页面上的第一个代码示例实际上并没有产生预处理常量,因此它不能用作未来预处理器检查的条件。


解决了这个特定问题后,您仍然应该尝试在同一代码库中找到其他架构问题实例。看起来作者拼凑了一些他/她知道的架构宏。例如,一个明显的缺席是_M_IX64。明智的做法是在代码库中搜索_M_or的任何其他实例,__i386__看看它是否依赖于这些实例。如果是; 然后再一次尝试将该测试抽象为您可以更好地控制的宏。

理想情况下,您会将所有此类宏定义在整个代码库的单个标头中;然后代码库的其余部分只使用已在该标头中定义的宏。

于 2015-01-20T12:32:31.077 回答