Криптографія на Python: шифрування інформації і створення електронних цифрових підписів з допомогою пакета PyCrypto



Довго мучився з PyCryptoв результаті вийшла ця стаття і повна реалізація наступного протоколу:

Етап відправки:

1. Аліса підписує повідомлення своєї цифровим підписом і шифрує її відкритим ключем Боба (асиметричним алгоритмом).
2. Аліса генерує випадковий сеансовий ключ шифрування цим ключем повідомлення (з допомогою симетричного алгоритму).
3. Сеансовий ключ шифрується відкритим ключем Боба (асиметричним алгоритмом).
Аліса посилає Бобу зашифроване повідомлення, підпис і зашифрований сеансовий ключ.

Етап прийому:

Боб отримує зашифроване повідомлення Аліси, підпис і зашифрований сеансовий ключ.
4. Боб розшифровує сеансовий ключ своїм закритим ключем.
5. За допомогою отриманого таким чином, сеансового ключа Боб розшифровує зашифроване повідомлення Аліси.
6. Боб розшифровує і перевіряє підпис Аліси.

Вищеописаний протокол є гібридною системою шифрування, яка працює наступним чином. Для симетричного алгоритму AES (або будь-якого іншого) генерується випадкова сеансовий ключ.

Такий ключ, як правило, має розмір від 128 до 512 біт (в залежності від алгоритму). Потім використовується симетричний алгоритм шифрування повідомлення. У разі блочного шифрування потрібно режим шифрування (наприклад, CBC), що дозволить шифрувати повідомлення з довжиною, що перевищує довжину блоку. Що стосується самого випадкового ключа, він повинен бути зашифровано за допомогою відкритого ключа отримувача повідомлення, і саме на цьому етапі застосовується криптосистема з відкритим ключем RSA.

Оскільки сеансовий ключ короткий, його шифрування займає трохи часу. Шифрування набору повідомлень за допомогою асиметричного алгоритму — це завдання обчислювально більш складна, тому тут краще використовувати симетричне шифрування. Потім достатньо відправити повідомлення, зашифроване симетричним алгоритмом, а також відповідний ключ в зашифрованому вигляді. Одержувач спочатку розшифровує ключ з допомогою свого секретного ключа, а потім за допомогою отриманого ключа і отримує повідомлення.

Почнемо з генерації пари ключів для Аліси і Боба.

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

# key generation
privatekey = RSA.generate(2048)
f = open('c:\cipher\\alisaprivatekey.txt','wb')
f.write(bytes(privatekey.exportKey('PEM'))); f.close()
publickey = privatekey.publickey()
f = open('c:\cipher\\alisapublickey.txt','wb')
f.write(bytes(publickey.exportKey('PEM'))); f.close()

privatekey = RSA.generate(2048)
f = open('c:\cipher\\bobprivatekey.txt','wb')
f.write(bytes(privatekey.exportKey('PEM'))); f.close()
publickey = privatekey.publickey()
f = open('c:\cipher\\bobpublickey.txt','wb')
f.write(bytes(publickey.exportKey('PEM'))); f.close()


На даний момент система шифрування на основі RSA вважається надійною, починаючи з розміру n 2048 біт.

В системі RSA можна створювати повідомлення, які будуть зашифровані, і містити цифровий підпис. Для цього автор спочатку повинен додати до повідомлення цифровий підпис, а потім зашифрувати отриману в результаті пару (що складається з самого повідомлення, підписи до нього) за допомогою відкритого ключа, що належить одержувачу. Одержувач розшифровує отримане повідомлення за допомогою свого таємного ключа і перевіряє підпис автора з його допомогою відкритого ключа.

Використання односторонньої криптографічного хеш-функції дозволяє оптимізувати вищеописаний алгоритм цифрового підпису. Проводиться шифрування не самого повідомлення, а значення хеш-функції, взятої від повідомлення. Даний метод забезпечує наступні переваги:

1. Зниження обчислювальної складності. Як правило, документ значно більше його хешу.
2. Підвищення криптостійкості. Криптоаналітик не може, використовуючи відкритий ключ, підібрати підпис під повідомлення, а тільки під його хеш.
3. Забезпечення сумісності. Більшість алгоритмів оперує з рядками біт даних, але деякі використовують інші уявлення. Хеш-функцію можна використовувати для перетворення довільного вхідного тексту у відповідний формат.

