Last active
July 17, 2023 07:38
-
-
Save savaged/fa1d2becbd60e5787c494258686cb72d to your computer and use it in GitHub Desktop.
Some fun with ciphers in C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Text; | |
App.Run(args, Console.WriteLine); | |
interface ICipherService | |
{ | |
string Apply(string input, bool encrypt = false); | |
} | |
class XOrCipherService : ICipherService | |
{ | |
private readonly Encoding _textEncoder; | |
private readonly byte[] _key; | |
public XOrCipherService( | |
string key = "", | |
Encoding? textEncoder = null) | |
{ | |
_textEncoder = textEncoder ?? Encoding.ASCII; | |
_key = _textEncoder.GetBytes(KeyOrDefault(key)); | |
} | |
public string Apply(string input, bool encrypt = false) => | |
EncryptDecrypt(input); | |
private string EncryptDecrypt(string input) => | |
_textEncoder.GetString(EncryptDecrypt(input, _key)); | |
private byte[] EncryptDecrypt(string input, IReadOnlyList<byte> key) => | |
EncryptDecrypt(_textEncoder.GetBytes(input), key); | |
private static byte[] EncryptDecrypt(byte[] input, IReadOnlyList<byte> key, int i = 0) | |
{ | |
if (i == input.Length) | |
return input; | |
input[i] ^= key[i % key.Count]; | |
return EncryptDecrypt(input, key, i + 1); | |
} | |
private static string KeyOrDefault(string key) => | |
!string.IsNullOrEmpty(key) ? key | |
: new string(Enumerable.Range(97, 26).Select(i => (char)i).ToArray()); | |
} | |
class Base64CipherService : ICipherService | |
{ | |
private readonly Encoding _textEncoder; | |
public Base64CipherService(Encoding? textEncoder = null) => | |
_textEncoder = textEncoder ?? Encoding.ASCII; | |
public string Apply(string input, bool encrypt = false) => | |
encrypt | |
? Try(i => Convert.ToBase64String(_textEncoder.GetBytes(i)), input) | |
: Try(i => _textEncoder.GetString(Convert.FromBase64String(i)), input); | |
private static string Try(Func<string, string> f, string input) | |
{ | |
try | |
{ | |
return f(input); | |
} | |
catch (FormatException e) | |
{ | |
return e.Message; | |
} | |
} | |
} | |
class Rot13CipherService : ICipherService | |
{ | |
public string Apply(string input, bool encrypt = false) => | |
EncryptDecrypt(input); | |
private static string EncryptDecrypt(string input) => | |
Rot13ForEach(input.ToCharArray()); | |
private static string Rot13ForEach(IEnumerable<char> input) => | |
new(input.Select(Rot13).ToArray()); | |
private static char Rot13(char c) => | |
c switch | |
{ | |
>= 'a' and <= 'm' or >= 'A' and <= 'M' => (char)(c + 13), | |
>= 'n' and <= 'z' or >= 'N' and <= 'Z' => (char)(c - 13), | |
_ => c | |
}; | |
} | |
static class App | |
{ | |
private const string _defaultFeedback = | |
"Args: -(b|r|x){base64/rot13/xor cipher} [-k \"key\"] -(e|d){encrypt/decrypt} \"text to encrypt/decrypt\""; | |
public static void Run(string[] args, Action<string> cout, Encoding? textEncoder = null) | |
{ | |
var arguments = ExtractArgs(args); | |
if (!IsValidArgs(arguments)) | |
{ | |
cout(_defaultFeedback); | |
return; | |
} | |
switch (arguments["cipher"]) | |
{ | |
case "b": | |
cout(ApplyBase64Cipher(arguments["input"], textEncoder, arguments["encrypt"] == "e")); | |
break; | |
case "r": | |
cout(ApplyRot13Cipher(arguments["input"])); | |
break; | |
case "x": | |
cout(ApplyXOrCipher(arguments["input"], textEncoder, arguments["key"])); | |
break; | |
} | |
} | |
private static bool IsValidArgs(IReadOnlyDictionary<string, string> arguments) => | |
arguments.Count == 4 | |
&& arguments.ContainsKey("key") | |
&& arguments.ContainsKey("cipher") | |
&& arguments.ContainsKey("encrypt") | |
&& arguments.ContainsKey("input"); | |
private static IReadOnlyDictionary<string, string> ExtractArgs( | |
IReadOnlyList<string> args) | |
{ | |
var value = new Dictionary<string, string> { { "key", "" } }; | |
if (args.Count <= 2 || !args[0].StartsWith("-")) return value; | |
value.Add("cipher", args[0][^1].ToString()); | |
if (args[1].StartsWith("-k") && args.Count > 3) | |
{ | |
value["key"] = args[2]; | |
if (!args[3].StartsWith("-")) return value; | |
value.Add("encrypt", args[3][1..]); | |
value.Add("input", args[4]); | |
} | |
else if (args[1].StartsWith("-")) | |
{ | |
value.Add("encrypt", args[1][1..]); | |
value.Add("input", args[2]); | |
} | |
return value; | |
} | |
private static string ApplyXOrCipher( | |
string input, Encoding? textEncoder = null, string key = "") => | |
GetXOrCipherService(key, textEncoder).Apply(input); | |
private static string ApplyRot13Cipher(string input) => | |
GetRot13CipherService().Apply(input); | |
private static string ApplyBase64Cipher( | |
string input, Encoding? textEncoder = null, bool encrypt = false) => | |
GetBase64CipherService(textEncoder).Apply(input, encrypt); | |
private static XOrCipherService GetXOrCipherService( | |
string key = "", | |
Encoding? textEncoder = null) => new XOrCipherService(key, textEncoder); | |
private static Base64CipherService GetBase64CipherService( | |
Encoding? textEncoder = null) => new Base64CipherService(textEncoder); | |
private static Rot13CipherService GetRot13CipherService() => | |
new Rot13CipherService(); | |
} |
dotnet run -- -r -d "Uryyb Jbeyq"
dotnet run -- -b -d "SGVsbG8gV29ybGQ="
TODO: Revisit XOr cipher
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
TODO: Convert bespoke args handling to use https://learn.microsoft.com/en-us/dotnet/standard/commandline/get-started-tutorial