Signature#

Join provides digital signature generation and verification using public-key cryptography. It wraps OpenSSL’s signature functions with a stream-based interface, supporting multiple digest algorithms for signing and verifying data integrity and authenticity.

Signature is designed to be:

  • secure — uses OpenSSL for cryptographic operations
  • flexible — supports multiple digest algorithms (SHA-256, SHA-512, etc.)
  • stream-based — integrates with C++ iostream for incremental signing
  • convenient — static methods for one-shot operations

Key concepts#

ConceptDescription
Digital signatureCryptographic proof of data authenticity
Private keyUsed to sign data (must be kept secret)
Public keyUsed to verify signatures (can be shared)
Digest algorithmHash function used before signing (SHA-256, etc.)
VerificationConfirms signature matches data and public key

Supported algorithms#

The signature class supports various digest algorithms:

AlgorithmSecuritySpeedSignature size
SHA-1WeakFast160 bits
SHA-224GoodMedium224 bits
SHA-256StrongMedium256 bits
SHA-384StrongSlower384 bits
SHA-512StrongSlower512 bits
SHA3-256StrongMedium256 bits
SHA3-512StrongSlower512 bits

Recommendation: Use SHA-256 or higher for new applications.


Basic usage#

Signing data#

#include <join/signature.hpp>

using join::Digest;
using join::Signature;
using join::BytesArray;

// Create signature instance
Signature sig(Digest::Sha256);

// Add data to sign
sig << "Message to sign";

// Generate signature with private key
BytesArray signature = sig.sign("private_key.pem");

if (!signature.empty())
{
    // Signature generated successfully
    saveSignature(signature);
}

Verifying signature#

Signature sig(Digest::Sha256);

// Add data to verify
sig << "Message to sign";

// Verify signature with public key
bool valid = sig.verify(signature, "public_key.pem");
if (valid)
{
    // Signature is valid
    std::cout << "Signature verified!\n";
}
else
{
    // Signature invalid or verification failed
    std::cout << "Invalid signature!\n";
}

Stream-based operations#

Incremental signing#

Signature sig(Digest::Sha256);

// Add data incrementally
sig << "Part 1\n";
sig << "Part 2\n";
sig << "Part 3\n";

// Generate signature over all data
BytesArray signature = sig.sign("private.pem");

Signing large data#

Signature sig(Digest::Sha512);

// Read and sign file incrementally
std::ifstream file("large_file.dat", std::ios::binary);
char buffer[4096];

while (file.read(buffer, sizeof(buffer)))
{
    sig.write(buffer, file.gcount());
}

BytesArray signature = sig.sign("private.pem");

Binary data#

Signature sig(Digest::Sha256);

// Sign binary data
std::vector<uint8_t> data = {0x01, 0x02, 0x03, 0x04};
sig.write(reinterpret_cast<const char*>(data.data()), data.size());

BytesArray signature = sig.sign("private.pem");

Static methods#

Convenient one-shot signing and verification.

Sign data directly#

// Sign string
std::string message = "Important message";
BytesArray sig1 = Signature::sign(
    message,
    "private.pem",
    Digest::Sha256
);

// Sign binary data
BytesArray data = {0x01, 0x02, 0x03};
BytesArray sig2 = Signature::sign(
    data,
    "private.pem",
    Digest::Sha256
);

// Sign buffer
const char* buffer = "data";
BytesArray sig3 = Signature::sign(
    buffer,
    strlen(buffer),
    "private.pem",
    Digest::Sha256
);

Verify data directly#

// Verify string
bool valid1 = Signature::verify(
    message,
    signature,
    "public.pem",
    Digest::Sha256
);

// Verify binary data
bool valid2 = Signature::verify(
    data,
    signature,
    "public.pem",
    Digest::Sha256
);

// Verify buffer
bool valid3 = Signature::verify(
    buffer,
    strlen(buffer),
    signature,
    "public.pem",
    Digest::Sha256
);

Key management#

Key file formats#

Signature supports PEM-encoded keys:

// RSA private key
"-----BEGIN RSA PRIVATE KEY-----
...
-----END RSA PRIVATE KEY-----"

// RSA public key
"-----BEGIN PUBLIC KEY-----
...
-----END PUBLIC KEY-----"

// PKCS#8 private key
"-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----"

Loading keys#

// Keys are loaded from file paths
BytesArray sig = Signature::sign(
    data,
    "/path/to/private_key.pem",  // Private key file
    Digest::Sha256
);

bool valid = Signature::verify(
    data,
    signature,
    "/path/to/public_key.pem",   // Public key file
    Digest::Sha256
);

Key types#

Supports various key types:

  • RSA keys (most common)
  • DSA keys
  • ECDSA keys
  • Ed25519 keys (EdDSA)

Error handling#

Checking results#

Signature sig(Digest::Sha256);
sig << data;

BytesArray signature = sig.sign("private.pem");

if (signature.empty())
{
    // Signing failed
    std::cerr << "Sign error: " << join::lastError.message() << "\n";
}

Verification errors#

bool valid = sig.verify(signature, "public.pem");