Хеш-функції, що являють собою функції, математичні чи інші, які отримують на вхід рядок змінної довжини (звану прообразом) і перетворюють її в рядок фіксованої, звичайно, меншої довжини (звану значенням хеш-функції). Зміст хеш-функції полягає в отриманні характерного ознаки прообразу — значення, за якого аналізуються різні прообрази при вирішенні оберненої задачі. Односпрямована хеш-функція — це хеш-функція, яка працює тільки в одному напрямку: легко обчислити значення хеш-функції за прообразом, але важко створити прообраз, що значення хеш-функції якої дорівнює заданій величині. Хеш-функція є відкритою, таємниці її розрахунку не існує. Безпека односпрямованої хеш-функції полягає саме в її однонаправленості.

Односпрямована функція H(M) застосовується до повідомлення довільної довжини М і повертає значення фіксованої довжини h.

h = H(M), де h має довжину m

Багато функції дозволяють обчислити значення фіксованої довжини за вхідними даними довільної довжини, але у односпрямованої хеш-функції є додаткові властивості, які роблять їх односпрямованими:

Знаючи М, легко обчислити h
Знаючи H, важко визначити, для якого H(M) = h
Знаючи М, важко визначити інше повідомлення, М', для якого H(M) = H(M')

Переходимо до написання першого пункту протоколу:

1. Аліса підписує повідомлення своїм підписом і шифрує її відкритим ключем Боба (асиметричним алгоритмом).

from Crypto.Hash import MD5
from Crypto.PublicKey import RSA
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP

# the creation of signature
f = open('c:\cipher\plaintext.txt','rb')
plaintext = f.read(); f.close()
randomnum = Random.new().read(16)
privatekey = RSA.importKey(open('c:\cipher\\alisaprivatekey.txt','rb').read())
myhash = MD5.new(plaintext).hexdigest()
signature = privatekey.sign(myhash, randomnum) # type tuple

# check long signature
signature = str(signature); a = len(signature); b = 16; c = 'aaaaaaaaaaaaaa'; i = 0
while a%b != 0:
i += 1
signature = signature + 'a'
a = len(signature)

if i > 0:
if i == 1:
signature = signature + c + '17'
if i > 1:
if i < 10:
num = '0'
signature = signature[0:-2] + num + str(i)
if i > 9:
signature = signature[0:-2] + str(i)

# signature encrypt
publickey = RSA.importKey(open('c:\cipher\\bobpublickey.txt','rb').read())
cipherrsa = PKCS1_OAEP.new(publickey)

a = len(signature); b = 16; c = a/b; i = 0; ii = -16; iii = 0

while i < c:
i += 1
ii += 16
iii += 16
if i == 1:
sig = cipherrsa.encrypt(signature[0:16])
if i > 1:
sig = sig + cipherrsa.encrypt(signature[ii:iii])

f = open('c:\cipher\signature.txt','wb')
f.write(bytes(sig)); f.close()


