Juce RSA implementation

I am setting up a licensing backend with Python and thought I might add my experiences here. My first attempts failed unfortunately; Spirux’ Python3 package didn’t work on Mac Sonoma. I might have gotten matkat’s RSA juce extension to work, but then the issue was encrypting something in Python with a private key which isn’t supported by the current standard cryptography package because apparently you aren’t supposed to do that :roll_eyes:

Long story short I ended up implementing the “applyToValue” function in Python:

def apply_to_value(value, key_part1, key_part2):
    result = 0
    part1 = int(key_part1, 16)
    part2 = int(key_part2, 16)

    if part1 == 0 or part2 == 0 or value <= 0:
        return result

    while value != 0:
        result = result * part2
        div, mod = divmod(value, part2)
        value = div
        result = result + pow(mod, part1, part2)

    return result

# Will only work for JUCE RSA cryptography
def generate_activation_token(xml_string, private_key_pem):
    xml_numeric = int.from_bytes((xml_string[::-1]).encode('utf-8'), byteorder='big')
    private_key_split = private_key_pem.split(',')
    key_part1 = private_key_split[0]
    key_part2 = private_key_split[1]
    signature = apply_to_value(xml_numeric, key_part1, key_part2)
    return format(signature, 'x') # convert signature (which is int) to signature_string of base 16

# Usage
some_string = "Lorem ipsum quia dolor sit amet"
private_key = "1a9b694004ff3b81,85090e41987a14c9" # Throwaway key (64 bit in this case)
encrypted_string =  generate_activation_token(some_string, private_key)
print(encrypted_string)

In C++, this could the encrypted_string could then be decrypted like this:

        juce::RSAKey publicKey("5,85090e41987a14c9");
        juce::String encryptedString = "13cc53ec14c480ef8251bea55d7c1d1393cf88b9ac960b8258c772fc0184a57"; // Output from Python
        
        // The following code was more or less copied from decryptXML in juce:OnlineUnlockStatus
        juce::BigInteger val;
        val.parseString (encryptedString, 16);

        juce::RSAKey key (publicKey);
        jassert (key.isValid());

        std::unique_ptr<juce::XmlElement> xml;

        if (! val.isZero())
        {
            key.applyToValue (val);
            
            auto mb = val.toMemoryBlock();
            
            if (juce::CharPointer_UTF8::isValidString (static_cast<const char*> (mb.getData()), (int) mb.getSize()))
            {
                std::cout << "Success:" << mb.toString() << std::endl;
            } else {
                std::cout << "Failure" << std::endl;}
        }

I have also found success with using the output directly in

KeyFileUtils::getDataFromKeyFile (KeyFileUtils::getXmlFromKeyFile (licenseTokenFromDisk, publicKey));

assuming of course the encryptedString is a single-line XML key file.

But why do it then anyways?
Private keys are for signing, public for encryption.
Making it „work“ will remove a lot of the security you get with RSA.

Don’t want to sound rude with that. Cryptography is such an important matter which is quite easy to get wrong.

I guess renaming the variable to signature and not encrypted would make things more clear

That seems sensible - I guess what I am attempting is an edge case then. I am encrypting XML files on my licensing server that contain the machine ID. That way, when the plugin decrypts, it knows that the device it is on is sanctioned by the server. It is inspired by this reply from another post.

This approach doesn’t seem so outlandish to me and it is working quite well. I may be needing be the signature functionality you are referring to - my knowledge on the matter is limited, but I think I have read about it elsewhere as well. AFAIK this specific functionality isn’t supported in JUCE, though I would be happy to learn otherwise.

Sounds like your server sends back a message whose signature the plug-in (and everyone else, which is okay) can verify. So it can trust that it came from your server. So all good :+1: :slight_smile:

1 Like

AFAIK this is quite possible. Just be aware that the public key can be deduced from the private key. So whoever has the private key has full control.

You can encrypt with either key, and you need the other to decrypt.

Anything in your plugin binary has to be seen as compromised, because the attacker can search the binary for blobs which look like a key and probe them.

So you have the private key on your server and the public on your plugin. That way the plugin can decrypt the license and verify, if it matches the machine ID.

You must not encrypt on your plugin, because that can always be forged.

signing and encryption are different things. If you sign something, you only verify the authenticity, but it is still there as plain text. The private key is used to create a hash, which can be verified if you have the public key and the hash. You could use that as well to verify the license file, but I would rather encrypt it. That way it is harder for the attacker.

1 Like

Thanks, folks! So it was actually signing that I was looking for. I checked and as one would expect, this is supported by the Cryptography Python package, but I think one would need additional dependencies to verify the signature on the plugin side. I think I’ll stick to my workaround, since it seems like it will be able to do the job.

Here’s my implementation of the applyToValue function in TypeScript (Node.js, target es2020):

const applyJuceRSAKey = (value: bigint, key: string) => {
  const [part1 = '', part2 = ''] = key.split(',');

  const keyPart1 = BigInt('0x' + part1);
  const keyPart2 = BigInt('0x' + part2);

  let result = 0n;

  while (value !== 0n) {
    result *= keyPart2;

    const remainder = value % keyPart2;
    value /= keyPart2;

    result += modPow(remainder, keyPart1, keyPart2);
  }

  return result;
};

const modPow = (base: bigint, exp: bigint, mod: bigint) => {
  let result = 1n;
  base = base % mod;

  while (exp > 0n) {
    if (exp % 2n === 1n) {
      result = (result * base) % mod;
    }
    exp = exp >> 1n;
    base = (base * base) % mod;
  }

  return result;
};

const publicKey = 'PUBLIC_KEY_PART1,PUBLIC_KEY_PART2';
const privateKey = 'PRIVATE_KEY_PART1,PRIVATE_KEY_PART2';
const message = 'Hello, world!';
const value = BigInt('0x' + Buffer.from(message).reverse().toString('hex'));
const encrypted = applyJuceRSAKey(value, privateKey);
const decrypted = applyJuceRSAKey(encrypted, publicKey);
console.log(Buffer.from(decrypted.toString(16), 'hex').reverse().toString()); // Hello, world!

I use it with Route Handlers in Next.js like this:

export const POST = async (req: NextRequest) => {
//・・・
  const result = applyJuceRSAKey(
    BigInt('0x' + Buffer.from(xmlString).reverse().toString('hex')),
    privateKey,
  );

  return new NextResponse(
    `<MESSAGE message="Thanks for registering our product!"><KEY>#${result.toString(
      16,
    )}</KEY></MESSAGE>\0`,
  );
}
1 Like