AES/CBC加密和解密之间的速度差异是什么?

4

我想知道在以下情况下,理论上AES/CBC解密相对于AES/CBC加密会慢多少:

  • 加密密钥为32字节(256位);
  • 块大小为16字节(128位)。

我之所以问这个问题是因为我想知道我的实现的解密速度是否异常缓慢。我对不同大小的随机内存块进行了一些测试。结果如下:

64B:

enter image description here

64KB:

enter image description here

10MB – 520MB:

enter image description here

所有数据都存储在系统的内部内存中。应用程序自动生成要加密的数据。测试PC上禁用虚拟内存,以便没有任何I/O调用。

当分析表格时,加密和解密之间的差异是否意味着我的实现速度异常缓慢?我做错了什么吗?

更新:

  • 此测试在另一台计算机上执行;
  • 此测试是使用随机数据执行的;
  • 使用Crypto++进行AES/CBC加密和解密。

解密实现如下:

CryptoPP::AES::Decryption aesDecryption(aesKey, ENCRYPTION_KEY_SIZE_AES);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, aesIv);

CryptoPP::ArraySink * decSink = new CryptoPP::ArraySink(data, dataSizeMax);
CryptoPP::StreamTransformationFilter stfDecryptor(cbcDecryption, decSink);
stfDecryptor.Put(reinterpret_cast<const unsigned char*>(ciphertext), cipherSize);
stfDecryptor.MessageEnd();

*dataOutputSize = decSink->TotalPutLength(); 

更新2:

  • 添加了64字节块的结果

1
我觉得看起来异常缓慢。 - President James K. Polk
1
使用本地实现时,它们应该具有非常类似的性能。通过优化实现,解密甚至可能更快,因为它可以并行处理。 - CodesInChaos
2
不要使用空的内存块。并且运行你的代码两次。第一次可能会遇到预热问题。你的库可能对解密速度较慢,也有可能你的基准测试有问题。 - CodesInChaos
@CodesInChaos 我进行了一项新测试,并添加了解密代码。 - Dagob
1
看起来你正在内存缓冲区中传递大块数据。如果你每次输入64KB的块,是否会看到相同的性能差异?你确定测量中没有包括I/O速度吗? - Maarten Bodewes
显示剩余3条评论
3个回答

2

2
加密/解密在像CBC这样的模式下并不一定是相似的。例如,我期望位切片AES实现在解密CBC时比加密要快得多,因为它需要同时处理多个块。 - CodesInChaos
这个回答如何回答问题? - Maarten Bodewes
1
它无法适应于评论中,我正在尽我所能提供帮助。如果你觉得它不完美,需要将其投票降低并指责它,那就随便吧,我不是为了得票而来,我只是为了学习和分享。 - stackuser
3
我不会因为我不喜欢这个信息或想要惹恼你而投反对票,而是因为它没有回答问题。如果人们寻找答案,找到这些信息并没有什么用。换句话说,互联网上有很多信息,找到正确的信息才是关键。这个问题特别涉及加密与解密速度的比较,而这个信息在回答中根本不存在。 - Maarten Bodewes
1
@stackuser - 关于“有优化的方法...”- Crypto++可能不会这样做。我们已经在尝试避免侧信道攻击的细线上行走,我认为引入四个表泄露信息并不是一个好主意。我们应该实现位切片实现,但投资回报率不高。大多数主要平台都具有优于位切片的硬件加速功能。硬件加速对抗侧信道攻击。我认为最好将资源用于新算法; 或者特别是ARM A-32 Rijndael。 - jww

1
当分析表格时,加密和解密之间的差异是否意味着我的实现异常缓慢?我做错了什么吗?三到四件事情引起了我的注意。我有点同意@JamesKPolk的看法-数字看起来不对。首先,加密库通常使用CTR模式而不是CBC模式进行基准测试。还可以参见SUPERCOP benchmarks。您必须使用每字节周期(cpb)来规范化跨机器的测量单位。没有上下文地说“9 MB / s”毫无意义。
其次,我们需要了解机器及其CPU频率。看起来您正在进行9 MB/s的加密和6.5 MB/s的解密数据推送。像2.7 GHz运行的Core-i5这样的现代iCore机器将以大约2.5或3.0 cpb的速度推动CBC模式数据,大约为980 MB/s或1 GB/s。即使是我旧的2.0 GHz运行的Core2 Duo也比您展示的速度更快。Core 2以14.5 cpb或130 MB/s的速度移动数据。
第三,废弃此代码。它有很大的改进空间,因此在此上下文中不值得批评;建议使用的代码如下。值得一提的是,您正在创建许多对象,例如ArraySourceStreamTransformationFilter。该过滤器添加填充会干扰AES加密和解密基准测试并扭曲结果。您只需要一个加密对象,然后只需调用ProcessBlockProcessString
CryptoPP::AES::Decryption aesDecryption(aesKey, ENCRYPTION_KEY_SIZE_AES);
CryptoPP::CBC_Mode_ExternalCipher::Decryption cbcDecryption(aesDecryption, aesIv);
...

