Full signing.py

import os
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key
import base64
import hashlib


# Function to generate RSA key pair
def generate_rsa_key_pair():
    private_key_file = 'private_key.pem'
    public_key_file = 'public_key.pem'
    
    # Check if the key files exist
    if os.path.exists(private_key_file) and os.path.exists(public_key_file):
        # Read the existing keys from the text files
        with open(private_key_file, 'r') as private_file:
            private_key_pem = private_file.read()

        with open(public_key_file, 'r') as public_file:
            public_key_pem = public_file.read()

        print("bunq - using existing keypair")
    else:
        # Generate new RSA keys with 2048 bits as required by Bunq
        private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )
        public_key = private_key.public_key()

        # Serialize private key to PEM format (PKCS#8 as required by Bunq)
        private_key_pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        ).decode('utf-8')

        # Serialize public key to PEM format
        public_key_pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        ).decode('utf-8')

        # Save the keys to text files
        with open(private_key_file, 'w') as private_file:
            private_file.write(private_key_pem)

        with open(public_key_file, 'w') as public_file:
            public_file.write(public_key_pem)

        print("bunq - creating new keypair [KEEP THESE FILES SAFE]")

    return private_key_pem, public_key_pem


def load_private_key(private_key_pem):
    """Load a private key from PEM format."""
    return load_pem_private_key(private_key_pem.encode(), password=None, backend=default_backend())


def load_public_key(public_key_pem):
    """Load a public key from PEM format."""
    return serialization.load_pem_public_key(
        public_key_pem.encode(),
        backend=default_backend()
    )


def sign_data(data, private_key_pem):
    """Signs the given data with the provided private key using SHA256 and PKCS#1 v1.5 padding.
    
    Args:
        data (str): The data to sign (should be the JSON request body)
        private_key_pem (str): The private key in PEM format
    
    Returns:
        str: Base64 encoded signature
    """
    private_key = load_private_key(private_key_pem)
    
    # Ensure the data is encoded in UTF-8 exactly as it will be sent
    encoded_data = data.encode('utf-8')

    # Debug: Print exact bytes being signed
    print("\n[DEBUG] Signing Data Bytes:", encoded_data)
    print("[DEBUG] SHA256 Hash of Data:", hashlib.sha256(encoded_data).hexdigest())

    # Generate signature using SHA256 and PKCS#1 v1.5 padding as required by Bunq
    signature = private_key.sign(
        encoded_data,
        padding.PKCS1v15(),
        hashes.SHA256()
    )

    # Encode in Base64 (as required by Bunq API)
    encoded_signature = base64.b64encode(signature).decode('utf-8')

    # Debug: Print signature
    print("[DEBUG] Base64 Encoded Signature:", encoded_signature)

    return encoded_signature


def verify_response(response_body, signature, server_public_key_pem):
    """Verifies the server's response signature.
    
    Args:
        response_body (str): The response body as a string
        signature (str): The base64 encoded signature from X-Bunq-Server-Signature header
        server_public_key_pem (str): The server's public key in PEM format
    
    Returns:
        bool: True if signature is valid, False otherwise
    """
    try:
        # Load the server's public key
        public_key = load_public_key(server_public_key_pem)
        
        # Decode the base64 signature
        decoded_signature = base64.b64decode(signature)
        
        # Verify the signature
        public_key.verify(
            decoded_signature,
            response_body.encode('utf-8'),
            padding.PKCS1v15(),
            hashes.SHA256()
        )
        return True
    except Exception as e:
        print(f"[ERROR] Signature verification failed: {e}")
        return False

Last updated

Was this helpful?