-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathShiftCipherBreaker.java
More file actions
191 lines (157 loc) · 6.96 KB
/
Copy pathShiftCipherBreaker.java
File metadata and controls
191 lines (157 loc) · 6.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
package project1;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
public class ShiftCipherBreaker {
private static final String API_URL = "https://generativelanguage.googleapis.com/v1beta/models/";
private static final String MODEL = "gemini-2.5-flash";
private static final String API_KEY = DotEnv.getOrThrow("SHIFT_BREAKER_API_KEY");
public static Result breakCipher(String ciphertext) throws Exception {
String[] candidates = ShiftCryptoSystem.bruteForce(ciphertext);
StringBuilder candidateList = new StringBuilder();
for (int i = 0; i < candidates.length; i++) {
System.out.println(i + " " + candidates[i]);
candidateList.append("Key ").append(i).append(": ").append(candidates[i]).append("\n");
}
String prompt = "Below are 26 possible decryptions of an additive (Caesar) cipher. "
+ "Exactly one of them is readable English. Identify the key and return "
+ "ONLY a JSON object: {\"key\": <number>, \"text\": \"<the sentence>\"}\n\n"
+ candidateList;
String responseBody = callGemini(prompt);
return parseResult(responseBody, candidates);
}
/* Calls the Gemini generateContent endpoint — mirrors ShiftCipherBreaker.callGemini */
private static String callGemini(String userPrompt) throws Exception {
// the way to write request to gemini in JSON format
// i took this from gemini itself
String jsonBody = "{"
+ "\"contents\": [{"
+ "\"parts\": [{"
+ "\"text\": " + jsonString(userPrompt)
+ "}]"
+ "}]"
+ "}";
String fullUrl = API_URL + MODEL + ":generateContent?key=" + API_KEY; // the URL structure to call the API
// send HTTP request
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(fullUrl))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonBody))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200)
throw new RuntimeException("Gemini API error " + response.statusCode() + ": " + response.body());
return response.body();
}
/* Escapes a Java string into a JSON string literal — mirrors ShiftCipherBreaker.jsonString */
// JSON we get will be stringified so i took this way from internet to just parse the JSON
private static String jsonString(String s) {
return "\"" + s.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t")
+ "\"";
}
/* Extracts the LAST "text": value from a Gemini JSON response.
* Gemini 2.5 Flash prepends a thinking block whose "text": appears first,
* so lastIndexOf skips it and lands on the actual answer. */
private static String extractText(String json) {
String marker = "\"text\":"; // we should skip the last marker string we find
int idx = json.lastIndexOf(marker); // last occurrence = actual answer, not thinking
if (idx == -1) return ""; // no answer
int start = idx + marker.length(); // start is afterrr skipping the marker
while (start < json.length() && (json.charAt(start) == ' ' || json.charAt(start) == ':')) start++; // the real start without spaces or colons
if (start >= json.length() || json.charAt(start) != '"') return ""; // no answer
// json parsing
StringBuilder sb = new StringBuilder();
int i = start + 1;
while (i < json.length()) {
char c = json.charAt(i);
if (c == '\\' && i + 1 < json.length()) {
char next = json.charAt(i + 1);
switch (next) {
case 'n' -> sb.append('\n');
case '"' -> sb.append('"');
case '\\' -> sb.append('\\');
default -> sb.append(next);
}
i += 2;
} else if (c == '"') {
break;
} else {
sb.append(c);
i++;
}
}
return sb.toString();
}
private static Result parseResult(String responseBody, String[] candidates) {
// Gemini's response nesting is: candidates[0].content.parts[0].text
String geminiText = extractText(responseBody);
String cleanJson = geminiText.replaceAll("```json", "").replaceAll("```", "").trim();
String keyStr = extractJsonString(cleanJson, "\"key\":");
String text = extractJsonString(cleanJson, "\"text\":");
int key;
try {
key = Integer.parseInt(keyStr.trim());
} catch (NumberFormatException e) {
key = findMatchingKey(candidates, text);
}
return new Result(key, text.isEmpty() ? candidates[Math.max(0, key)] : text);
}
private static String extractJsonString(String json, String fieldMarker) {
int idx = json.indexOf(fieldMarker);
if (idx == -1) return "";
int start = idx + fieldMarker.length();
while (start < json.length() && (json.charAt(start) == ' ' || json.charAt(start) == ':')) start++;
if (start >= json.length()) return "";
if (json.charAt(start) == '"') {
StringBuilder sb = new StringBuilder();
int i = start + 1;
while (i < json.length()) {
char c = json.charAt(i);
if (c == '\\' && i + 1 < json.length()) {
char next = json.charAt(i + 1);
switch (next) {
case 'n' -> sb.append('\n');
case '"' -> sb.append('"');
case '\\' -> sb.append('\\');
default -> sb.append(next);
}
i += 2;
} else if (c == '"') {
break;
} else {
sb.append(c);
i++;
}
}
return sb.toString();
} else {
int end = start;
while (end < json.length() && ",}\n]".indexOf(json.charAt(end)) == -1) end++;
return json.substring(start, end).trim();
}
}
private static int findMatchingKey(String[] candidates, String text) {
for (int i = 0; i < candidates.length; i++) {
if (candidates[i].equalsIgnoreCase(text)) return i;
}
return 0;
}
public static class Result {
public final int key;
public final String text;
public Result(int key, String text) {
this.key = key;
this.text = text;
}
@Override
public String toString() {
return "Key: " + key + "\nDecrypted text: " + text;
}
}
}