DecryptCaesar.java
1 import java.util.ArrayList; 2 import java.util.HashMap; 3 import java.util.List; 4 import java.util.Map; 5 import java.util.Optional; 6 7 /// Caesar cipher decoder 8 /// 9 /// SPDX-License-Identifier: GPL-3.0-or-later 10 /// SPDX-FileCopyrightText: 2024 Jonas Smedegaard <dr@jones.dk> 11 /// 12 /// Decrypts a string encoded with the Caesar cipher. 13 /// The encrypted string is passed as first command-line argument, 14 /// and all possible decryptions are emitted to stdout. 15 /// 16 /// @version 0.0.1 17 /// @since JDK 15 18 /// @see <https://app.radicle.xyz/nodes/ash.radicle.garden/rad:z3YzABgyz2D36LiKe3YcdJ6PcDCXM/tree/assign4/README.md> 19 /// @see <https://en.wikipedia.org/wiki/Caesar_cipher> 20 /// @see <https://moodle.ruc.dk/mod/assign/view.php?id=507318> 21 public class DecryptCaesar { 22 23 /// Constructs a new DecryptCaesar object. 24 public DecryptCaesar() { 25 } 26 27 /// main method for the CLI tool 28 /// 29 /// Accepts ciphertext as first argument. 30 /// 31 /// @param args command-line arguments 32 public static void main(String[] args) { 33 final String alphabet = envString("ALPHABET", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); 34 35 SimpleOptArgs optargs = new SimpleOptArgs(args); 36 37 if (optargs.opt("--help") != null) { 38 System.err.println(help); 39 System.exit(0); 40 } 41 42 // basic sanity check of ciphertext 43 if (optargs.args.length <= 0) { 44 System.err.println("ERROR: No ciphertext provided."); 45 System.err.println(help); 46 System.exit(1); 47 } 48 49 // get string to decrypt as first command-line argument 50 final String ciphertext = optargs.args[0]; 51 52 // instantiate core class 53 String[] coreArgs = {ciphertext, alphabet, optargs.opt("--key", "0")}; 54 DecryptCaesarCore core = new DecryptCaesarCore(coreArgs); 55 core.decipherAsNeeded(); 56 } 57 58 /// Help message 59 /// 60 /// @since JDK 15 61 /// @see <https://openjdk.org/jeps/378> 62 public static String help = """ 63 Usage: java DecryptCaesar [OPTION]... CIPHERTEXT 64 65 Decrypts Caesar-encrypted CIPHERTEXT by brute force: 66 Computes and prints all possible combinations. 67 68 OPTIONS: 69 --key decrypt using specific key (i.e. not brute force) 70 --help this message 71 72 By default, a modern american alphabet is used. 73 Environment variable ALPHABET can override the alphabet to use, 74 e.g. like this to crack a ciphertext contemporary to Julius Caesar: 75 76 ALPHABET=ABCDEFZGHIKLMNOPQRSTVX java DecryptCaesar 'OVH KCD XBVGZDPV' 77 """; 78 79 /// Decode a ciphertext 80 /// 81 /// @param alphabet the ordered character set, a.k.a. the alphabet 82 /// @param ciphertext the excyptet text 83 /// @param key the key to decrypt the ciphertext 84 public static void decipher(String alphabet, String ciphertext, int key) { 85 // compose a plaintext iterating over each ciphertext character 86 final int space = alphabet.length(); 87 String plaintext = ""; 88 for ( int j = 0; j < ciphertext.length(); j++) { 89 90 // extract character, and look it up in alphabet 91 char c = ciphertext.charAt(j); 92 int k = alphabet.indexOf(c); 93 94 // return alien character as-is, 95 // or if found return rotated position, modulo the space 96 plaintext += (k < 0) ? c : alphabet.charAt((key + k) % space); 97 } 98 System.out.printf("%s: %s%n", key, plaintext); 99 } 100 101 /// Environment variable lookup 102 /// 103 /// @param variable the environment variable to look up 104 /// @param fallback a fallback string if variable is undefined 105 /// @return contents of environment variable, or fallback string 106 public static String envString(String variable, String fallback) { 107 String s = System.getenv(variable); 108 if (s != null && s.length() > 0) { 109 return s; 110 } 111 return fallback; 112 } 113 114 /// Simple generic GNU-ish options and argument parser 115 /// 116 /// * Arguments beginning with a dash are stored as options. 117 /// * Other arguments are stored non-options. 118 /// * Any option after a bare `--` are stored as non-options. 119 /// * A bare `-` is stored as a non-option. 120 /// * Options containing an equal sign are split. 121 /// * An option shadows any earlier option by the same name. 122 /// 123 /// @see <https://stackoverflow.com/a/26376532/18619283> 124 /// @see <https://stackoverflow.com/a/51421535/18619283> 125 public static class SimpleOptArgs { 126 127 /// Command-line arguments, with options stripped 128 public String[] args; 129 130 private final Map<String, List<String>> params = new HashMap<>(); 131 132 /// Constructs a new SimpleGetOpts object. 133 /// 134 /// @param orig_args command-line arguments 135 public SimpleOptArgs(String[] orig_args) { 136 List<String> options = null; 137 List<String> non_options = new ArrayList<>(); 138 params.put("--", non_options); 139 140 boolean persist = true; 141 for (int i = 0; i < orig_args.length; i++) { 142 final String arg = orig_args[i]; 143 144 if ("-".equals(arg)) { 145 non_options.add(arg); 146 } 147 else if (persist && "--".equals(arg)) { 148 persist = false; 149 } 150 else if (persist && arg.charAt(0) == '-') { 151 String[] parts = arg.split("=", 2); 152 options = new ArrayList<>(); 153 params.put(parts[0], options); 154 if (parts.length > 1) { 155 options.add(parts[1]); 156 } 157 } 158 else { 159 non_options.add(arg); 160 } 161 } 162 163 args = params.get("--").toArray(new String[0]); 164 } 165 166 /// Get an option 167 /// 168 /// @param key the option string including dash(es) 169 /// @return option value or the empty string, 170 /// or null if option is missing 171 public String opt(String key) { 172 List<String> list = params.get(key); 173 if (list == null) { 174 return null; 175 } 176 if (list.isEmpty()) { 177 return ""; 178 } 179 return list.get(0); 180 } 181 182 /// Get an option, or a fallback value 183 /// 184 /// @param key the option string including dash(es) 185 /// @param fallback the option string including dash(es) 186 /// @return option value or the empty string, 187 /// or the fallback value if option is missing 188 public String opt(String key, String fallback) { 189 List<String> list = params.get(key); 190 if (list == null) { 191 return fallback; 192 } 193 if (list.isEmpty()) { 194 return ""; 195 } 196 return list.get(0); 197 } 198 } 199 }