Кількість байт в підписі має бути кратно 16, інакше при шифруванні асиметричним алгоритмом видасть помилку. Я додав блок check long signature, який додає підпис недостатню кількість байт і записує це число в кінець підпису. Перед перевіркою підпису потрібно видалити всі символи. При шифруванні кожен блок по 16 байт шифрується окремо, для цього я використовував цикл. При генерації випадкового числа (генератором псевдовипадкових чисел для шифру AES (змінна randomnum) в методі read обов'язково вказуйте довжину кратну 128 біт або 16 байт.

Я використовував 128-бітний алгоритм хешування MD5.

В наступному лістингу код для двох пунктів протоколу:

2. Аліса генерує випадковий сеансовий ключ шифрування цим ключем повідомлення (з допомогою симетричного алгоритму).
3. Сеансовий ключ шифрується відкритим ключем Боба (асиметричним алгоритмом).

from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

# test long message file
f = open('c:\cipher\plaintext.txt','rb')
plaintext = f.read(); f.close()

a = len(plaintext); b = 16; i = 0
while a%b != 0:
i += 1
plaintext = plaintext + 'a'
a = len(plaintext)

if i > 0:
if i < 10:
num = '0'
plaintext = plaintext[0:-2] + num + str(i)
else:
plaintext = plaintext[0:-2] + str(i)

# the creation of a session key
sessionkey = Random.new().read(32) # 256 bit

# the encryption AES of the message
iv = Random.new().read(16) # 128 bit
obj = AES.new(sessionkey, AES.MODE_CBC, iv)
ciphertext = iv + obj.encrypt(plaintext)

f = open('c:\cipher\plaintext.txt','wb')
f.write(bytes(ciphertext)); f.close()

# the encryption RSA of the session key
publickey = RSA.importKey(open('c:\cipher\\bobpublickey.txt','rb').read())
cipherrsa = PKCS1_OAEP.new(publickey)
sessionkey = cipherrsa.encrypt(sessionkey)

f = open('c:\cipher\sessionkey.txt','wb')
f.write(bytes(sessionkey)); f.close()


Кількість байт у повідомленні має бути кратно 16. Як і у випадку з підписом я змінив вихідне повідомлення, потім створив сесійний ключ розміром 256 біт. Ну і звичайно зашифрував сесійний ключ з допомогою відкритого ключа Боба.

для режиму CBC потрібно вектор ініціалізації (IV) (змінна iv).

В криптографії, вектор ініціалізації (IV) являє собою деяке число, як правило воно повинно бути випадковим або псевдовипадковим. Випадковість має вирішальне значення для досягнення семантичної безпеки, яка при повторному використанні схеми під тим же ключем не дозволить зловмиснику вивести відносини між сегментами зашифрованих повідомлень. Вектор ініціалізації не шифрується і зберігається перед зашифрованим повідомленням.

Далі Аліса посилає Бобу зашифроване повідомлення, підпис і зашифрований сеансовий ключ.

Боб отримує зашифроване повідомлення Аліси, підпис і зашифрований сеансовий ключ.

4. Боб розшифровує сеансовий ключ своїм закритим ключем.
5. За допомогою отриманого таким чином, сеансового ключа Боб розшифровує зашифроване повідомлення Аліси.

from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA

# the decryption session key
privatekey = RSA.importKey(open('c:\cipher\\bobprivatekey.txt','rb').read())
cipherrsa = PKCS1_OAEP.new(privatekey)
f = open('c:\cipher\sessionkey.txt','rb')
sessionkey = f.read(); f.close()
sessionkey = cipherrsa.decrypt(sessionkey)


# the decryption and reception of the vector IV
f = open('c:\cipher\plaintext.txt','rb')
ciphertext = f.read(); f.close()
iv = ciphertext[:16]

obj = AES.new(sessionkey, AES.MODE_CBC, iv)
plaintext = obj.decrypt(ciphertext)

f = open('c:\cipher\plaintext.txt','wb')
f.write(bytes(plaintext[16:])); f.close()

# remove unnecessary
f = open('c:\cipher\plaintext.txt','rb')
plaintext = f.read(); f.close()

num = plaintext[-2:]

f = open('c:\cipher\plaintext.txt','wb')
f.write(bytes(plaintext[:-int(num)])); f.close()


Для дешифрування не забувайте отримати вектор ініціалізації, який знаходиться в перших 16 байтах шіфротекста.
У блоці remove unnecessary ми видаляємо вже непотрібні символи, які ми додавали в повідомлення для його шифрування (діна повідомлення повинна бути кратна 16).

Останній і найважчий етап:

6. Боб розшифровує і перевіряє підпис Аліси.

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from Crypto.Hash import MD5

# decrypt the signature
f = open('c:\cipher\signature.txt','rb')
signature = f.read(); f.close()

privatekey = RSA.importKey(open('c:\cipher\\bobprivatekey.txt','rb').read())
cipherrsa = PKCS1_OAEP.new(privatekey)

a = len(signature); b = 256; c = a/b; i = 0; ii = -256; iii = 0
while i < c:
i += 1
ii += 256
iii += 256
if i == 1:
sig = cipherrsa.decrypt(signature[0:256])
if i > 1:
sig = sig + cipherrsa.decrypt(signature[ii:iii])

signature = sig
num = signature[-2:]
signature = signature[:-int(num)]

# signature verification
f = open('c:\cipher\plaintext.txt','rb')
plaintext = f.read(); f.close()
publickey = RSA.importKey(open('c:\cipher\\alisapublickey.txt','rb').read())
myhash = MD5.new(plaintext).hexdigest()

t = signature
t = t[1:-3]
t = int(t)
t = (t,)
test = publickey.verify(myhash, t)
test = signature + str(test)

f = open('c:\cipher\signature.txt','wb')
f.write(bytes(test)); f.close()


Кожні 16 байт підпису зашифровувати в 256 байт шіфротекста, тому підпис дешифрується блоками по 256 байт. Підпис це кортеж, тому рядок signature потрібно обов'язково перетворити в кортеж. Після перевірки підпису, в кінець файлу signature.txt додасться True — у разі успішної перевірки або False якщо повідомлення після підписання було змінено.

Безпека RSA базується на складності завдання факторизації твори двох великих простих чисел. Факторизація цілих чисел для великих чисел є завданням великої складності. Не існує жодного відомого способу, щоб вирішити цю задачу швидко. Факторизація кандидат в односторонні функції, які відносно легко обчислюються, але інвертуються з великим трудом. Тобто, знаючи x просто розрахувати f(x), але за відомим f(x) нелегко обчислити x. Тут, «нелегко» означає, що для обчислення x f(x) можуть знадобитися мільйони років, навіть якщо над цією проблемою будуть битися всі комп'ютери світу.

Література:
Брюс Шнайер — Прикладна криптографія

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.