Source code for synapse.lib.crypto.rsa

import hashlib

import cryptography.hazmat.primitives.serialization as c_ser
import cryptography.hazmat.primitives.asymmetric.rsa as c_rsa

import synapse.exc as s_exc
import synapse.common as s_common
import synapse.lib.msgpack as s_msgpack
import synapse.lib.crypto.utils as s_crypto

from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.backends import default_backend

[docs] class PriKey: ''' A helper class for using RSA private keys. Signing methods use RSA-PSS and MFG1 with sha256 hashing. ''' def __init__(self, priv): self.priv = priv # type: c_rsa.RSAPrivateKey self.publ = self.public()
[docs] def iden(self) -> str: ''' Return a SHA256 hash for the public key (to be used as a GUID). Returns: str: The SHA256 hash of the public key bytes. ''' return self.publ.iden()
[docs] def sign(self, byts, padding='pss', hashalgo='sha256', saltlen=None): ''' Compute the RSA signature for the given bytestream. Args: byts (bytes): The bytes to sign. padding (str): The padding scheme, "pss" (default) or "pkcs1v15". hashalgo (str): The hash algorithm name (sha256, sha384, or sha512). saltlen: The PSS salt length in bytes, or None for the maximum length. Returns: bytes: The RSA signature bytes. ''' hashobj = s_crypto.getHashByName(hashalgo) pad = s_crypto.getPaddingByName(padding, hashobj, saltlen=saltlen) return self.priv.sign(byts, pad, hashobj)
[docs] def signitem(self, item) -> bytes: ''' Compute the RSA signature for the given python primitive. Args: item: The item to sign. This will be flattened and msgpacked prior to signing. Returns: bytes: The RSA Signature bytes. ''' byts = s_msgpack.en(s_common.flatten(item)) return self.sign(byts)
[docs] def public(self): ''' Get the PubKey which corresponds to the RSA PriKey. Returns: PubKey: A new PubKey object whose key corresponds to the private key. ''' return PubKey(self.priv.public_key())
[docs] @staticmethod def generate(bits=2048): ''' Generate a new RSA PriKey instance. Args: bits (int): The size of the RSA key in bits. Returns: PriKey: A new PriKey instance. ''' return PriKey(c_rsa.generate_private_key( public_exponent=65537, key_size=bits, backend=default_backend()))
[docs] def dump(self, fmt='der'): ''' Get the private key bytes in PKCS8 format. Args: fmt (str): The encoding format, "der" (default) or "pem". Returns: bytes: The encoded PKCS8 private key. ''' return self.priv.private_bytes( encoding=s_crypto.getEncodingByName(fmt), format=c_ser.PrivateFormat.PKCS8, encryption_algorithm=c_ser.NoEncryption())
[docs] @staticmethod def load(byts, fmt='der'): ''' Create a PriKey instance from PKCS8 encoded bytes. Args: byts (bytes): Bytes to load. fmt (str): The encoding format, "der" (default) or "pem". Returns: PriKey: A new PriKey instance. ''' s_crypto.getEncodingByName(fmt) if fmt.lower() == 'pem': priv = c_ser.load_pem_private_key(byts, password=None, backend=default_backend()) else: priv = c_ser.load_der_private_key(byts, password=None, backend=default_backend()) return PriKey(priv)
[docs] def loadKey(byts): ''' Load a single RSA public or private key, auto-detecting the PEM vs DER encoding and whether the key is public or private. Args: byts (bytes): The DER or PEM encoded RSA key bytes. Returns: PriKey or PubKey: The loaded key wrapper. ''' isprivate, key = s_crypto.loadKey(byts) if isprivate: if not isinstance(key, c_rsa.RSAPrivateKey): raise s_exc.BadArg(mesg='Key is not an RSA private key.') return PriKey(key) if not isinstance(key, c_rsa.RSAPublicKey): raise s_exc.BadArg(mesg='Key is not an RSA public key.') return PubKey(key)
[docs] class PubKey: ''' A helper class for using RSA public keys. ''' def __init__(self, publ): self.publ = publ # type: c_rsa.RSAPublicKey
[docs] def dump(self, fmt='der'): ''' Get the public key bytes in SubjectPublicKeyInfo format. Args: fmt (str): The encoding format, "der" (default) or "pem". Returns: bytes: The encoded SubjectPublicKeyInfo public key. ''' return self.publ.public_bytes( encoding=s_crypto.getEncodingByName(fmt), format=c_ser.PublicFormat.SubjectPublicKeyInfo)
[docs] def verify(self, byts, sign, padding='pss', hashalgo='sha256', saltlen=None): ''' Verify the signature for the given bytes using the RSA public key. Args: byts (bytes): The data bytes. sign (bytes): The signature bytes. padding (str): The padding scheme, "pss" (default) or "pkcs1v15". hashalgo (str): The hash algorithm name (sha256, sha384, or sha512). saltlen: The PSS salt length in bytes, or None for the maximum length. Returns: bool: True if the data was verified, False otherwise. ''' hashobj = s_crypto.getHashByName(hashalgo) pad = s_crypto.getPaddingByName(padding, hashobj, saltlen=saltlen) try: self.publ.verify(sign, byts, pad, hashobj) return True except InvalidSignature: return False
[docs] def verifyitem(self, item, sign): ''' Verify the signature for the given item with the RSA public key. Args: item: The Python primitive to verify. sign (bytes): The signature bytes. Returns: bool: True if the data was verified, False otherwise. ''' byts = s_msgpack.en(s_common.flatten(item)) return self.verify(byts, sign)
[docs] def iden(self): ''' Return a SHA256 hash for the public key (to be used as a GUID). Returns: str: The SHA256 hash of the public key bytes. ''' return hashlib.sha256(self.dump()).hexdigest()
[docs] @staticmethod def load(byts, fmt='der'): ''' Create a PubKey instance from SubjectPublicKeyInfo encoded bytes. Args: byts (bytes): Bytes to load. fmt (str): The encoding format, "der" (default) or "pem". Returns: PubKey: A new PubKey instance. ''' s_crypto.getEncodingByName(fmt) if fmt.lower() == 'pem': publ = c_ser.load_pem_public_key(byts, backend=default_backend()) else: publ = c_ser.load_der_public_key(byts, backend=default_backend()) return PubKey(publ)