Source code for stringphone.crypto

"""
Symmetric and asymmetric cryptography- and signing-related classes and methods.
"""
import hashlib

import nacl.encoding
import nacl.exceptions
import nacl.secret
import nacl.signing
import nacl.public
import nacl.utils
import six

from .exceptions import BadSignatureError

PARTICIPANT_ID_LENGTH = 16


def _get_id_from_key(public_key):
    """
    Derive the participant's ID from the public key.

    The ID is the first %s bytes of the SHA256 hash of the participant's public
    key.
    """ % PARTICIPANT_ID_LENGTH
    return hashlib.sha256(public_key).digest()[:PARTICIPANT_ID_LENGTH]


[docs]def generate_topic_key(): """ Generate and return a new topic key. The generated key is cryptographically secure. :return: A cryptographically secure random topic key. :rtype: bytes """ return nacl.utils.random(nacl.secret.SecretBox.KEY_SIZE)
[docs]def generate_signing_key_seed(): """ Generate and return a new signing key seed. The generated seed is cryptographically secure. :return: A cryptographically secure random signing key seed. :rtype: bytes """ return nacl.signing.SigningKey.generate().encode()
[docs]class AsymmetricCrypto: def __init__(self): self._private_key = nacl.public.PrivateKey.generate()
[docs] def encrypt(self, plaintext, public_key): """ Asymmetrically encrypt and sign the plaintext to the given public key. :param bytes plaintext: The plaintext to encrypt. :param bytes public_key: The recipient's public encryption key. :return: The ciphertext. :rtype: bytes """ box = nacl.public.Box( self._private_key, nacl.public.PublicKey(public_key) ) nonce = nacl.utils.random(nacl.public.Box.NONCE_SIZE) ciphertext = box.encrypt(plaintext, nonce) return ciphertext
[docs] def decrypt(self, ciphertext, public_key): """ Asymmetrically decrypt and verify the ciphertext. :param bytes plaintext: The ciphertext to decrypt. :param bytes public_key: The sender's public encryption key. :return: The plaintext. :rtype: bytes """ box = nacl.public.Box( self._private_key, nacl.public.PublicKey(public_key) ) return box.decrypt(ciphertext)
@property def public_key(self): """ The public encryption key of the AsymmetricCrypto object. :rtype: bytes """ return self._private_key.public_key.encode()
[docs]class SymmetricCrypto: def __init__(self, key): """ Instantiate a new SymmetricCrypto object. SymmetricCrypto performs symmetric encryption and decryption of byte arrays. :param bytes key: The key to use for encryption and decryption. Use `generate_topic_key` to generate this. """ self._box = nacl.secret.SecretBox(key)
[docs] def encrypt(self, plaintext): """ Encrypt the plaintext. :param bytes plaintext: The plaintext to encrypt. :return: The ciphertext. :rtype: bytes """ nonce = nacl.utils.random(nacl.secret.SecretBox.NONCE_SIZE) return six.binary_type(self._box.encrypt(plaintext, nonce))
[docs] def decrypt(self, ciphertext): """ Decrypt the ciphertext. :param bytes ciphertext: The ciphertext to decrypt. :return: The ciphertext. :rtype: bytes """ return self._box.decrypt(ciphertext)
[docs]class Signer: def __init__(self, private_key): """ Instantiate a new Signer. :param bytes private_key: The private signing key to use. Use `generate_signing_key_seed` to generate this. """ self._signer = nacl.signing.SigningKey(private_key)
[docs] def sign(self, plaintext): """ Sign the given plaintext. :param bytes plaintext: The plaintext to sign. :return: The signed plaintext. :rtype: bytes """ signed = self._signer.sign(plaintext) return six.binary_type(signed)
@property def public_key(self): """ The public key of this Signer object. :rtype: bytes """ return self._signer.verify_key.encode()
[docs]class Verifier: def __init__(self, public_key): """ Instantiate a new Verifier. :param bytes public_key: The public signing key to use. """ self._verifier = nacl.signing.VerifyKey(public_key)
[docs] def verify(self, signed): """ Verify the signature of a signed bytestring. :return: The plaintext that was signed with a valid signature. :rtype: bytes :raises BadSignatureError: The signature was invalid. """ try: plaintext = self._verifier.verify(signed) except nacl.exceptions.BadSignatureError as e: raise BadSignatureError(str(e)) return plaintext