2

我花了大约 2 天的时间尝试完成这项工作,并在 Google 上搜索了有关密码学、PyNaCl 和 paramiko 等软件包的信息。

4

1 回答 1

1

最后,感谢 GitHub https://github.com/R-VdP的Ramses,我在https://gist.github.com/R-VdP/b7ac0106a4fd395ee1c37bfe6f552a36找到了一些示例代码seal.py。

您可以阅读使用 ssh-keygen 生成的 id_ed25519 和 id_ed25519.pub 文件,将密钥作为文本剥离,然后使用seal.py 中的extract_curve_private_keyextract_curve_public_key。这些键可用于从 PyNaCl 构造 Box 类。因此,您可以使用 Ramses 的代码和 PyNaCl 完成对称、非对称和签名操作。

希望这可以帮助一些人比我花更少的时间在谷歌上搜索。

编辑:根据 Kenster 的建议添加了包装类和客户端代码。data/ 中的文件是通过ssh-keygen -t ed25519 生成的,没有密码。请注意,这是一个围绕 Ramses 代码的薄类包装器,值得称赞。

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'\x00\x00\x00\x40'
    __public_key_signature  = b'\x00\x00\x00\x20'

    @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('\n')
        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()
于 2021-01-12T13:10:32.243 回答