
4. Комментарии к алгоритму
1. Как на практике выбрать два простых числа р и q ?
Так как значение n = pq будет известно любому потенциальному противнику, то для того, чтобы не допустить возможности нахождения р и q с помощью простого перебора вариантов, эти простые числа должны быть выбраны из достаточно большого множества (т.е. р и q должны быть большими числами).
Вкратце процедуру выбора простого числа можно представить в следующем виде.
Выберите нечетное целое число n некоторым случайным образом (например, используя генератор псевдослучайных чисел встроенный в язык программирования, который вы используете).
Для того чтобы проверить число на простоту воспользуемся теоремой Ферма):
Если p является простым и a является положительным целым числом, которое не делится на p, то:
.
Для простоты выбора a будем выбирать любое a< n, т.к. любое такое a заведомо не делится на n. При этом если возведение в степень выполнять непосредственно с целыми числами и только потом проводить сравнение по модулю n, то промежуточные значения окажутся просто огромными. К счастью, здесь можно воспользоваться свойствами арифметики в классах вычетов:
[(a mod n) x (b mod n)] mod n = (a x b) mod n .
Таким образом, мы можем рассматривать промежуточные результаты по модулю п. Это делает вычисления возможными на практике.
Также можно выделить проблему эффективной реализации операции возведения в степень, так как мы должны иметь дело с потенциально большими показателями. Чтобы продемонстрировать, насколько здесь можно увеличить эффективность вычислений, предположим, что необходимо вычислить x16. Прямолинейный подход потребует 15 умножений, как показано ниже.
Однако такого же конечного результата можно достичь и с помощью всего четырех умножений, если повторно возводить в квадрат промежуточные результаты, получая при этом x2, x4, x8, x16.
Приведем реализацию алгоритма возведения положительного целого в положительную целую степень в классах вычетов[5,6].
Unsigned ex_pow(unsigned x, unsigned n, unsigned p)
{
unsigned m;
unsigned __int64 res=1, t;
if(n == 0) return 1;
while(n!=0)
{
for(m=2, t=x; m<=n && t > 4294967295; m*=2)
{
t*=t;
t%=p;
}
if(m <= n)
m/=2;
res*=t;
res%=p;
n-=m;
if(n == 1)
return (unsigned)((x*res)%p);
}
return (unsigned)res;
}
Зная, что если тождество Ферма не выполняется, то это значит, что число n не является простым. Но если тождество выполняется, то это не значит, что n — простое. Поэтому эту проверку необходимо проводить при разных значениях a.
Учитывая, что теорема Ферма дает нам необходимое условие, но не достаточное, приведем вероятностный тест на «простоту»[7]:
int Ferma(unsigned p, unsigned beg, unsigned end)
{
unsigned a;
for(a=beg; a<=end; a++)
if(ex_pow(a,p-1,p)!=1) return 0;
return 1;
}
где p – проверяемое число, beg и end – границы интервала выбора a. Функция возвращает 0, в том случае если найдено такое a, при котором тождество Ферма не выполняется. В противном случае — 1.
2. Вычисляется n = pq.
Т.к. n — мощность алфавита, а размер блока (S) зависит от мощности как
.
Отсюда следует, что если размер блока S будем брать равный 8 битам (для побайтового шифрования), то получается, что n должно быть не менее 256 (n≥28).
3. Вычисляется φ(n) = (p – 1)(q -1) .
4. Выбирается e, взаимно простое с φ(n) и меньше, чем φ(n). Определяется такое d, что de = 1 mod φ(n) и d < φ(n).
Необходимо сначала выбрать такое е, что НОД(φ(n), е) = 1 (т.е. Наибольший общий делитель φ(n) и e = 1), а потом вычислить d = e–1 mod φ(n). К счастью, имеется один алгоритм, который в одно и то же время вычисляет наибольший общий делитель двух целых чисел и, если наибольший общий делитель оказывается равным 1, определяет обратное для одного из целых чисел по модулю другого. Этот алгоритм называется обобщенным алгоритмом Евклида.
Алгоритм Евклида опирается на следующую теорему[4]:
Для любого неотрицательного числа a и любого положительного целого числа b справедливо следующее.
НОД(a, b)=НОД(b, a mod b).
Рассмотрим реализацию расширенного алгоритма Евклида:
int Euklid(int e,int f)
{
int x1=1,x2=0,x3=f;
int y1=0, y2=1, y3=e;
int Q=0;
int T1,T2,T3;
while(true)
{
if (y3 == 0) return 0; //нет обратного
if (y3 == 1)
if(y2<0)
return f+y2;//y2 = e^-1 mod f
else
return y2; //y2 = e^-1 mod f
Q = x3/y3; //Q – целая часть от деления x3 на y3
T1=x1 – Q*y1; T2=x2 – Q*y2; T3=x3 % y3;
x1=y1; x2=y2; x3=y3;
y1=T1; y2=T2; y3=T3;
}
}
Функция возвращает 0 в случае, если e и f не являются взаимно простыми. Иначе возвращается число d = e–1mod f.
Результат выполнения функции Euklid(550, 1769) |
||||||
Q |
x1 |
x2 |
x3 |
y1 |
y2 |
y3 |
0 |
1 |
0 |
1769 |
0 |
1 |
550 |
3 |
0 |
1 |
550 |
1 |
-3 |
119 |
4 |
1 |
3 |
119 |
-4 |
13 |
74 |
1 |
-4 |
13 |
74 |
5 |
-16 |
45 |
1 |
5 |
-16 |
45 |
-9 |
29 |
29 |
1 |
-9 |
29 |
29 |
14 |
-45 |
16 |
1 |
14 |
-45 |
16 |
-23 |
74 |
13 |
1 |
-23 |
74 |
13 |
37 |
-119 |
3 |
4 |
37 |
-119 |
3 |
-171 |
550 |
1 |
Теперь, когда мы выбрали числа e и d, мы можем сгенерировать открытый (KU={e,n}) и личный (KR={d,n}) ключи.
Т.к. трудоемкость при шифровании/дешифровании зависит от e и d экспоненциально, то, для быстрого выполнения шифрования и дешифрования, желательно выбирать e и d не очень большими (не более 10000).
Шифрование/дешифрование.
При шифровании открытый текст возводится в степень e. В результате деления на n определяется остаток, который будет шифрованным текстом.
Для дешифрования необходимо шифротекст возводиться в степень d и взять остаток от деления на n, результатом операции будет открытый текст.