In this article, we’ll build a Spring Boot REST API that securely generates and validates software license keys using RSA public/private key encryption.

This approach is ideal if you want to:

  • Create a licensing system for your desktop or enterprise applications
  • Control access based on hardware ID or user name
  • Implement a time-based license expiry (e.g., 6 months, 1 year)

We’ll walk through the complete implementation — from generating keys to building APIs that encrypt and decrypt license data.


Project Overview

We’ll create two API endpoints:

  1. /api/license/generate – Generates a signed license using the private key.
  2. /api/license/decrypt – Decrypts and verifies the license using the public key.

Folder Structure


src
 ├── main
 │   ├── java
 │   │   └── com.diagnotoutil.controller
 │   │       └── LicenseController.java
 │   └── resources
 │       ├── private_key.pem
 │       └── public_key.pem

Full Working Code

Here’s the full working example of the LicenseController:


package com.diagnotoutil.controller;

import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import javax.crypto.Cipher;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/license")
public class LicenseController {
  private final String privateKeyPEM;

  public LicenseController() {
    String key = "";
    try {
      key = loadKeyFromResource("private_key.pem");
    } catch (IOException e) {
      e.printStackTrace();
    }
    this.privateKeyPEM = key;
  }

  @GetMapping("/generate")
  public ResponseEntity<String> generateLicense(@RequestParam String name, @RequestParam String systemId,
      @RequestParam int extendedMonths) throws Exception {
    LocalDate futureDate = LocalDate.now(ZoneId.of("UTC")).plusMonths(extendedMonths);
    String expiry = futureDate.format(DateTimeFormatter.ISO_LOCAL_DATE);
    String data = name + "|" + systemId + "|" + expiry;

    PrivateKey privateKey = loadPrivateKey(privateKeyPEM);
    java.security.Signature signature = java.security.Signature.getInstance("SHA256withRSA");
    signature.initSign(privateKey);
    signature.update(data.getBytes(StandardCharsets.UTF_8));
    byte[] signedBytes = signature.sign();

    String json = String.format("{\"data\":\"%s\",\"signature\":\"%s\"}", data,
        Base64.getEncoder().encodeToString(signedBytes));

    return ResponseEntity.ok(Base64.getEncoder().encodeToString(json.getBytes(StandardCharsets.UTF_8)));
  }

  private PrivateKey loadPrivateKey(String pem) throws Exception {
    String key = pem.replace("-----BEGIN PRIVATE KEY-----", "")
        .replace("-----END PRIVATE KEY-----", "")
        .replaceAll("\\s", "");
    byte[] bytes = Base64.getDecoder().decode(key);
    PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);
    return KeyFactory.getInstance("RSA").generatePrivate(spec);
  }

  @PostMapping("/decrypt")
  public ResponseEntity<String> decryptLicense(@RequestBody String encryptedLicense) throws Exception {    String publicKeyPEM = loadKeyFromResource("public_key.pem");

    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.DECRYPT_MODE, loadPublicKey(publicKeyPEM));
    byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedLicense));
    String decryptedData = new String(decryptedBytes, StandardCharsets.UTF_8);

    return ResponseEntity.ok(decryptedData);
  }

  private PublicKey loadPublicKey(String pem) throws Exception {
    String key = pem.replace("-----BEGIN PUBLIC KEY-----", "")
        .replace("-----END PUBLIC KEY-----", "")
        .replaceAll("\\s", "");
    byte[] decoded = Base64.getDecoder().decode(key);
    X509EncodedKeySpec spec = new X509EncodedKeySpec(decoded);
    return KeyFactory.getInstance("RSA").generatePublic(spec);
  }

  private String loadKeyFromResource(String fileName) throws IOException {
    try (InputStream is = getClass().getClassLoader().getResourceAsStream(fileName)) {
      if (is == null) {
        throw new IOException("Resource not found: " + fileName);
      }
      return new String(is.readAllBytes(), StandardCharsets.UTF_8);
    }
  }
}

How to Generate RSA Keys

You can create RSA public and private keys using OpenSSL:


openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

Make sure both .pem files are placed inside the resources folder.


How to Test the API

Generate License


GET http://localhost:8080/api/license/generate?name=Ashish&systemId=SYS12345&extendedMonths=6

Decrypt License


POST http://localhost:8080/api/license/decrypt
Body: <paste your generated license string>

Example Output


License Generated:
eyJkYXRhIjoiQXNpc2ghfFNZU0lEMTIzNHwyMDI1LTA1LTAyIiwic2lnbmF0dXJlIjoiU0dWc2JDQkViM1Z5YjJ4dmJtYz0ifQ==

Decoded license JSON:


{
  "data": "Ashish|SYS12345|2025-05-02",
  "signature": "SGVsbG9Xb3JsZA=="
}

Use Cases

  • Verify legitimate installations of software
  • Manage subscription-based features
  • Bind licenses to specific hardware IDs
  • Build auto-renewal or revocation logic

Conclusion

This simple but powerful Spring Boot API demonstrates how to implement a secure software licensing system using RSA encryption. It’s lightweight, extendable, and production-ready.

With a few modifications, you can add:

  • Encrypted metadata (edition, version, or features)
  • License renewal APIs
  • Client-side verification logic

If you’re building commercial or internal software tools, this approach ensures that your licenses are tamper-proof and securely verifiable.