Non-intrusive file-based licensing

Hello! I was wondering how you implement the kind of non-intrusive licensing used by developers like ValhallaDSP where you just download a license file alongside the plugin, and as long as the plugin finds it, you’re good to go.

I’m pretty familiar with basic cryptographic concepts, but I’m not entirely sure how to incorporate that kind of thing into a plugin. Any suggestions greatly appreciated, thanks!

I think the best approach is to provide a text field that the user can paste their license (which is emailed to them or displayed on their web registration account, or whatever) into, and then the plugin just stashes that license in local data directory, somewhere - i.e. ~/Library/Application\ Support/MySuperPlugin/license.dat.

Edit: something like this:

#include <iostream>
#include <fstream>
#include <string>
#include <filesystem>
#include <stdexcept>

class LicenseManager {
public:
    LicenseManager() : encryptionKey_("MySuperSecretKey123") {
        // Initialize license file path
        std::string homeDir = std::getenv("HOME") ? std::getenv("HOME") : "";
        if (homeDir.empty()) {
            throw std::runtime_error("Cannot determine home directory");
        }
        licenseFilePath_ = std::filesystem::path(homeDir) / "Library/Application Support/MySuperPlugin/license.dat";
        std::filesystem::create_directories(licenseFilePath_.parent_path()); // Ensure directory exists
    }

    // Store license key from text field input
    void saveLicense(const std::string& licenseKey) {
        try {
            std::string encryptedLicense = xorEncryptDecrypt(licenseKey);
            std::ofstream outFile(licenseFilePath_, std::ios::binary);
            if (!outFile) {
                throw std::runtime_error("Failed to open license file for writing");
            }
            outFile.write(encryptedLicense.data(), encryptedLicense.size());
            outFile.close();
            std::cout << "License saved successfully to " << licenseFilePath_ << std::endl;
        } catch (const std::exception& e) {
            std::cerr << "Error saving license: " << e.what() << std::endl;
            throw;
        }
    }

    // Load and decrypt license key
    std::string loadLicense() const {
        try {
            std::ifstream inFile(licenseFilePath_, std::ios::binary);
            if (!inFile) {
                throw std::runtime_error("Failed to open license file for reading");
            }
            std::string encryptedLicense((std::istreambuf_iterator<char>(inFile)),
                                         std::istreambuf_iterator<char>());
            inFile.close();
            return xorEncryptDecrypt(encryptedLicense);
        } catch (const std::exception& e) {
            std::cerr << "Error loading license: " << e.what() << std::endl;
            return "";
        }
    }

    // Simulate getting license from GUI text field (replace with actual GUI code)
    static std::string getLicenseFromTextField() {
        std::string license;
        std::cout << "Enter license key: ";
        std::getline(std::cin, license);
        return license;
    }

private:
    std::string encryptionKey_;
    std::filesystem::path licenseFilePath_;

    // Simple XOR encryption/decryption (NOT SECURE for production)
    std::string xorEncryptDecrypt(const std::string& input) const {
        std::string output = input;
        for (size_t i = 0; i < input.size(); ++i) {
            output[i] = input[i] ^ encryptionKey_[i % encryptionKey_.size()];
        }
        return output;
    }
};

// Example usage
int main() {
    try {
        LicenseManager licenseMgr;

        // Simulate getting license from text field
        std::string licenseKey = LicenseManager::getLicenseFromTextField();

        // Save the license
        licenseMgr.saveLicense(licenseKey);

        // Load and verify the license
        std::string loadedLicense = licenseMgr.loadLicense();
        if (!loadedLicense.empty()) {
            std::cout << "Loaded license key: " << loadedLicense << std::endl;
        } else {
            std::cout << "No valid license found." << std::endl;
        }
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        return 1;
    }

    return 0;
}

If you’re concerned about security you should use PACE’s tools. There is no alternative “secure” algorithm you could swap in here, as part of an offline, file-based system, that will resist being being cracked.

There are quite a few forum threads about this:

1 Like

I concur with t0m - my example (disclaimer: which was ML-generated, by the way!) was only meant to highlight how it could be done - ymmv, buyer beware, objects in mirror are closer than they appear ..

Remember that you can’t access ~/Library/Application Support from a sandboxed environment.

Also the best advice I’ve gotten about encryption algorithms is to never roll your own unless cryptography is your thing (and definitely don’t let ChatGPT roll it for you either!). In our software we use file-based licensing, secured with an RSA signature scheme.

1 Like

I’d love to know how you set that up! That’s almost exactly what I’m trying to implement myself. Thanks!

What we do on Moonbase is use asymmetric key encryption and ship the public key with the plugin. This way you can verify the integrity of the license file on the device while keeping the secret key actually secret on your server. Depending on the level of security you want, you likely want to tie the license file to the actual device using some fingerprinting technique, but this is where things get interesting. To do this smoothly, you need a back and forth to your server to exchange some authentication key for a license file for the specific device.

Anyways, the idea is pretty simple:

  1. Generate some metadata about the license, product and device being activated
  2. Sign or encrypt said metadata with your private (RSA for example) key
  3. In the plugin, verify the signature or decrypt the license and verify that all the metadata match the current instance

If you want to lean on standards for doing this, you can look into JWTs: JSON Web Tokens. This is a pretty solid standard for how to sign claims using key encryption, and has libraries for most languages.

Like mentioned in this thread, preventing cracking is hard, and while you can obfuscate the heck out of the verification process, it’s always a battle against pirates, and commercially speaking, we don’t see big enough drawbacks from piracy to warrant less user-friendly activation processes.

