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:
/api/license/generate– Generates a signed license using the private key./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.
0 Comments