使用 Intel 集成性能基元 (IPP) 优化 RSA 加密算法





0/5 (0投票)
在本文中,我将通过演示如何优化流行的 RSA 加密算法,向您介绍 Intel 的加密 API。
Intel® 集成性能原语 (Intel® IPP) Intel IPP 加密是一个软件库,作为 Intel 广泛的 IPP 库的附加组件。它是一个 API,提供一套全面且高度优化的加密函数,用于构建健壮、高效且安全的加密模型和其他提供最大性能和速度的领域应用程序软件。
在本文中,我将通过演示如何优化流行的 RSA 加密算法,向您介绍 Intel 的加密 API。我们将简要介绍如何将 API 与 Microsoft Visual Studio 集成,并开始使用库中的函数。设置完成后,我们将继续使用 API 中提供的函数来实现和创建 RSA 加密系统。在继续进行实现之前,我将尝试解释 RSA 算法的工作原理。但首先,让我们在开发环境中设置好库。(在本教程中,我们将始终使用 Microsoft Visual Studio 作为我们的开发环境)。
开始使用 Intel IPP 加密
我将简要展示如何将您的开发环境与 Intel IPP 加密集成。Intel IPP 可作为 Intel® Parallel Studio XE、Intel System Studio 的一部分,或作为独立版本提供。加密功能是以独立包的形式提供的,不需要安装主要的 Intel® IPP 包。 要获取 Intel IPP 加密库,您可以遵循本文:我应该在哪里下载 Intel® IPP 加密库? Intel® IPP 加密库也可通过开源获得。请访问 GitHub 上的 Intel® IPP 加密开源页面 来访问库源代码。本文将使用独立的 Intel IPP 加密包 — 我将假设您已下载并安装了开发环境 Microsoft Visual Studio。
然后,我们将下载 Intel IPP 加密的独立版本。
下载后,单击下载的可执行文件以安装 Intel IPP 加密。
一旦 Visual Studio 就绪且 Intel IPP 安装完成,请继续创建您的 c/c++ 项目。创建项目后,我们将把 IPP 加密链接到项目。在 Visual Studio 的文件资源管理器中,右键单击您的项目,然后按照此路径的下拉菜单导航
Properties>vc++ directories
从那里,执行以下操作
- 在 Intel IPP 可执行文件目录(默认为 <install_dir>\redist\<arch>\ippcp\)中键入,作为可执行目录。
我的可执行文件位于以下位置
c:\Program Files(x86)\IntelSWTools\compilers_and_libraries_2019.1.144\windows\redist\ia32_win\ippcp
默认情况下,<install_dir> 位于C:\Program files(x86)\IntelSWTools\compilers_and_libraries_2019.x.xxx\<target_os>
2. 在库目录中键入 Intel IPP 库文件目录(默认为 <ipp directory>\lib)。
我的 IPP 库文件位于以下位置
c:\Program Files(x86)\IntelSWTools\compilers_and_libraries_2019.1.144\windows\ippcp\include
3. 在包含目录中键入 Intel IPP 包含文件目录(默认为 <ipp directory>\include)。
我的 IPP 包含文件位于以下位置
c:\Program Files(x86)\IntelSWTools\compilers_and_libraries_2019.1.144\windows\ippcp\lib
配置完项目后,要链接 Intel IPP 加密,请向您的项目添加一个 C/C++ 文件,并将 此处 的以下代码复制到文件中并进行构建。成功构建代码后,运行它,您应该会看到以下输出
在此阶段,我们已准备好继续实现我们的 RSA 算法。
但在此之前,让我们花几分钟时间了解 RSA 算法的工作原理。
RSA 加密算法
RSA 是一种非对称加密算法。这意味着它使用不同的密钥进行加密和解密。加密密钥称为公钥,解密密钥称为私钥。接收方发布其公钥,发送方可以使用此公钥加密消息以生成密文。密文被发送到接收方,并可以使用私钥进行解密。
本质上,加密学就是纯数学。基本上,我只需要两个函数来构建一个加密算法。一个函数将是另一个函数的逆。这意味着如果我用一个函数计算一个值,我可以用逆函数恢复输入值。让我们探讨 RSA 算法所使用的基本数学概念。
RSA 通过一个函数将明文 (m) 加密为密文 (c)。然后,可以使用逆函数解密此密文以获得原始明文消息。
加密函数:me mod n = c
解密函数:cd mod n = m
(e, n) 参数构成公钥,而 (d, n) 构成私钥。
一旦明文被加密,如果不了解模数 n,就很难解密原始消息。RSA 模型的安全性基于素数分解的思想 — 即任何存在的数字都有唯一的素数分解,并且由于尚未找到系统性方法,素数分解被认为很难做到。例如,15 的素数分解是 3 * 5,这只能通过试错法找到。当一个数字变得非常大时,找到其素数分解几乎是不可能的。知道变量 n 的因子称为 RSA 问题,因为如果您知道了,您就可以解密用公钥加密的消息。
我们如何计算 e、n 和 d?
I. 选择两个非常大的素数 (p 和 q)。
II. 找到 n,其中 n=p*q。n 成为公钥和私钥加密的模数。
III. 计算 phi,称为欧拉函数:phi(φ) = (p-1) (p-1)。
IV. 选择满足以下条件的 e
- 1 < e < phi(φ)
- e 和 phi(φ) 是素数,除了 1 之外没有其他公约数。
V. 计算 d,使得 d*e mod phi = 1。
此时,您的加密模型已设置为 n = 模数, e = 公钥指数, d = 私钥指数。
VI. 加密和解密过程可以开始。为了使系统健壮,n 必须是一个非常大的数字(理想情况下为 4,096 位长)。这是因为随着计算机速度的提高,位长较短的 n 参数提供的置换非常有限,容易被猜测。
使用 Intel 的 IPP 加密的 RSA 算法
现在我们已经理解了加密学的要点,让我们看看 Intel 的 IPP 如何帮助优化加密算法的性能。我们将一步一步地进行。
为了开始,我们将首先创建我们的 RSA 加密系统,它将使我们能够生成加密和解密消息所需的各种参数(e, n, d)。幸运的是,Intel IPP 已经处理了我们本应通过加密 API 提供的函数来完成的重要任务。它提供了一个高效的实现来处理我们加密系统中的大整数。(阅读有关 Intel IPP 大整数及其算术运算的 https://software.intel.com/en-us/ipp-crypto-reference。)我们实际上不会使用大整数运算,因为 API 中可用的函数将为我们完成这项工作;但是,了解大整数将有助于您在此实现过程中。
设置我们的加密系统
1. 我们将首先将密钥组件(e, n, d)的上下文大小指定为 IppsBigNumState
。
// specify the context of key components to contain generated key // data IppsBigNumState* pModulus = newBN(1024 / 32, NULL); IppsBigNumState* pPublicExp = newBN(1024 / 32, NULL); IppsBigNumState* pPrivateExp = newBN(1024 / 32, NULL);
我们的密钥组件的上下文使用 newBN()
函数创建。一旦我们初始化了我们的加密系统并调用了 ippsRSA_GenerateKeys()
,我们就可以从这些 IppsBigNumState
状态变量中检索我们的密钥。
2. 然后,我们指定加密系统中各种参数的位数。
// (bit) size of key components int bitsN = 1024; // Length of RSA system in bits(the modulus. size of // our modulus, must be large(4096 standard), but we will 1024 int bitsE = 512; // Length of the RSA public exponent in bits(the e //component int bitsP = 512; //Length in bits of the p factors of the modulus(that //is, the p in the equation: n = p*q int bitsQ = 512;//Length in bits of the q factors of the modulus(that //is, the q in the equation: n = p*q int keyCtxSize; //Available size of memory buffer being initialized
3. 我们将按照以下步骤定义和设置我们的公钥组件
- 使用
ippsRSA_GetSizePublicKey()
计算我们的公钥的大小。 - 为公钥上下文分配足够大的内存空间。
- 初始化公钥上下文。
// setup public key // calculate size of public keys context ippsRSA_GetSizePublicKey(bitsN, bitsE, &keyCtxSize); //public key context allocated with generated buffer size IppsRSAPublicKeyState* pPub = (IppsRSAPublicKeyState*)(new Ipp8u[keyCtxSize]); //context initialized in memory ippsRSA_InitPublicKey(bitsN, bitsE, pPub, keyCtxSize);
4. 我们还将定义和设置我们的私钥组件,其步骤与我们为私钥组件遵循的步骤相同。
- 计算公钥的大小
ippsRSA_GetSizePrivateKeyType2()
。 - 为私钥上下文分配足够大的内存空间。
- 初始化私钥上下文。
// setup (type2) private key //calculate size of private keys context ippsRSA_GetSizePrivateKeyType2(bitsP, bitsQ, &keyCtxSize); //private key context allocated with generated buffer size IppsRSAPrivateKeyState* pPrv = (IppsRSAPrivateKeyState*)(new Ipp8u[keyCtxSize]); //context initialized in memory ippsRSA_InitPrivateKeyType2(bitsP, bitsQ, pPrv, keyCtxSize);
5. 我们将创建临时缓冲区以协助 RSA 操作。
为此,我将检索生成的私钥或公钥缓冲区大小,并使用此大小为我的临时缓冲区分配大小。
int buffSizePrivate; // create variable to reference retrieved size ippsRSA_GetBufferSizePrivateKey(&buffSizePrivate, pPrv); //retrieve //buffer size for private key Ipp8u * scratchBuffer = NULL; //initialize scratch buffer as ipp8u //(usigned char equivalent) scratchBuffer = new Ipp8u[buffSizePrivate]; //allocate memory for //scratch buffer
检查 ippsRSA_GetBufferSizePrivateKey()
的函数输入参数 https://software.intel.com/en-us/ipp-crypto-reference
6. 我们这里需要两样东西
- 我们需要一个随机数生成器,以确保在每次加密系统中都生成不同的密钥集。
- 一个素数生成器,用于为我们的 p,q 变量(在我们的 n=p*q 方程中)生成素数。
// random generator IppsPRNGState* pRand = newPRNG(); // prime generator IppsPrimeState* pPrimeG = newPrimeGen(512);
有了这些,我们的加密系统就设置好了,可以使用了。但我们需要验证我们的系统,以确保我们生成的密钥是有效的,并且一切正常。
7. 要验证密钥,我们将使用 ippsRSA_ValidateKeys()
函数。我们将在验证函数中使用上面创建的上下文。
在此处查看 ippsRSA_ValidateKeys()
的函数输入 here
int validateRes = IS_VALID_KEY; // IS_VALID_KEY is the success code // validate keys ippsRSA_ValidateKeys(&validateRes, pPub, pPrv, NULL, scratchBuffer, 10, pPrimeG, ippsPRNGen, pRand); // check that success code hasn’t changed, and print message on //successful if (IS_VALID_KEY == validateRes) { cout << "validation successful \n" << endl; }
8. 在成功设置我们的 RSA 加密系统后,我们将继续生成我们的密钥。ippsRSA_GenerateKeys()
函数为我们的加密系统生成密钥组件。
在此处阅读有关 ippsRSA_GenerateKeys()
的更多信息 here
// Pointer to IppsBigNumState context for searching an RSA public //exponent Ipp32u E = { 0x11 }; IppsBigNumState* pSrcPublicExp = newBN(1, &E); IppStatus status; // reference to generated message code // keys generator status = ippsRSA_GenerateKeys(pSrcPublicExp, pModulus, pPublicExp, pPrivateExp, pPrv, scratchBuffer, 10, pPrimeG, ippsPRNGen, pRand); // check for successful generation of keys // ippStsNoErr signals success if (status == ippStsNoErr) { cout << "keys generation successful \n" << endl; }
9. 让我们在屏幕上显示我们生成的密钥组件(n, e, d)。
调用 ippsRSA_GenerateKeys()
函数后,我们生成的密钥将存储在我们最初创建的 IppsBigNumState
变量中。因此,我们将使用这些变量创建 BigNumber
对象实例,并使用 tBN()
BigNumber
方法显示每个对象中的数据。
// get modulus generated BigNumber modN(pModulus); // create BigNumber instance of modulus modN.tBN("Modulus (n): "); // display key data cout << "\n" << endl; // print empty line // get public key generated BigNumber Pk(pPublicExp); // create BigNumber instance of public key Pk.tBN("Public Key Exponent (e): "); // display key data cout << "\n" << endl; // print empty line //get private key generated BigNumber Pvk(pPrivateExp); //create BigNumber instance of private key Pvk.tBN("Private Key Exponent (d): "); // display key data
此时,加密系统已设置完毕,可以生成密钥。让我们看看加密和解密过程是如何完成的。
10. 为了开始加密和解密过程,我们首先使用上述生成的密钥在现有的 RSA 上下文中设置我们的公钥和私钥。
// set public key with generated public key ippsRSA_SetPublicKey(pModulus, pPublicExp, pPub); // set up type1 private key component with generated private key ippsRSA_SetPrivateKeyType1(pModulus, pPrivateExp, pPrv);
在此处查看 ippsRSA_SetPublicKey()
和 ippsRSA_SetPrivateKeyType1()
的函数输入参数 here
11. 假设我们有一条要加密的消息,我们将首先使用一些定义的方案将其转换为一个值。让我们假设转换后的格式是下面的 Ipp32u 格式的 dataM,然后让我们对其进行加密。
Ipp32u dataM[] = { // plain text to be encrypted 0x12345678,0xabcde123,0x87654321, 0x111aaaa2,0xbbbbbbbb,0xcccccccc, 0x12237777,0x82234587,0x1ef392c9, 0x43581159,0xb5024121,0xa48D2869, 0x2abababa,0x1a2b3c22,0xa47728B4, 0x54321123,0xaaaaaaaa,0xbbbbbbbb, 0xcccccccc,0xdddddddd,0x34667666, 0xa46a3aaa,0xe4251e84,0xf31f2Eff, 0xfec55267,0x11111111,0x98765432, 0x54376511,0x21323111,0x85433abc,0xcaa44322,0x001234ef }; // we will create ciphertext context with size of dataN Ipp32u dataN[] = { // data for ciphertext context creation 0x03cccb37,0x6acadded,0xdf4f20d0,0x2458257d, 0xda3b7886,0x5c1b1a4c,0xea6f676b,0x59f51e09, 0xc0691195,0x8076c61f,0x4221d059,0xd021673a, 0x139bd5ef,0x95189046,0x10eb90ea,0x127af4e5, 0x14f5dcb8,0x1e13510f,0x6e2e0558,0xa650fce0, 0xff0bcd51,0xe218e43d,0xad045536,0xdc4a21d7, 0x74edee68,0xb474ad57,0x79514004,0xa65a27a3, 0x9e5259c1,0xe78e89eb,0xb34ed292,0x99197f0d }; // create contexts for message and ciphertext, this allocate the //appropriate memory size for storing message and ciphertext // create message context IppsBigNumState* Msg = newBN(sizeof(dataM) / sizeof(dataM[0]), dataM); //create ciphertext context IppsBigNumState* C = newBN(sizeof(dataN) / sizeof(dataN[0])); // encrypt message, and return status code for verification IppStatus status1; status1 = ippsRSA_Encrypt(Msg, C, pPub, scratchBuffer); // check for successful encryption of msg if (status1 == ippStsNoErr) { cout << "message encryption successful \n" << endl; }
12. 然后,我们将创建我们的解密上下文,并继续解密加密的消息(密文)。
// create de-ciphertext context IppsBigNumState* Z = newBN(sizeof(dataN) / sizeof(dataN[0])); // de-crypt message, and return status code for verification IppStatus status2; status2 = ippsRSA_Decrypt(C, Z, pPrv, scratchBuffer); // check for successful decryption of ciphertext if (status2 == ippStsNoErr) { cout << "message decryption successful \n" << endl; }
13. 现在我们确信我们的加密和解密过程已成功完成,我们的最后一步是验证解密的密文确实与我们的明文消息相同。
我们将通过 IPP 的 ippsCmp_BN()
函数将明文消息与解密的消息进行比较来做到这一点。
// compare plaintext and decrypted message Ipp32u Result; // reference to generated status code ippsCmp_BN(Msg, Z, &Result); // plain text and decrypted cipher text cout << Result << endl; // comparison 0 --> OK
// remove sensitive data and free resources used ippsRSA_InitPrivateKeyType2(bitsP, bitsQ, pPrv, keyCtxSize); // delete generators deletePrimeGen(pPrimeG); deletePRNG(pRand); // release resource delete[] scratchBuffer; delete[](Ipp8u*) pPub; delete[](Ipp8u*) pPrv; return 0; // finish program
您可以在 GitHub 上访问完整代码(包括本文中使用的 Intel 源代码)。
结论
在任何与 IT 相关的环境中,在性能和安全性之间取得平衡都很难,尤其是在加密算法方面。幸运的是,借助 Intel IPP 等工具,可以轻松地最大化加密性能,而不会损害算法的健壮性,也不会花费大量成本购买执行它的基础设施。