Безопасная передача данных с помощью шифрования Public Key и pyNaCl
pyNaCl позволяет легко реализовать безопасную передачу данных.
Это небольшая заметка о безопасной передаче данных между двумя людьми. Для этого мы используем пакет Python pyNaCl для генерации закрытых и открытых ключей, а также для шифрования и расшифровки данных. Я также добавил пакет Python keyring для хранения private_key и public_key. На самом деле это не так сложно. Для этого мне понадобился базовый класс, и вот я им делюсь. Возможно, вы найдете его полезным.
Как всегда, я разрабатываю на Ubuntu 22.04.
Как это работает
Есть два человека, Bob и Alice. Bob хочет отправить некоторые данные через Интернет в Alice безопасным способом. Здесь мы используем шифрование с открытым ключом.
Bob генерирует уникальный ключ private_key и public_key. Он хранит private_key в секрете и делится public_key с Alice. Alice следует той же процедуре, сохраняет свой private_key в секрете и делится своим public_key с Bob.
Чтобы отправить данные в Alice, Bob выполняет следующие действия:
- Bob шифрует данные с помощью public_key из Alice.
- Bob подписывает данные своим собственным private_key
Когда Alice получает зашифрованные данные, она использует свой собственный private_key для расшифровки данных и использует public_key от Bob для проверки, что данные действительно от Bob.
Хранение private_key и public_key
В большинстве систем есть место, где пароли хранятся в безопасности, называемое системной службой keyring . Пакет Python keyring можно использовать для взаимодействия с системным сервисом keyring или использовать как отдельное решение для хранения.
Для каждого user нашего приложения мы храним public_key и (опционально) private_key в поле пароля, используя словарь, который преобразуется в JSON и обратно.
Код
Создайте virtual environment и установите следующее:
> pip install pynacl
> pip install keyring
Вы можете выбрать кодировку Base64 или Hex для ключей и данных. Base64 гораздо эффективнее. Не забывайте удалять ключи при переходе на другую кодировку!
# my_app.py
import json
import os
import sys
import traceback
import keyring as kr
from nacl.utils import random
from nacl.public import Box, PrivateKey, PublicKey
from nacl.encoding import Base64Encoder, HexEncoder
class PKUtils:
def __init__(
self,
key_encoding='hex',
data_encoding='hex',
kr_servicename=None,
):
self.kr_servicename = kr_servicename
self.key_encoder = Base64Encoder if key_encoding == 'base64' else HexEncoder
self.data_encoder = Base64Encoder if data_encoding == 'base64' else HexEncoder
def get_publ_key_from_priv_key(self, priv_key):
priv_key_obj = PrivateKey(priv_key, encoder=self.key_encoder)
publ_key_obj = priv_key_obj.public_key
publ_key_encoded = publ_key_obj.encode(self.key_encoder)
return publ_key_encoded.decode('ascii')
def create_key_pair(self):
priv_key_obj = PrivateKey.generate()
priv_key_encoded = priv_key_obj.encode(self.key_encoder)
priv_key = priv_key_encoded.decode('ascii')
publ_key = self.get_publ_key_from_priv_key(priv_key)
return priv_key, publ_key
def get_box(self, sender_priv_key, receiver_publ_key):
return Box(
PrivateKey(sender_priv_key, encoder=self.key_encoder),
PublicKey(receiver_publ_key, encoder=self.key_encoder)
)
def encrypt_data(self, sender_priv_key, receiver_publ_key, data):
if isinstance(data, str):
data = bytes(data, 'utf-8')
box = self.get_box(sender_priv_key, receiver_publ_key)
nonce = random(Box.NONCE_SIZE)
encrypted_data = box.encrypt(data, nonce, encoder=self.data_encoder)
return encrypted_data
def decrypt_data(self, receiver_priv_key, sender_publ_key, encrypted_data, decode=None):
box = self.get_box(receiver_priv_key, sender_publ_key)
data = box.decrypt(encrypted_data, encoder=self.data_encoder)
if decode is not None:
data = data.decode('utf-8')
return data
def delete_key_pair_from_kr(self, username):
try:
kr.delete_password(self.kr_servicename, username)
except:
pass
def get_key_pair_from_kr_or_create_new(self, username):
try:
keys = json.loads(kr.get_password(self.kr_servicename, username))
return keys['priv_key'], keys['publ_key']
except:
pass
priv_key, publ_key = self.create_key_pair()
kr.set_password(
self.kr_servicename,
username,
json.dumps({'priv_key': priv_key, 'publ_key': publ_key})
)
return priv_key, publ_key
def set_public_key_in_kr(self, username, publ_key, keep_priv_key=False):
priv_key_cur, publ_key_cur = self.get_key_pair_from_kr_or_create_new(username)
if not keep_priv_key:
priv_key_cur = None
kr.set_password(
self.kr_servicename,
username,
json.dumps({'priv_key': priv_key_cur, 'publ_key': publ_key})
)
def main():
pku = PKUtils(
#key_encoding='base64',
#data_encoding='base64',
kr_servicename='my_app',
)
#pku.delete_key_pair_from_kr('bob')
#pku.delete_key_pair_from_kr('alice')
# bob gets/generates priv_key and publ_key
bob_priv_key, bob_publ_key = pku.get_key_pair_from_kr_or_create_new('bob')
print(f'bob_priv_key = {bob_priv_key}, bob_publ_key = {bob_publ_key}')
# alice gets/generates priv_key and publ_key
alice_priv_key, alice_publ_key = pku.get_key_pair_from_kr_or_create_new('alice')
print(f'alice_priv_key = {alice_priv_key}, alice_publ_key = {alice_publ_key}')
# bob wants to send this data
data = """this is line 1
this is line 2
this is line 3
this is line 4
this is line 5
"""
print(f'data = {data}')
# bob encrypts the data
try:
encrypted_data = pku.encrypt_data(bob_priv_key, alice_publ_key, data)
print(f'encrypted_data = {encrypted_data}')
except Exception as e:
print(f'encryption exception = {type(e).__name__}, e = {e}')
traceback.print_exc()
# bob sends encrypted_data to alice ...
# verify it is working, inject error
#alice_priv_key = alice_publ_key
#bob_publ_key = alice_publ_key
# alice decrypts the received encrypted_data
try:
decrypted_data = pku.decrypt_data(alice_priv_key, bob_publ_key, encrypted_data, decode='utf-8')
print(f'decrypted_data = {decrypted_data}')
except Exception as e:
print(f'decryption exception = {type(e).__name__}, e = {e}')
traceback.print_exc()
print('ready')
if __name__ == '__main__':
main()
Резюме
Пакет Python pyNaCl позволяет очень легко реализовать шифрование public_key . Шифрование с открытым ключом слабо подвержено атаке "человек посередине". Третья сторона может модифицировать открытые ключи.
Кроме того, всегда помните, что зашифрованные данные могут быть скопированы по пути. Это означает, что если третья сторона завладеет ключами, то все (!) данные, которые были скопированы третьей стороной (за длительный период времени), могут быть расшифрованы. Чтобы избежать этого, вы можете обновить ключи через некоторое время и удалить старые ключи из системы!
Ссылки / кредиты
C 431: Public-Key Encryption With Sodium (25 extra)
https://samsclass.info/141/proj/C431.htm
Public Key Encryption
https://www.geeksforgeeks.org/public-key-encryption
PyNaCl
https://pynacl.readthedocs.io/en/latest
Подробнее
Cryptography
Недавний
- Скрытие первичных ключей базы данных UUID вашего веб-приложения
- Don't Repeat Yourself (DRY) с Jinja2
- SQLAlchemy, PostgreSQL, максимальное количество строк для user
- Показать значения в динамических фильтрах SQLAlchemy
- Безопасная передача данных с помощью шифрования Public Key и pyNaCl
- rqlite: альтернатива dist с высокой степенью готовности и SQLite
Большинство просмотренных
- Используя Python pyOpenSSL для проверки SSL-сертификатов, загруженных с хоста
- Использование UUID вместо Integer Autoincrement Primary Keys с SQLAlchemy и MariaDb
- Подключение к службе на хосте Docker из контейнера Docker
- Использование PyInstaller и Cython для создания исполняемого файла Python
- SQLAlchemy: Использование Cascade Deletes для удаления связанных объектов
- Flask Удовлетворительный запрос API проверка параметров запроса с помощью схем Маршмэллоу