-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathProgram.cs
373 lines (318 loc) · 14.5 KB
/
Program.cs
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
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
using System.Security.Cryptography;
using System.Xml;
using System.Text;
using CommandLine;
using System;
class Program{
//configurable sizes for pad data in bytes
private static int messageLength = 192;
private static int shuffleKeyLength = 32;
private static int macNonceLength = 32;
class MessageData{
public byte[] MAC { get; set; }
public byte[] Body { get; set; }
}
class PadData{
public byte[] NonceBytes { get; set; }
public byte[] ShuffleBytes { get; set; }
public byte[] MixBytes { get; set; }
}
public class Options{
[Option('g', "generatePad", HelpText = "Use a bin file to generate a directory of pad files.")]
public bool GeneratePad { get; set; }
[Option('e', "encrypt", HelpText = "Encrypt a plaintext message using the specified pad file.")]
public bool Encrypt { get; set; }
[Option('d', "decrypt", HelpText = "Decrypt an encrypted message using the specified pad file.")]
public bool Decrypt { get; set; }
[Option('b', "binPath", HelpText = "Path for the bin file for generating pad files.")]
public string BinPath { get; set; }
[Option('m', "messagePath", HelpText = "Path for the plaintext or encrypted message file.")]
public string MessagePath { get; set; }
[Option('p', "padPath", HelpText = "Path to the pad file.")]
public string PadPath { get; set; }
[Option('o', "outputPath", HelpText = "Path for the output files.")]
public string OutputPath { get; set; }
}
public static void Main(string[] args){
string mode = "";
string binPath = "";
string messagePath = "";
string padPath = "";
string outputPath = "";
PadData padData = new PadData();
byte[] plaintextMessage = new byte[messageLength];
MessageData messageData = new MessageData();
Parser.Default.ParseArguments<Options>(args).WithParsed(options =>{
// Check if more than one main action flag (-g, -e, -d) is specified
if ((options.GeneratePad ? 1 : 0) + (options.Encrypt ? 1 : 0) + (options.Decrypt ? 1 : 0) > 1){
throw new ArgumentException("Error: Only one of -g, -e, -d flags can be used.");
}
//set mode based on flag used
if (options.GeneratePad){
mode = "generatePad";
}else if (options.Encrypt){
mode = "encrypt";
}else if (options.Decrypt){
mode = "decrypt";
} else {
throw new ArgumentException("Invalid mode selection.");
}
binPath = options.BinPath;
messagePath = options.MessagePath;
padPath = options.PadPath;
outputPath = options.OutputPath;
}).WithNotParsed(errors =>{
Console.WriteLine("Error parsing command-line arguments:");
foreach (var error in errors){
Console.WriteLine(error);
}
Environment.Exit(1);
});
try{
switch(mode){
case "generatePad":
CreatePadbook(binPath, outputPath);
break;
case "encrypt":
//read message from disk
plaintextMessage = Encoding.UTF8.GetBytes(File.ReadAllText(messagePath));
padData = ReadPadData(padPath);
//pad message
plaintextMessage = PadMessage(plaintextMessage);
//shuffle text
plaintextMessage = ShuffleUtility.Shuffle(padData.ShuffleBytes, plaintextMessage);
//encrypt text and generate hmac
byte[] cypherText = MixBytes(plaintextMessage, padData.MixBytes);
byte[] hmac = GenerateHMACSHA512(padData.NonceBytes, cypherText);
//write hmac and cypher text to disk
WriteMessageData(cypherText, hmac, outputPath);
break;
case "decrypt":
//read cypher text and hmac from disk
messageData = ReadMessageData(messagePath);
padData = ReadPadData(padPath);
//verify if hmac is valid or invalid
if(VerifyHMACSHA512(padData.NonceBytes, messageData.Body, messageData.MAC)){
Console.WriteLine("HMAC is valid");
} else {
Console.WriteLine("HMAC is not valid");
throw new ArgumentException("HMAC invalid. Canceling decryption.");
}
//decrypt cypher text
byte[] decryptedMessage = MixBytes(messageData.Body, padData.MixBytes);
//unshuffle plaintext
decryptedMessage = ShuffleUtility.Unshuffle(padData.ShuffleBytes, decryptedMessage);
//write plaintext to disk
File.WriteAllText(outputPath, Encoding.UTF8.GetString(decryptedMessage));
break;
default:
throw new ArgumentException("Invalid mode selection.");
}
}catch (Exception ex){
Console.WriteLine($"Error: {ex.Message}");
}
}
private static byte[] PadMessage(byte[] message){
//message must be <= key length>
if(message.Length >= messageLength){
throw new ArgumentException("Message length exceeds key length");
}
try{
Random random = new Random();
//append random ascii char until message matches length of key
while(message.Length < messageLength){
Array.Resize(ref message, message.Length + 1);
message[message.Length-1] = (byte)(char)random.Next(33, 127);
}
return message;
}catch (Exception ex){
Console.WriteLine($"Error while padding message: {ex.Message}");
return null;
}
}
private static byte[] MixBytes(byte[] messageBytes, byte[] padBytes){
try{
//message and key must be the same length
if(messageBytes.Length != messageLength){
throw new ArgumentException("Message length must equal key length");
}
byte[] result = new byte[messageLength];
//xor each byte in message with bytes in pad
for (int i = 0; i < messageLength; i++){
result[i] = (byte)(messageBytes[i] ^ padBytes[i]);
}
return result;
}catch (Exception ex){
Console.WriteLine($"Error mixing message and pad bytes: {ex.Message}");
return null;
}
}
private static byte[] GenerateHMACSHA512(byte[] key, byte[] data){
try{
//generate an HMAC for the given cypher text and nonce
using (HMACSHA512 hmac = new HMACSHA512(key)){
return hmac.ComputeHash(data);
}
}catch (Exception ex){
Console.WriteLine($"Error generating HMAC: {ex.Message}");
return null;
}
}
private static bool VerifyHMACSHA512(byte[] key, byte[] data, byte[] expectedMac){
try{
//generate the HMAC for the recieved cypher text and nonce from pad
byte[] actualMac = GenerateHMACSHA512(key, data);
//check if hmac recieved matches what the hmac should be
return CryptographicOperations.FixedTimeEquals(actualMac, expectedMac);
}catch (Exception ex){
Console.WriteLine($"Error verifying HMAC: {ex.Message}");
return false;
}
}
private static MessageData ReadMessageData(string messagePath){
try{
if(!File.Exists(messagePath)){
throw new ArgumentException("File " + messagePath + "doesn't exist");
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(messagePath);
//read xml attributes and convert to byte[]
XmlNode padNode = xmlDoc.SelectSingleNode("message");
byte[] mac = Convert.FromBase64String(padNode.SelectSingleNode("mac").InnerText);
byte[] body = Convert.FromBase64String(padNode.SelectSingleNode("body").InnerText);
//return new MessageData instance with the read message data
return new MessageData{
MAC = mac,
Body = body,
};
}catch (Exception ex){
Console.WriteLine($"Error reading cypher text message XML file: {ex.Message}");
return null;
}
}
private static void WriteMessageData(byte[] bodyBytes, byte[] hmacBytes, string outputPath){
try{
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.Encoding = Encoding.ASCII;
//write message components to disk
using (XmlWriter writer = XmlWriter.Create(outputPath, settings)){
// Write XML declaration
writer.WriteStartDocument();
// Write root element <pad>
writer.WriteStartElement("message");
// Write mac element
writer.WriteStartElement("mac");
writer.WriteBase64(hmacBytes, 0, hmacBytes.Length);
writer.WriteEndElement();
// Write body element
writer.WriteStartElement("body");
writer.WriteBase64(bodyBytes, 0, bodyBytes.Length);
writer.WriteEndElement();
// Close root element </pad>
writer.WriteEndElement();
// Close the document
writer.WriteEndDocument();
}
}catch (Exception ex){
Console.WriteLine($"Error writing cyphertext message file: {ex.Message}");
}
}
private static PadData ReadPadData(string padPath){
try{
if(!File.Exists(padPath)){
throw new ArgumentException("File " + padPath + "doesn't exist");
}
XmlDocument xmlDoc = new XmlDocument();
xmlDoc.Load(padPath);
//read xml attributes and convert to byte[]
XmlNode padNode = xmlDoc.SelectSingleNode("pad");
byte[] nonceBytes = Convert.FromBase64String(padNode.SelectSingleNode("nonceBytes").InnerText);
byte[] shuffleBytes = Convert.FromBase64String(padNode.SelectSingleNode("shuffleBytes").InnerText);
byte[] mixBytes = Convert.FromBase64String(padNode.SelectSingleNode("mixBytes").InnerText);
//return new PadData instance with read pad data
return new PadData{
NonceBytes = nonceBytes,
ShuffleBytes = shuffleBytes,
MixBytes = mixBytes
};
}catch (Exception ex){
Console.WriteLine($"Error parsing XML file: {ex.Message}");
return null;
}
}
private static void CreatePadbook(string binPath, string outputDir){
try{
int padQuantity = 0;
int padCount = 0;
if(!File.Exists(binPath)){
throw new ArgumentException("File " + binPath + " doesn't exist");
}
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
settings.Encoding = Encoding.ASCII;
//determine how many pads can be made from the specified bin
FileInfo fileInfo = new FileInfo(binPath);
padQuantity = (int)(fileInfo.Length / (messageLength+shuffleKeyLength+macNonceLength));
using (FileStream fs = new FileStream(binPath, FileMode.Open)){
for(padCount = 0; padCount < padQuantity; padCount++){
byte[] buffer = new byte[(messageLength+shuffleKeyLength+macNonceLength)];
//read bytes from bin and parse them into each attribute for PadData
using (XmlWriter writer = XmlWriter.Create(outputDir + "pad" + padCount + ".xml", settings)){
// Write XML declaration
writer.WriteStartDocument();
// Write root element <pad>
writer.WriteStartElement("pad");
// Write nonceBytes element
writer.WriteStartElement("nonceBytes");
fs.Read(buffer, 0, macNonceLength);
writer.WriteBase64(buffer, 0, macNonceLength);
writer.WriteEndElement();
// Write shuffleBytes element
writer.WriteStartElement("shuffleBytes");
fs.Read(buffer, 0, shuffleKeyLength);
writer.WriteBase64(buffer, 0, shuffleKeyLength);
writer.WriteEndElement();
// Write messageBytes element
writer.WriteStartElement("mixBytes");
fs.Read(buffer, 0, messageLength);
writer.WriteBase64(buffer, 0, messageLength);
writer.WriteEndElement();
// Close root element </pad>
writer.WriteEndElement();
// Close the document
writer.WriteEndDocument();
}
}
}
}catch (Exception ex){
Console.WriteLine($"Error parsing bin file: {ex.Message}");
}
}
}
public static class ShuffleUtility{
public static byte[] Shuffle(byte[] key, byte[] array){
//shuffle using the key
for (int i = array.Length - 1; i > 0; i--){
int j = GetNextIndexFromKey(key, i);
byte temp = array[i];
array[i] = array[j];
array[j] = temp;
}
return array;
}
public static byte[] Unshuffle(byte[] key, byte[] shuffledArray){
//unshuffle using key
for (int i = 1; i < shuffledArray.Length; i++){
int j = GetNextIndexFromKey(key, i);
byte temp = shuffledArray[i];
shuffledArray[i] = shuffledArray[j];
shuffledArray[j] = temp;
}
return shuffledArray;
}
private static int GetNextIndexFromKey(byte[] key, int currentIndex){
// Use bytes from the key array to determine swapping positions
return key[currentIndex % key.Length] % (currentIndex + 1);
}
}