Going further, what we landed on way back is to offer full user accounts to customers, because it enables a whole bunch of things like:

  • Easy one-click activations (just open your website where they are already logged in)
  • Self-service license management (users want to be able to revoke devices from their licenses and move seats around)
  • Flexibility around how you activate plugins (offline or online)

We wrote a story on how on of our customers do their licensing flow a while back if you’re interested: Black Salt Audio Customer Story | Moonbase

4 Likes

A 50% drop in revenue when a crack becomes available is fairly common. This holds true for both small and large companies.

1 Like

What data leads you to make this conclusive statement?

I’m afraid this isn’t data that can be made public.

If you search on this forum you will find some stories. Otherwise JUCE has connections to most audio technology companies, and this is an interesting question that gets asked frequently. A significant impact on revenue is common enough that if you ask people you know in a handful of different companies, and they’re willing to divulge the information, I think it’s likely you will find an example. The impact is generally larger for products that are more popular.

1 Like

This is great info, thank you! I’m still at a stage where I’m aiming for a low-overhead offline licensing scheme, in-spite of its security flaws, but the system you guys use seems like a great balance.

On my end, I’ve built a system where you can perform RSA signatures by hashing the customer/product data, and then verifying it with the public key on the plugin. My problem is I’m using a single .txt file to contain both the customer data that will be re-hashed to verify the signature, and the signature itself. Ergo I’m running into issues just parsing the data properly.

Perhaps .xml would be a better format?

Yeah, the first version of our JUCE module we made way back just separated the different parts by a \n newline, and for the structured data used XML for easier parsing in C++/JUCE land. Totally reasonable, and you could also contain it all in XML with a separate element for the signature itself. The challenge is that you cannot sign the signature itself, since it would change, so you need to divide it up into two chunks anyways.

1 Like

Now that you mention it I’m almost certain the issue I was running into was because of either not parsing out the separator between the data and signature, or not separating them at all. XML it is. Thank you!

We use a plain text (XML) license file, that the user needs to download from our website. Rather than encrypt the license file, we chose to keep it readable, so that there is accountability (the users’s name and email address appear in it as XML elements). Then we sign the file using the RSA signature scheme. If any of the license file contents are altered (signature or any of the user info), it fails the signature verification step in our software, and does not authorize the demo.

Because JUCE’s RSAKey class doesn’t offer signatures, only encryption, we used a 3rd party library for this called Crypto++.

The license files are generated by a PHP script on our server.

So, even for a fairly simple scheme like this, there are a bunch of moving parts. If you’re new at this, I would not recommend adding all this extra work on top of the design and implementation of a good plugin. If you can, release a simplified version of your first product as a freebie, or if that doesn’t work for you, look for an off-the-shelf solution so you can focus on the creative aspects of development.

2 Likes

I see where you’re coming from. I was thinking that a ‘lite’ version of the plugin I have would be a good way to circumvent worrying about a license for now. That said, I can’t imagine the overhead for incorporating an offline .xml licensing system would be that bad, though in my conception, I’m going to be manually generating licenses for any customers.

It’s definitely not fun though!

I ended up making my own scheme. It took some time but it was definitely worth it even just for the lessons learnt. I decided to opt for an offline activation, with an XML-based scheme very similar the one used by Valhalla DSP. It seemed the best solution for the scale of my operation, at least for now.

As others mentioned, if you want everything to work automatically, there are a couple of moving parts that you have to connect: payment system, license creation and website.

When a customer buys a license, the payment processor sends a notification to some serverless computing service I set up with the code required to create the license. The created license file is then made available to the customer to download from their dashboard on my website. The license generating code is completely separated from the website.

The license is a simple unencrypted XML file that contains the metadata and the key needed to activate the plugin. If the activations fails, the plugin just bypasses the sound. I don’t keep track of activations and don’t limit the number of activations for a license.

This lecture has some good tips on how to avoid some trivial mistakes.

Some quick tips from the top of my head:

  • Don’t just use asymmetric encryption but add your own spice to it. Make it as confusing as possible.
  • Have separate binaries for the demo (publicly accessible) and full version (only accessible to customers who purchased) and strip the demo version from any license checking code.
  • Don’t have the most uncrackable license and then only check it in one place.
  • Don’t go crazy on it, you’re fighting a losing battle. Be ready to adapt instead.
3 Likes

That’s what I ended up going with myself also using Valhalla DSP as the model. It’s working pretty well now where I can store the selected filepath for the license in a userData.xml which is separate from the actual license.xml file. I still have to generate everything manually but I don’t predict getting more than a couple of customers anyways so it’s fine for now.

That said, for checking in multiple places I assume there’s some way of setting it so that it regularly (or maybe irregularly) verifies whether the license is still valid? Like putting it on a timer + actually putting checks in different points of the code.

I still have to generate everything manually but I don’t predict getting more than a couple of customers anyways so it’s fine for now.

Yeah in my case it was mostly for the user experience, I didn’t want customer to have to wait for me to manually upload a license. Also, why doing something manually in 5 minutes when you can spend 2 months automating it?! :nerd_face:

That said, for checking in multiple places I assume there’s some way of setting it so that it regularly (or maybe irregularly) verifies whether the license is still valid? Like putting it on a timer + actually putting checks in different points of the code.

Yeah exactly. I made it as confusing as I could and as often as I thought was enough without affecting the CPU usage in a noticeable way. As I said, it’s a lost battle anyway, so there is no reason to go insane about it.