第四,Crypto++维基有一篇基准测试文章,其中包含以下代码。这是一个新的部分,在你提出问题时还没有。以下是如何运行您的测试。

AutoSeededRandomPool prng;
SecByteBlock key(16);
prng.GenerateBlock(key, key.size());

CTR_Mode<AES>::Encryption cipher;
cipher.SetKeyWithIV(key, key.size(), key);

const int BUF_SIZE = RoundUpToMultipleOf(2048U,
    dynamic_cast<StreamTransformation&>(cipher).OptimalBlockSize());

AlignedSecByteBlock buf(BUF_SIZE);
prng.GenerateBlock(buf, buf.size());

const double runTimeInSeconds = 3.0;
const double cpuFreq = 2.7 * 1000 * 1000 * 1000;
double elapsedTimeInSeconds;
unsigned long i=0, blocks=1;

ThreadUserTimer timer;
timer.StartTimer();

do
{
    blocks *= 2;
    for (; i<blocks; i++)
        cipher.ProcessString(buf, BUF_SIZE);
    elapsedTimeInSeconds = timer.ElapsedTimeAsDouble();
}
while (elapsedTimeInSeconds < runTimeInSeconds);

const double bytes = static_cast<double>(BUF_SIZE) * blocks;
const double ghz = cpuFreq / 1000 / 1000 / 1000;
const double mbs = bytes / 1024 / 1024 / elapsedTimeInSeconds;
const double cpb = elapsedTimeInSeconds * cpuFreq / bytes;

std::cout << cipher.AlgorithmName() << " benchmarks..." << std::endl;
std::cout << "  " << ghz << " GHz cpu frequency"  << std::endl;
std::cout << "  " << cpb << " cycles per byte (cpb)" << std::endl;
std::cout << "  " << mbs << " MiB per second (MiB)" << std::endl;

在一台主频为2.7 GHz的Core-i5 6400上运行代码会得到以下结果:
$ ./bench.exe
AES/CTR benchmarks...
  2.7 GHz cpu frequency
  0.58228 cycles per byte (cpb)
  4422.13 MiB per second (MiB)

第五步,当我修改上面展示的基准程序以操作64字节块时:
const int BUF_SIZE = 64;
unsigned int blocks = 0;
...

do
{
    blocks++;
    cipher.ProcessString(buf, BUF_SIZE);
    elapsedTimeInSeconds = timer.ElapsedTimeAsDouble();
}
while (elapsedTimeInSeconds < runTimeInSeconds);

我看到Core-i5 6400在2.7 GHz时,64字节块的速度为3.4 cpb或760 MB/s。对于小缓冲区,该库会受到影响,但大多数(全部?)库都会。

$ ./bench.exe
AES/CTR benchmarks...
  2.7 GHz cpu frequency
  3.39823 cycles per byte (cpb)
  757.723 MiB per second (MiB)

第六步,为了获得最佳/最一致的结果,您需要将处理器从省电模式或低能量状态中取出。该库在Linux上使用governor.sh来执行此操作。它位于TestScript/目录中。
第七步,在我使用以下方式切换到CTR模式解密时:
CTR_Mode<AES>::Decryption cipher;
cipher.SetKeyWithIV(key, key.size(), key);

然后我看到批量解密的速率大致相同:

$ ./bench.exe
AES/CTR benchmarks...
  2.7 GHz cpu frequency
  0.579923 cycles per byte (cpb)
  4440.11 MiB per second (MiB)

第八点,这里是一些不同机器的基准测试数据集合。在调整测试时,它应该提供一个大致的目标。
我的Beaglebone开发板运行在980 MHz时,数据传输速度是您报告的两倍。Beaglebone以无聊的40 cpb和20 MB/s的速度运行,因为它是纯C/C++,并未针对A-32进行优化。
(这是一份关于不同处理器的加密库性能测试数据,包括Skylake Core-i5、Core2 Duo、LeMaker HiKey Kirik SoC Aarch64、AMD Opteron Aarch64、BananaPi Cortex-A7开发板和IBM Power8服务器。)
我的收获是:
  • 在现代计算机上,CTR模式的批量加密和解密大致相同
  • CTR模式的密钥设置不同,解密在现代计算机上需要更长的时间
  • 小块大小比大块大小更昂贵
这正是我所期望看到的。
我认为你下一步应该使用Crypto++ wiki上的示例程序收集一些数据,然后评估结果。

-2

3
那个30%的数字是在8位CPU的背景下得出的。我认为在大型CPU上这个差别不会太大。 - CodesInChaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接