关于RSA加密的各种问题
Posted
技术标签:
【中文标题】关于RSA加密的各种问题【英文标题】:Various questions about RSA encryption 【发布时间】:2013-12-05 09:12:06 【问题描述】:我目前正在用 C++ 为 Unix 编写自己的 ASE/RSA 加密程序。我已经阅读了大约一周的文献,我已经开始思考这一切,但我仍然有一些紧迫的问题:
1) 根据我的理解,最基本形式的 RSA 密钥是使用的两个素数 (R) 和指数的乘积的组合。对我来说很明显,以明文形式存储密钥会完全破坏加密任何内容的目的。因此,我可以以什么形式存储我生成的公钥和私钥?向用户询问密码并使用 ASCII 表对密钥的各个数字进行一些“简单”的移位/替换?还是有其他一些我没有遇到的标准?此外,当生成密钥时,R 和相应的指数是否简单地按顺序存储?即##primeproduct####exponent##?在这种情况下,解密算法如何将密钥解析为两个单独的值?
2) 鉴于我已决定使用 65537 作为所有加密的公共指数,我将如何以编程方式生成私有指数?我有方程 P*Q = 1mod(M),其中 P 和 Q 以及指数和 M 是欧拉总函数的结果。这仅仅是生成随机数并测试它们对公共指数的相对质数,直到你遇到报酬的问题吗?我知道您不能简单地从 1 开始并递增直到找到这样一个数字,因为任何人都可以简单地做同样的事情并自己获取您的私人指数。
3) 在生成字符等价集时,我理解该集中使用的数字不能小于且与 P*Q 互质。同样,这是一个测试数字对 P*Q 的相对素数的问题。测试相对素数的速度是否与您正在使用的数字的大小无关?还是需要特殊算法?
提前感谢所有花时间阅读和回答的人,干杯!
【问题讨论】:
什么是 ASE?你的意思是AES/RSA?它们是两个不同的标准。我假设您的意思只是 RSA,因为它的密钥确实是两个(希望是)非常大的素数的乘法。据我所知(我可能错了),AES 密钥可以是任何值,只要它们是块长度的大小(AES 意味着只有 128 位块)。 这个问题似乎是题外话,因为它是关于一般的密码学,而不是关于编程。 【参考方案1】:有一些用于存储/交换 RSA 密钥的标准格式,例如 RFC 3447。不管是好是坏,大多数(无论如何,很多)都使用 ASN.1 编码,这比大多数人喜欢的都增加了更多的复杂性,这一切都是单独的。少数使用Base64编码,实现起来要容易得多。
就构成密钥而言:以最基本的形式,您是正确的;公钥包括模数(通常称为n
)和指数(通常称为e
)。
要计算密钥对,您从两个大质数开始,通常称为p
和q
。您将模数 n
计算为 p * q
。您还计算了一个数字(通常称为r
),即(p-1) * (q-1)
。
e
是一个或多或少随机选择的数字,相对于r
是质数。警告:你不希望e
非常小——至少 log(e) >= log(n)/4。
然后您将d
(私有解密密钥)计算为满足关系的数字:
d * e = 1 (mod r)
您通常使用欧几里得算法计算此值,但还有其他选项(见下文)。同样,您也不希望 d
非常小,所以如果它的结果非常小,您可能想尝试 e
的另一个值,并计算一个新的 d
来匹配。
还有另一种计算e
和d
的方法。您可以从找到与 1 mod r 一致的数字 K 开始,然后将其分解。将质因子放在一起得到两个大小大致相等的因子,并将它们用作e
和d
。
就计算您的d
的攻击者而言:您需要r
来计算它,而知道r
取决于知道p
和q
。这正是分解 RSA 的原因/地点/方式。如果你考虑n
,那么你就知道p
和q
。从它们中,您可以找到r
,从r
中,您可以计算出与已知e
匹配的d
。
所以,让我们通过数学来创建一个密钥对。我们将使用非常太小而无法有效的素数,但应该足以展示所涉及的想法。
所以让我们从选择 p 和 q 开始(当然,两者都需要是素数):
p = 9999991
q = 11999989
根据我们计算出的n
和r
:
n = 119999782000099
r = 119999760000120
接下来我们需要选择e
或计算K
,然后将其分解为e
和d
。目前,我们将采用您对 e=65537 的建议(因为 65537 是质数,它和r
不是相对质数的唯一可能性是r
是 65537 的精确倍数,我们可以验证不是很容易)。
据此,我们需要计算我们的d
。我们可以使用欧几里得算法的“扩展”版本、(如您所提到的)欧拉的 Totient、高斯方法或其他任何方法相当容易地做到这一点(尽管不一定很快)。
目前,我将使用高斯方法计算它:
template <class num>
num gcd(num a, num b)
num r;
while (b > 0)
r = a % b;
a = b;
b = r;
return a;
template <class num>
num find_inverse(num a, num p)
num g, z;
if (gcd(a, p) > 1) return 0;
z = 1;
while (a > 1)
z += p;
if ((g=gcd(a, z))> 1)
a /= g;
z /= g;
return z;
我们得到的结果是:
d = 38110914516113
然后我们可以将这些插入到 RSA 的实现中,并使用它们来加密和解密消息。
所以,让我们加密“非常机密的消息!”。使用上面给出的e
和n
,加密为:
74603288122996
49544151279887
83011912841578
96347106356362
20256165166509
66272049143842
49544151279887
22863535059597
83011912841578
49544151279887
96446347654908
20256165166509
87232607087245
49544151279887
68304272579690
68304272579690
87665372487589
26633960965444
49544151279887
15733234551614
并且,使用上面给出的d
,它解密回原来的。进行加密/解密的代码(使用硬编码的密钥和模数)如下所示:
#include <iostream>
#include <iterator>
#include <algorithm>
#include <vector>
#include <functional>
typedef unsigned long long num;
const num e_key = 65537;
const num d_key = 38110914516113;
const num n = 119999782000099;
template <class T>
T mul_mod(T a, T b, T m)
if (m == 0) return a * b;
T r = T();
while (a > 0)
if (a & 1)
if ((r += b) > m) r %= m;
a >>= 1;
if ((b <<= 1) > m) b %= m;
return r;
template <class T>
T pow_mod(T a, T n, T m)
T r = 1;
while (n > 0)
if (n & 1)
r = mul_mod(r, a, m);
a = mul_mod(a, a, m);
n >>= 1;
return r;
int main()
std::string msg = "Very Secret Message!";
std::vector<num> encrypted;
std::cout << "Original message: " << msg << '\n';
std::transform(msg.begin(), msg.end(),
std::back_inserter(encrypted),
[&](num val) return pow_mod(val, e_key, n); );
std::cout << "Encrypted message:\n";
std::copy(encrypted.begin(), encrypted.end(), std::ostream_iterator<num>(std::cout, "\n"));
std::cout << "\n";
std::cout << "Decrypted message: ";
std::transform(encrypted.begin(), encrypted.end(),
std::ostream_iterator<char>(std::cout, ""),
[](num val) return pow_mod(val, d_key, n); );
std::cout << "\n";
即使是希望的安全性,您也需要使用更大的模数——至少数百位(对于偏执狂来说可能是一千位或更多)。您可以使用普通的任意精度整数库或专门为手头任务编写的例程来做到这一点。 RSA 本质上是相当慢的,所以曾经大多数实现使用带有大量优化优化的代码来完成这项工作。如今,硬件已经足够快了,您可能可以很容易地摆脱一个相当普通的大整数库(特别是因为在实际使用中,您只想使用 RSA 加密/解密对称算法的密钥,而不是加密原始数据)。
即使具有合适大小的模数(并且修改了代码以支持所需的大量数字),这仍然是有时被称为“教科书 RSA”的内容,并且它并不适合真正的加密方式。例如,现在,它一次加密输入的一个字节。这会在加密数据中留下明显的模式。查看上面的加密数据并发现第二个和第七个单词是相同的,这很简单——因为它们都是e
的加密形式(这也出现在消息中的其他几个地方)。
就目前而言,这可以作为简单的替换代码进行攻击。 e
是英语中最常见的字母,因此我们可以(正确地)猜测加密数据中最常见的单词代表e
(并且各种语言中字母的相对频率是众所周知的)。更糟糕的是,我们还可以查看诸如字母对和三连音之类的东西来改进攻击。例如,如果我们在加密数据中连续两次看到同一个单词,我们就知道我们看到的是一个双字母,在普通英文文本中只能是几个字母。底线:尽管 RSA 本身可以很强大,但上面显示的使用它的方式肯定是不是。
为防止出现该问题,对于(比如说)512 位密钥,我们还将以 512 位块的形式处理输入。这意味着我们只有在原始输入中有两个位置一次 512 位完全相同时才会重复。即使发生这种情况,也很难猜测会发生这种情况,因此尽管它是不可取的,但它并不像上面显示的逐字节版本那样容易受到攻击。此外,您总是希望将输入填充为被加密大小的倍数。
参考
https://crypto.stackexchange.com/questions/1448/definition-of-textbook-rsa
【讨论】:
哇,感谢所有出色的信息!真的很有帮助!另一个问题:加密文本会按原样顺序存储吗?即对于你的榜样?7460328812299649544151279887830119128415789634710635636220256165166509662720491438424954415127988722863535059597830119128415784954415127988796446347654908202561651665098723260708724549544151279887683042725796906830427257969087665372487589266339609654444954415127988715733234551614在这种情况下,你会用什么类型的分隔符来解析编码成原来的个人编码数 SPAN> @user3013727:分隔符的选择几乎取决于您。在典型情况下,您只使用 RSA 加密单个块(您将使用类似 AES 的密钥),因此根本不会出现问题。 C0ffin 好的,我明白了。 “块”是指 AES 块吗? @user3013727:不——它指的是 AES 密钥作为一个单独的项目,要使用 RSA 一次性加密。 假设我的程序已经生成了它的公钥和私钥。我已经在 Base64 中编码了主要产品和公共/私有指数。我现在想将公钥写入文件。我是否将乘积写入文件后紧跟指数,是否添加自己的分隔符,或者我如何存储这两条编码信息,以便当我需要将它们读回并将它们拆分为他们的两个个人价值观我可以吗?以上是关于关于RSA加密的各种问题的主要内容,如果未能解决你的问题,请参考以下文章