angle-uparrow-clockwisearrow-counterclockwisearrow-down-uparrow-leftatcalendarcard-listchatcheckenvelopefolderhouseinfo-circlepencilpeoplepersonperson-fillperson-plusphoneplusquestion-circlesearchtagtrashx

Безопасная передача данных с помощью шифрования Public Key и pyNaCl

pyNaCl позволяет легко реализовать безопасную передачу данных.

2 декабря 2023 Обновленный 2 декабря 2023
post main image
https://www.pexels.com/@reezky11

Это небольшая заметка о безопасной передаче данных между двумя людьми. Для этого мы используем пакет 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

Оставить комментарий

Комментируйте анонимно или войдите в систему, чтобы прокомментировать.

Комментарии

Оставьте ответ

Ответьте анонимно или войдите в систему, чтобы ответить.