if (!valid)
{
    std::error_code ec = join::lastError;

    if (ec == DigestErrc::InvalidSignature)
    {
        // Signature doesn't match
        std::cerr << "Signature verification failed\n";
    }
    else if (ec == DigestErrc::InvalidAlgorithm)
    {
        // Algorithm mismatch or error
        std::cerr << "Algorithm error\n";
    }
}

Common errors#

ErrorCause
InvalidSignatureSignature doesn’t match data
InvalidAlgorithmAlgorithm not supported
InvalidKeyIncorrect key format

Use cases#

API request signing#

class ApiClient
{
    std::string privateKey;

public:
    std::string signRequest(const std::string& method, const std::string& path, const std::string& body)
    {
        // Create canonical request
        std::string canonical = method + "\n" + path + "\n" + body;

        // Sign request
        BytesArray sig = Signature::sign(
            canonical,
            privateKey,
            Digest::Sha256
        );

        // Return base64-encoded signature
        return base64Encode(sig);
    }
};

Document signing#

class DocumentSigner
{
public:
    bool signDocument(const std::string& docPath, const std::string& keyPath)
    {
        // Read document
        std::ifstream doc(docPath, std::ios::binary);

        // Create signature
        Signature sig(Digest::Sha256);
        sig << doc.rdbuf();

        // Generate signature
        BytesArray signature = sig.sign(keyPath);

        if (signature.empty())
        {
            return false;
        }

        // Save signature alongside document
        std::ofstream sigFile(docPath + ".sig", std::ios::binary);
        sigFile.write(
            reinterpret_cast<const char*>(signature.data()),
            signature.size()
        );

        return true;
    }
};

License verification#

class LicenseManager
{
public:
    bool verifyLicense(const std::string& licenseData, const BytesArray& signature)
    {
        // Verify with vendor's public key
        bool valid = Signature::verify(
            licenseData,
            signature,
            "vendor_public.pem",
            Digest::Sha256
        );

        if (!valid)
        {
            return false;
        }

        // Parse license data
        return parseLicense(licenseData);
    }
};

Software update verification#

class UpdateManager
{
public:
    bool verifyUpdate(const std::string& updateFile)
    {
        // Read update file
        std::ifstream update(updateFile, std::ios::binary);
        Signature sig(Digest::Sha512);
        sig << update.rdbuf();

        // Read signature file
        BytesArray signature = readSignatureFile(updateFile + ".sig");

        // Verify with publisher's public key
        return sig.verify(signature, "publisher_public.pem");
    }
};

Security considerations#

Private key protection#

// Good: Restrict key file permissions
// chmod 600 private_key.pem

// Good: Store keys securely
const std::string keyPath = getSecureKeyPath();
BytesArray sig = Signature::sign(data, keyPath, Digest::Sha256);

// Bad: Hardcoded keys in source code
const std::string key = "-----BEGIN PRIVATE KEY-----...";

Algorithm selection#

// Good: Use strong algorithms
Signature sig(Digest::Sha256);  // Recommended
Signature sig(Digest::Sha512);  // More secure

// Avoid: Weak algorithms
Signature sig(Digest::Sha1);    // Deprecated
Signature sig(Digest::Md5);     // Broken

Signature verification#

// Good: Always verify return value
bool valid = sig.verify(signature, pubKey);
if (!valid)
{
    // Reject data
    return false;
}

// Bad: Ignoring verification result
sig.verify(signature, pubKey);
// Proceeding without checking!

Key size recommendations#

Key typeMinimum sizeRecommended size
RSA2048 bits3072-4096 bits
ECDSA256 bits384-521 bits
Ed25519256 bits256 bits (fixed)

Best practices#

Use appropriate algorithms#

// Modern applications
Signature sig(Digest::Sha256);    // Good balance

// High security requirements
Signature sig(Digest::Sha512);

Verify signatures immediately#

// Good: Verify before processing
bool valid = Signature::verify(data, sig, pubKey, Digest::Sha256);
if (valid)
{
    processData(data);
}

// Bad: Process then verify
processData(data);
if (!Signature::verify(data, sig, pubKey, Digest::Sha256))
{
    // Too late!
}

Include context in signatures#

// Good: Sign data with context
std::string toSign =
    "v1" + "\n" +
    timestamp + "\n" +
    userId + "\n" +
    actualData;

BytesArray sig = Signature::sign(toSign, key, Digest::Sha256);

// Prevents signature reuse across contexts

Handle errors properly#

BytesArray signature = Signature::sign(data, key, Digest::Sha256);

if (signature.empty())
{
    // Log error with context
    std::cerr << "Failed to sign data: " << join::lastError.message() << "\n";
    return false;
}

Separate key storage#

// Good: Keys in separate secure location
class SecureConfig
{
    std::string getPrivateKeyPath()
    {
        return "/etc/secure/keys/private.pem";
    }
};

// Bad: Keys with application code
// ./app/private.pem

Summary#

FeatureSupported
RSA signatures
DSA signatures
ECDSA signatures
Multiple digest algorithms
Stream-based signing
Static one-shot methods
PEM key format
Incremental data signing
Binary data support
Error handling