Added starter implementation.
This commit is contained in:
parent
5e3bfa6ca2
commit
27979d58e5
|
@ -0,0 +1,16 @@
|
||||||
|
.dub
|
||||||
|
docs.json
|
||||||
|
__dummy.html
|
||||||
|
docs/
|
||||||
|
/scrambler
|
||||||
|
scrambler.so
|
||||||
|
scrambler.dylib
|
||||||
|
scrambler.dll
|
||||||
|
scrambler.a
|
||||||
|
scrambler.lib
|
||||||
|
scrambler-test-*
|
||||||
|
*.exe
|
||||||
|
*.pdb
|
||||||
|
*.o
|
||||||
|
*.obj
|
||||||
|
*.lst
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"authors": [
|
||||||
|
"Andrew Lalis"
|
||||||
|
],
|
||||||
|
"copyright": "Copyright © 2023, Andrew Lalis",
|
||||||
|
"dependencies": {
|
||||||
|
"botan": "~>1.13.4"
|
||||||
|
},
|
||||||
|
"description": "Tool for encrypting and decrypting entire directories on-the-fly.",
|
||||||
|
"license": "MIT",
|
||||||
|
"name": "scrambler"
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"fileVersion": 1,
|
||||||
|
"versions": {
|
||||||
|
"botan": "1.13.4",
|
||||||
|
"botan-math": "1.0.4",
|
||||||
|
"memutils": "1.0.9"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
import std.stdio;
|
||||||
|
import std.path;
|
||||||
|
import std.file;
|
||||||
|
import std.algorithm : endsWith;
|
||||||
|
import std.string : strip;
|
||||||
|
|
||||||
|
import botan.block.block_cipher : BlockCipher;
|
||||||
|
import botan.block.aes : AES256;
|
||||||
|
import botan.hash.hash : HashFunction;
|
||||||
|
import botan.hash.sha2_32 : SHA256;
|
||||||
|
|
||||||
|
import cipher_utils;
|
||||||
|
|
||||||
|
int main(string[] args) {
|
||||||
|
string action = "encrypt";
|
||||||
|
if (args.length >= 2) {
|
||||||
|
string command = args[1];
|
||||||
|
if (command == "encrypt") {
|
||||||
|
action = "encrypt";
|
||||||
|
} else if (command == "decrypt") {
|
||||||
|
action = "decrypt";
|
||||||
|
} else {
|
||||||
|
stderr.writefln!"Invalid action: %s"(command);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string target = ".";
|
||||||
|
if (args.length >= 3) {
|
||||||
|
target = args[2];
|
||||||
|
if (!exists(target)) {
|
||||||
|
stderr.writefln!"Target \"%s\" doesn't exist."(target);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BlockCipher cipher = null;
|
||||||
|
// TODO: Use args to determine block cipher.
|
||||||
|
writeln("Enter a password:");
|
||||||
|
string password = readln().strip();
|
||||||
|
|
||||||
|
HashFunction hash = new SHA256();
|
||||||
|
auto secureKeyVector = hash.process(password);
|
||||||
|
cipher = new AES256();
|
||||||
|
cipher.setKey(secureKeyVector);
|
||||||
|
|
||||||
|
ubyte[] buffer = new ubyte[cipher.blockSize];
|
||||||
|
if (isDir(target)) {
|
||||||
|
if (action == "encrypt") {
|
||||||
|
encryptDir(target, cipher, buffer, true);
|
||||||
|
} else if (action == "decrypt") {
|
||||||
|
decryptDir(target, cipher, buffer, true);
|
||||||
|
} else {
|
||||||
|
stderr.writefln!"Unsupported action: %s"(action);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (isFile(target)) {
|
||||||
|
if (action == "encrypt") {
|
||||||
|
encryptAndRemoveFile(target, cipher, buffer);
|
||||||
|
} else if (action == "decrypt") {
|
||||||
|
decryptAndRemoveFile(target, cipher, buffer);
|
||||||
|
} else {
|
||||||
|
stderr.writefln!"Unsupported action: %s"(action);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stderr.writeln("Target is not a directory or file.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void encryptAndRemoveFile(string filename, BlockCipher cipher, ref ubyte[] buffer) {
|
||||||
|
string encryptedFilename = filename ~ ".enc";
|
||||||
|
encryptFile(filename, encryptedFilename, cipher, buffer);
|
||||||
|
std.file.remove(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decryptAndRemoveFile(string filename, BlockCipher cipher, ref ubyte[] buffer) {
|
||||||
|
string decryptedFilename = filename[0 .. $-4];
|
||||||
|
decryptFile(filename, decryptedFilename, cipher, buffer);
|
||||||
|
std.file.remove(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void encryptDir(string dirname, BlockCipher cipher, ref ubyte[] buffer, bool recursive) {
|
||||||
|
string[] dirsToTraverse;
|
||||||
|
foreach (DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
|
||||||
|
if (entry.isFile && !endsWith(entry.name, ".enc")) {
|
||||||
|
encryptAndRemoveFile(entry.name, cipher, buffer);
|
||||||
|
} else if (entry.isDir && recursive) {
|
||||||
|
dirsToTraverse ~= entry.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recursive) {
|
||||||
|
foreach (string childDirname; dirsToTraverse) {
|
||||||
|
encryptDir(childDirname, cipher, buffer, recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void decryptDir(string dirname, BlockCipher cipher, ref ubyte[] buffer, bool recursive) {
|
||||||
|
string[] dirsToTraverse;
|
||||||
|
foreach (DirEntry entry; dirEntries(dirname, SpanMode.shallow, false)) {
|
||||||
|
if (entry.isFile && endsWith(entry.name, ".enc")) {
|
||||||
|
decryptAndRemoveFile(entry.name, cipher, buffer);
|
||||||
|
} else if (entry.isDir && recursive) {
|
||||||
|
dirsToTraverse ~= entry.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (recursive) {
|
||||||
|
foreach (string childDirname; dirsToTraverse) {
|
||||||
|
decryptDir(childDirname, cipher, buffer, recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
module cipher_utils;
|
||||||
|
|
||||||
|
import botan.block.block_cipher : BlockCipher;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
public void encryptFile(string filename, string outputFilename, BlockCipher cipher, ref ubyte[] buffer) {
|
||||||
|
assert(buffer.length == cipher.blockSize, "Buffer length must match cipher block size.");
|
||||||
|
File fIn = File(filename, "rb");
|
||||||
|
File fOut = File(outputFilename, "wb");
|
||||||
|
// First, write one block containing the file's size.
|
||||||
|
writeSizeBytes(buffer, fIn.size);
|
||||||
|
cipher.encrypt(buffer);
|
||||||
|
fOut.rawWrite(buffer);
|
||||||
|
// Then write the rest of the file.
|
||||||
|
foreach (ubyte[] chunk; fIn.byChunk(buffer)) {
|
||||||
|
cipher.encrypt(buffer);
|
||||||
|
fOut.rawWrite(buffer);
|
||||||
|
}
|
||||||
|
fIn.close();
|
||||||
|
fOut.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void decryptFile(string filename, string outputFilename, BlockCipher cipher, ref ubyte[] buffer) {
|
||||||
|
assert(buffer.length == cipher.blockSize, "Buffer length must match cipher block size.");
|
||||||
|
File fIn = File(filename, "rb");
|
||||||
|
File fOut = File(outputFilename, "wb");
|
||||||
|
// First, read one block containing the file's size.
|
||||||
|
fIn.rawRead(buffer);
|
||||||
|
cipher.decrypt(buffer);
|
||||||
|
ulong size = readSizeBytes(buffer);
|
||||||
|
ulong bytesWritten = 0;
|
||||||
|
// Then read the rest of the file.
|
||||||
|
foreach (ubyte[] chunk; fIn.byChunk(buffer)) {
|
||||||
|
cipher.decrypt(buffer);
|
||||||
|
size_t bytesToWrite = buffer.length;
|
||||||
|
if (bytesWritten + buffer.length > size) {
|
||||||
|
bytesToWrite = cast(size_t) (size - bytesWritten);
|
||||||
|
}
|
||||||
|
fOut.rawWrite(buffer[0 .. bytesToWrite]);
|
||||||
|
bytesWritten += bytesToWrite;
|
||||||
|
}
|
||||||
|
fIn.close();
|
||||||
|
fOut.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void writeSizeBytes(ref ubyte[] bytes, ulong size) {
|
||||||
|
assert(bytes.length >= 4, "Array length must be at least 4.");
|
||||||
|
bytes[0] = size & 0xFF;
|
||||||
|
bytes[1] = (size << 8) & 0xFF;
|
||||||
|
bytes[2] = (size << 16) & 0xFF;
|
||||||
|
bytes[3] = (size << 24) & 0xFF;
|
||||||
|
if (bytes.length > 4) {
|
||||||
|
for (size_t i = 4; i < bytes.length; i++) {
|
||||||
|
bytes[i] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ulong readSizeBytes(ref ubyte[] bytes) {
|
||||||
|
assert(bytes.length >= 4, "Array length must be at least 4.");
|
||||||
|
ulong size = 0;
|
||||||
|
size += bytes[0];
|
||||||
|
size += bytes[1] << 8;
|
||||||
|
size += bytes[2] << 16;
|
||||||
|
size += bytes[3] << 24;
|
||||||
|
return size;
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"bank": {
|
||||||
|
"id": 1234,
|
||||||
|
"password": "hunter2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Hello world!
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"bank": {
|
||||||
|
"id": 1234,
|
||||||
|
"password": "hunter2"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
Hello world!
|
Loading…
Reference in New Issue