Welcome toVigges Developer Community-Open, Learning,Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
596 views
in Technique[技术] by (71.8m points)

How to use ssh-keygen ed25519 keys for encryption in Python?

I spent about 2 days trying to accomplish this and had googled information regarding packages such as cryptography, PyNaCl and paramiko.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Finally I found some example code sealing.py at https://gist.github.com/R-VdP/b7ac0106a4fd395ee1c37bfe6f552a36 with thanks to Ramses at GitHub https://github.com/R-VdP.

You can read your id_ed25519 and id_ed25519.pub files that were generated with ssh-keygen with open, strip out the keys as text, then use extract_curve_private_key and extract_curve_public_key from sealing.py. These keys can be used for constructing Box classes from PyNaCl. Hence you can accomplish symmetric, asymmetric and signing operations using Ramses' code and PyNaCl.

Hope this helps some people spend less time googling than I did.

EDIT: Have added wrapper class and client code after suggestion from Kenster. The files in data/ are produced via ssh-keygen -t ed25519 with no passphrase. Note this is a thin class wrapper around Ramses' code whom deserves credit.

from nacl.public import Box
from nacl.utils import random as nacl_random
from nacl.secret import SecretBox
from nacl.exceptions import BadSignatureError

from base64 import b64decode
from nacl.encoding import RawEncoder
from nacl.signing  import SigningKey, VerifyKey

class C25519:
    # Adapted from https://gist.github.com/R-VdP/b7ac0106a4fd395ee1c37bfe6f552a36 sealing.py
    # Author: Ramses https://github.com/R-VdP
    __key_length = 32
    __private_key_signature = b'x00x00x00x40'
    __public_key_signature  = b'x00x00x00x20'

    @classmethod
    def __bytes_after(cls, signature, length, bytestr):
        start = bytestr.find(signature) + len(signature)
        return bytestr[start:start+length]

    @classmethod
    def __extract_signing_key(cls, private_data):
        openssh_bytes = b64decode(private_data)
        private_bytes = cls.__bytes_after(
            cls.__private_key_signature,
            cls.__key_length,
            openssh_bytes
        )
        signing_key = SigningKey(seed=private_bytes, encoder=RawEncoder)
        return signing_key

    @classmethod
    def __extract_verify_key(cls, public_data):
        openssh_bytes = b64decode(public_data)
        public_bytes = cls.__bytes_after(
            cls.__public_key_signature,
            cls.__key_length,
            openssh_bytes
        )
        verify_key = VerifyKey(key=public_bytes, encoder=RawEncoder)
        return verify_key

    @classmethod
    def __private_data_from_file(cls, file_name):
        with open(file_name, 'r') as file:
            contents = file.read()
        contents = contents.split('
')
        private_data = ''
        for line in contents:
            if 'PRIVATE KEY' in line:
                continue
            if not line:
                continue
            private_data += line
        return private_data

    @classmethod
    def __public_data_from_file(cls, file_name):
        with open(file_name, 'r') as file:
            contents = file.read()
        contents = contents.split(' ')
        # assert contents[0] == 'ssh-ed25519'
        public_data = contents[1].strip(' ')
        return public_data

    @classmethod
    def signingKey(cls, private_ed25519_file):
        private_data = cls.__private_data_from_file(private_ed25519_file)
        signing_key = cls.__extract_signing_key(private_data)
        return signing_key

    @classmethod
    def verifyKey(cls, public_ed25519_file):
        public_data = cls.__public_data_from_file(public_ed25519_file)
        verify_key = cls.__extract_verify_key(public_data)        
        return verify_key

    @classmethod
    def privateKey(cls, private_ed25519_file):
        signing_key = cls.signingKey(private_ed25519_file)
        return signing_key.to_curve25519_private_key()

    @classmethod
    def publicKey(cls, public_ed25519_file):
        verify_key = cls.verifyKey(public_ed25519_file)
        return verify_key.to_curve25519_public_key()

def asymmetric():
    # Alice and Bob exchange public keys
    public_key_alice = C25519.publicKey('data/id_ed25519_alice.pub')
    public_key_bob = C25519.publicKey('data/id_ed25519_bob.pub')
    private_key_alice = C25519.privateKey('data/id_ed25519_alice')
    private_key_bob = C25519.privateKey('data/id_ed25519_bob')

    bob_box = Box(private_key_bob, public_key_alice)
    message = 'Asymmetric secret message from Bob to Alice'
    message_bytes = message.encode('utf-8')
    encrypted = bob_box.encrypt(message_bytes)

    alice_box = Box(private_key_alice, public_key_bob)
    plaintext_bytes = alice_box.decrypt(encrypted)
    plaintext = plaintext_bytes.decode('utf-8')
    assert message == plaintext

def symmetric():
    # Alice and Bob exchange public keys
    public_key_alice = C25519.publicKey('data/id_ed25519_alice.pub')
    public_key_bob = C25519.publicKey('data/id_ed25519_bob.pub')
    private_key_alice = C25519.privateKey('data/id_ed25519_alice')
    private_key_bob = C25519.privateKey('data/id_ed25519_bob')

    symmetric_key_bytes = nacl_random(SecretBox.KEY_SIZE)
    bob_box_assymetric = Box(private_key_bob, public_key_alice)
    encrypted = bob_box_assymetric.encrypt(symmetric_key_bytes)

    alice_box_assymetric = Box(private_key_alice, public_key_bob)
    plaintext_bytes = alice_box_assymetric.decrypt(encrypted)
    assert symmetric_key_bytes == plaintext_bytes

    bob_box_symmetric = SecretBox(symmetric_key_bytes)
    message = 'Symmetric secret message from Bob to Alice'
    message_bytes = message.encode('utf-8')
    encrypted_message = bob_box_symmetric.encrypt(message_bytes)

    alice_box_symmetric = SecretBox(symmetric_key_bytes)
    plaintext_bytes = alice_box_symmetric.decrypt(encrypted_message)
    plaintext = plaintext_bytes.decode('utf-8')
    assert plaintext == message

def signing():
    signingKey = C25519.signingKey('data/id_ed25519_bob')
    message = 'Document signed with private key'
    message_bytes = message.encode('utf-8')
    signed = signingKey.sign(message_bytes)
    signed_message = signed.message
    signed_signature = signed.signature
    
    verifyKey = C25519.verifyKey('data/id_ed25519_bob.pub')
    verifyKey.verify(signed_message, signed_signature)

    forged = signed[:-1] + bytes([int(signed[-1]) ^ 1])
    try:
        verifyKey.verify(forged)
    except BadSignatureError as exc:
        print(exc.args)

if __name__ == "__main__":
    asymmetric()
    symmetric()
    signing()

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to Vigges Developer Community for programmer and developer-Open, Learning and Share
...