Crypto Tool

Spring Boot CommandLineRunner crypto tool. This tool: - Runs only when explicitly invoked - Uses Spring Boot only for lifecycle & DI - Exists immediately after doing its job - Does not start Tomcat - Never runs in production by accident

1. Project structure

You usually put this in a separate module.
 
crypto-tool/
    src/main/java/com/minte9/crypto
    ├─ CryptoCliApp.java
    ├─ CryptoCommandRunner.java
    └─ AES_GCM.java
    pom.xml

2. Pom

Make sure you do NOT include web starters.
 
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>        
</dependencies>

3. Entry point (CLI-only)

 
/**
 * Spring Boot CLI entry point
 *
 * This application is NOT a web app.
 * It is only to run CommandLinnerRunner logic.
 */

package com.example.crypto;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class CryptoCliApp {
    public static void main(String[] args) {
        SpringApplication
            .run(CryptoCliApp.class, args)
            .close(); // force shutdown after task completes
    }
}

4. CommandLineRunner

 
/**
 * CommandLineRunner 
 * 
 * It acts like a CLI tool.
 *
 * Usage:
 *  java -jar crypto-tool.jar genkeys
 *  java -jar crypto-tool.jar encrypt "PlainTextPassword"
 * 
 * Genkeys:
 *  - Generates AES-256 key + GCM IV
 *  - Run ONCE, then store tehm as environment variables.
 * 
 * Encrypt:
 *  - Encrypts a plaintext password using env-provided key and IV
 */

package com.example.crypto.controller;

import com.example.crypto.service.AES_GCM;

import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class CryptoCommandRunner implements CommandLineRunner {

    @Override
    public void run(String... args) throws Exception {
        if (args.length == 0) {
            printUsageAndExit();
        }

        switch (args[0]) {
            case "genkeys":
                genkeys();
                break;

            case "encrypt":
                if (args.length != 2) {
                    System.err.println("ERROR: Missing plaintext password");
                    printUsageAndExit();
                }
                encrypt(args[1]);
                break;

            default:
                System.err.println("ERRROR: Unknown command: " + args[0]);
                printUsageAndExit();
        }
    }
    
    private void printUsageAndExit() {
        System.out.println();
        System.out.println("Usage:");
        System.out.println("  java -jar crypto-tool.jar genkeys");
        System.out.println("  java -jar crypto-tool.jar encrypt \"PlainTextPassword\"");
        System.exit(1);
    }

    private void genkeys() throws Exception {
        String key = AES_GCM.createKey(256);
        String iv = AES_GCM.createIv();

        System.out.println("SB_ENCRYPT_PASSWORD_KEY=" + key);
        System.out.println("SB_ENCRYPT_PASSWORD_IV=" + iv);
    }

    private void encrypt(String platinText) throws Exception {
        String key = System.getenv("SB_ENCRYPT_PASSWORD_KEY");
        String iv = System.getenv("SB_ENCRYPT_PASSWORD_IV");
        
        if (key == null || iv == null) {
            throw new IllegalStateException(
                "Missing environment variables: " + 
                "SB_ENCRYPT_PASSWORD_KEY / SB_ENCRYPT_PASSWORD_IV"
            );
        }

        String encrypted = AES_GCM.encrypt(platinText, key, iv);
        System.out.println(encrypted);
    }
}

5. AES_GCM cipher

 
/**
 * AES GCM cipher
 *
 * GCM is proven secure in the concrete security model.
 * The ECB can leak information about the plaintext.
 */

package com.example.crypto.service;

import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES_GCM {

   public static String encrypt(String plainText, String key, String ivStr)
           throws Exception {
       
       byte[] iv = Base64.getDecoder().decode(ivStr);
       SecretKey secretKey = AES_GCM.getSecretKey(key);
       Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
       cipher.init(
           Cipher.ENCRYPT_MODE, secretKey, new GCMParameterSpec(128, iv)
       );

       return Base64.getEncoder().encodeToString(
           cipher.doFinal(plainText.getBytes("UTF-8"))
       );
   }

   public static String decrypt(String encryptedText, String key, String ivStr)
           throws Exception {
       
       byte[] iv = Base64.getDecoder().decode(ivStr);
       SecretKey secretKey = AES_GCM.getSecretKey(key);
       Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
       cipher.init(
           Cipher.DECRYPT_MODE, secretKey, new GCMParameterSpec(128, iv)
       );

       return new String(cipher.doFinal(
           Base64.getDecoder().decode(encryptedText)
       ));
   }

   public static String createKey(int size)
           throws NoSuchAlgorithmException {

       KeyGenerator keyGen = KeyGenerator.getInstance("AES");
       keyGen.init(256);
       SecretKey secretKey = keyGen.generateKey();
       return Base64.getEncoder().encodeToString(secretKey.getEncoded());
   }

   public static String createIv() {
       byte[] iv = new byte[12];
       new SecureRandom().nextBytes(iv);
       return Base64.getEncoder().encodeToString(iv);
   }

   public static SecretKey getSecretKey(String key) {
       byte[] decoded = Base64.getDecoder().decode(key);
       return new SecretKeySpec(decoded, 0, decoded.length, "AES");
   }

}

6. Test it

Generate keys.
 
mvn package

java -jar .\target\crypto-tool-0.0.1.jar genkeys
# SB_ENCRYPT_PASSWORD_KEY=kQvk8AYEaawD1z05AxkM045I8Fi/fVcldA6osPbf6xo=
# SB_ENCRYPT_PASSWORD_IV=Dllfq8jY2t0QmVLN
Set environment variables (linux):
 
export SB_ENCRYPT_PASSWORD_KEY="kQvk8AYEaawD1z05AxkM045I8Fi/fVcldA6osPbf6xo="
export SB_ENCRYPT_PASSWORD_IV="Dllfq8jY2t0QmVLN"
Encrypt plain password.
 
java -jar .\target\crypto-tool-0.0.1.jar encrypt mypass
# p1Akt2ya1eVCn34WUfJtnpOiF3G0zA==
PowerShell treats $something as a variable expansion, even inside double quotes. Solution: use single quotes.
 
java -jar .\target\crypto-tool-0.0.1.jar encrypt 'tgW3$#2222222$11111111'
# vk4D5TvKd6dkb9sHe8UdGWWz0lgnHtGYuA3x6PoSiZUphKYjPYs=