SharpBox - Time boxed password decryption
This file contains 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
/* | |
SharpBox is a C# version of Simon Bracegirdle's Lockedbox: https://github.com/sbracegirdle/lockedbox | |
It encrypts any text you give it, but can only be unencypted during certain times of the day. This provides you a safety net when your willpower fails for time-sucking applications (Steam, Netflix, etc). | |
PREREQUISITES: | |
dotnet | |
INSTALLATION: | |
The program first needs to be built with an inbuilt password. | |
1. Run `dotnet new console` to establish the project as a cli application. | |
2. Modify the `passPhrase` string to someting random you cannot memorize. | |
3. Modify the `weekdays` string to only apply on days you wish to be unable to decrypt. | |
4. Modify the `after` TimeSpan to allow decryption on the aforementioned days after the specified time | |
5. Build with `dotnet publish -c Release --self-contained -r linux-x64 /p:PublishSingleFile=true` | |
a. substitute out the runtime for whichever environment you're on | |
6. Modify the `passPhrase` string to something else to prevent manual unencryption | |
In your `bin/Release/.../publish/` folder there will be an executable program `sharpbox`. | |
*/ | |
using System; | |
using System.IO; | |
using System.Linq; | |
using System.Security.Cryptography; | |
using System.Text; | |
namespace sharpbox | |
{ | |
public class Sharpbox | |
{ | |
// This constant is used to determine the keysize of the encryption algorithm in bits. | |
// We divide this by 8 within the code below to get the equivalent number of bytes. | |
private const int Keysize = 128; | |
// This constant determines the number of iterations for the password bytes generation function. | |
private const int DerivationIterations = 1000; | |
private const string passPhrase = "9psa8jdf982j3-r9a-sd0jf98awj9r8j329r8jas97djf98sajd9-fj9sa-8dvj8sand83249-jra98-sdm9f-8nsad-98nc-98asd98-9sjdf-9832fj89fj2-983fj-9283jf98asjd9vj7as987jf98j932jr98asdfjsa8f-ksa9d8jv9sa8jdf98jaw98fja9w8j398r2nn1ri4n437580f6dsn87xnqp4rpq2;4lh3po4hutwerih;31i4h[aspodf8h9asjdfpioudisf"; | |
private static string[] weekdays = { "Monday", "Tuesday", "Wednesday", "Thursday", "Friday" }; | |
private static TimeSpan after = new TimeSpan(12, 0, 0); | |
public static void Main(string[] args) | |
{ | |
if (args.Length != 2) | |
{ | |
Console.WriteLine("Usage: sharpbox.exe [encrypt/decrypt] [PASSWORD/CIPHER]\n"); | |
return; | |
} | |
string task = args[0]; | |
string value = args[1]; | |
if (!(String.Equals(task, "encrypt") || String.Equals(task, "decrypt"))) | |
{ | |
Console.WriteLine("Unknown argument `" + task + "`"); | |
Console.WriteLine("Usage: sharpbox.exe [encrypt/decrypt] [PASSWORD/CIPHER]\n"); | |
return; | |
} | |
DateTime currentTime = DateTime.Now; | |
if (task == "decrypt" && weekdays.Contains(currentTime.DayOfWeek.ToString()) && currentTime.TimeOfDay < after) | |
{ | |
Console.WriteLine("Not allowed to decrypt at this time."); | |
return; | |
} | |
string output = ""; | |
switch (task) | |
{ | |
case "encrypt": | |
Console.WriteLine("Encrypting..."); | |
output = Encrypt(value); | |
break; | |
case "decrypt": | |
Console.WriteLine("Decrypting..."); | |
output = Decrypt(value); | |
break; | |
} | |
Console.WriteLine(output); | |
} | |
public static string Encrypt(string plainText) | |
{ | |
// Salt and IV is randomly generated each time, but is preprended to encrypted cipher text | |
// so that the same Salt and IV values can be used when decrypting. | |
var saltStringBytes = Generate256BitsOfRandomEntropy(); | |
var ivStringBytes = Generate256BitsOfRandomEntropy(); | |
var plainTextBytes = Encoding.UTF8.GetBytes(plainText); | |
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) | |
{ | |
var keyBytes = password.GetBytes(Keysize / 8); | |
using (var symmetricKey = new RijndaelManaged()) | |
{ | |
symmetricKey.BlockSize = 128; | |
symmetricKey.Mode = CipherMode.CBC; | |
symmetricKey.Padding = PaddingMode.PKCS7; | |
using (var encryptor = symmetricKey.CreateEncryptor(keyBytes, ivStringBytes)) | |
{ | |
using (var memoryStream = new MemoryStream()) | |
{ | |
using (var cryptoStream = new CryptoStream(memoryStream, encryptor, CryptoStreamMode.Write)) | |
{ | |
cryptoStream.Write(plainTextBytes, 0, plainTextBytes.Length); | |
cryptoStream.FlushFinalBlock(); | |
// Create the final bytes as a concatenation of the random salt bytes, the random iv bytes and the cipher bytes. | |
var cipherTextBytes = saltStringBytes; | |
cipherTextBytes = cipherTextBytes.Concat(ivStringBytes).ToArray(); | |
cipherTextBytes = cipherTextBytes.Concat(memoryStream.ToArray()).ToArray(); | |
memoryStream.Close(); | |
cryptoStream.Close(); | |
return Convert.ToBase64String(cipherTextBytes); | |
} | |
} | |
} | |
} | |
} | |
} | |
public static string Decrypt(string cipherText) | |
{ | |
// Get the complete stream of bytes that represent: | |
// [32 bytes of Salt] + [32 bytes of IV] + [n bytes of CipherText] | |
var cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText); | |
// Get the saltbytes by extracting the first 32 bytes from the supplied cipherText bytes. | |
var saltStringBytes = cipherTextBytesWithSaltAndIv.Take(Keysize / 8).ToArray(); | |
// Get the IV bytes by extracting the next 32 bytes from the supplied cipherText bytes. | |
var ivStringBytes = cipherTextBytesWithSaltAndIv.Skip(Keysize / 8).Take(Keysize / 8).ToArray(); | |
// Get the actual cipher text bytes by removing the first 64 bytes from the cipherText string. | |
var cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize / 8) * 2).Take(cipherTextBytesWithSaltAndIv.Length - ((Keysize / 8) * 2)).ToArray(); | |
using (var password = new Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)) | |
{ | |
var keyBytes = password.GetBytes(Keysize / 8); | |
using (var symmetricKey = new RijndaelManaged()) | |
{ | |
symmetricKey.BlockSize = 128; | |
symmetricKey.Mode = CipherMode.CBC; | |
symmetricKey.Padding = PaddingMode.PKCS7; | |
using (var decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)) | |
{ | |
using (var memoryStream = new MemoryStream(cipherTextBytes)) | |
{ | |
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)) | |
{ | |
var plainTextBytes = new byte[cipherTextBytes.Length]; | |
var decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length); | |
memoryStream.Close(); | |
cryptoStream.Close(); | |
return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount); | |
} | |
} | |
} | |
} | |
} | |
} | |
private static byte[] Generate256BitsOfRandomEntropy() | |
{ | |
var randomBytes = new byte[16]; // 32 Bytes will give us 256 bits. | |
using (var rngCsp = new RNGCryptoServiceProvider()) | |
{ | |
// Fill the array with cryptographically secure random bytes. | |
rngCsp.GetBytes(randomBytes); | |
} | |
return randomBytes; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment