2023-03-20 22:59:09 +00:00
|
|
|
module cipher_utils;
|
|
|
|
|
|
|
|
import botan.block.block_cipher : BlockCipher;
|
|
|
|
import std.stdio;
|
2023-03-21 13:30:09 +00:00
|
|
|
import std.file;
|
2023-03-22 07:04:21 +00:00
|
|
|
import std.datetime.stopwatch;
|
2023-03-20 22:59:09 +00:00
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
public const string ENCRYPTED_SUFFIX = ".enc";
|
|
|
|
|
|
|
|
public void encryptFile(string filename, string outputFilename, BlockCipher cipher, ref ubyte[] buffer, bool verbose) {
|
2023-03-20 22:59:09 +00:00
|
|
|
assert(buffer.length == cipher.blockSize, "Buffer length must match cipher block size.");
|
2023-03-21 13:30:09 +00:00
|
|
|
if (verbose) {
|
|
|
|
writefln!"Encrypting file \"%s\" of %d bytes to \"%s\" using cipher %s."(
|
|
|
|
filename,
|
|
|
|
getSize(filename),
|
|
|
|
outputFilename,
|
|
|
|
cipher.name
|
|
|
|
);
|
|
|
|
}
|
2023-03-22 07:04:21 +00:00
|
|
|
StopWatch sw = StopWatch(AutoStart.yes);
|
2023-03-20 22:59:09 +00:00
|
|
|
File fIn = File(filename, "rb");
|
|
|
|
File fOut = File(outputFilename, "wb");
|
|
|
|
// First, write one block containing the file's size.
|
|
|
|
writeSizeBytes(buffer, fIn.size);
|
2023-03-21 13:30:09 +00:00
|
|
|
// Fill the rest of the block with an incrementing series of bytes, so we can easily validate decryption.
|
|
|
|
if (buffer.length > 8) {
|
|
|
|
ubyte marker = 1;
|
|
|
|
for (size_t i = 8; i < buffer.length; i++) {
|
|
|
|
buffer[i] = marker++;
|
|
|
|
}
|
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
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();
|
2023-03-22 07:04:21 +00:00
|
|
|
sw.stop();
|
2023-03-21 13:30:09 +00:00
|
|
|
if (verbose) {
|
2023-03-22 07:04:21 +00:00
|
|
|
writefln!" Encrypted file in %d ms, new size %d bytes."(sw.peek().total!"msecs", getSize(outputFilename));
|
2023-03-21 13:30:09 +00:00
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
|
2023-03-21 13:30:09 +00:00
|
|
|
public bool decryptFile(string filename, string outputFilename, BlockCipher cipher, ref ubyte[] buffer, bool verbose) {
|
2023-03-20 22:59:09 +00:00
|
|
|
assert(buffer.length == cipher.blockSize, "Buffer length must match cipher block size.");
|
2023-03-21 13:30:09 +00:00
|
|
|
if (verbose) {
|
|
|
|
writefln!"Decrypting file \"%s\" of %d bytes to \"%s\" using cipher %s."(
|
|
|
|
filename,
|
|
|
|
getSize(filename),
|
|
|
|
outputFilename,
|
|
|
|
cipher.name
|
|
|
|
);
|
|
|
|
}
|
2023-03-22 07:04:21 +00:00
|
|
|
StopWatch sw = StopWatch(AutoStart.yes);
|
2023-03-20 22:59:09 +00:00
|
|
|
File fIn = File(filename, "rb");
|
|
|
|
// First, read one block containing the file's size.
|
|
|
|
fIn.rawRead(buffer);
|
|
|
|
cipher.decrypt(buffer);
|
2023-03-21 13:30:09 +00:00
|
|
|
// Verify the sequence of values to ensure decryption was successful.
|
2023-03-22 07:04:21 +00:00
|
|
|
if (buffer.length > 8 && !validateBufferDecryptionMarker(buffer[8..$], verbose)) {
|
|
|
|
fIn.close();
|
|
|
|
return false;
|
2023-03-21 13:30:09 +00:00
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
ulong size = readSizeBytes(buffer);
|
2023-03-21 13:30:09 +00:00
|
|
|
if (verbose) {
|
|
|
|
writefln!" Original file had size of %d bytes."(size);
|
|
|
|
}
|
2023-03-20 22:59:09 +00:00
|
|
|
ulong bytesWritten = 0;
|
2023-03-21 13:30:09 +00:00
|
|
|
File fOut = File(outputFilename, "wb");
|
2023-03-20 22:59:09 +00:00
|
|
|
// 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();
|
2023-03-22 07:04:21 +00:00
|
|
|
sw.stop();
|
2023-03-21 13:30:09 +00:00
|
|
|
if (verbose) {
|
2023-03-22 07:04:21 +00:00
|
|
|
writefln!" Decrypted file in %d ms, new size %d bytes."(sw.peek().total!"msecs", getSize(outputFilename));
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
private bool validateBufferDecryptionMarker(ubyte[] bufferSlice, bool verbose) {
|
|
|
|
ubyte expectedMarker = 1;
|
|
|
|
for (size_t i = 0; i < bufferSlice.length; i++) {
|
|
|
|
if (bufferSlice[i] != expectedMarker) {
|
|
|
|
if (verbose) {
|
|
|
|
writefln!" Decryption validation failed. Expected byte at index %d to be %d, but got %d."(
|
|
|
|
i,
|
|
|
|
expectedMarker,
|
|
|
|
bufferSlice[i]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
expectedMarker++;
|
2023-03-21 13:30:09 +00:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
union LongByteArrayUnion {
|
|
|
|
ulong longValue;
|
|
|
|
ubyte[8] bytes;
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private void writeSizeBytes(ref ubyte[] bytes, ulong size) {
|
2023-03-21 13:30:09 +00:00
|
|
|
assert(bytes.length >= 8, "Array length must be at least 8.");
|
|
|
|
LongByteArrayUnion u;
|
|
|
|
u.longValue = size;
|
|
|
|
for (size_t i = 0; i < 8; i++) bytes[i] = u.bytes[i];
|
|
|
|
if (bytes.length > 8) {
|
|
|
|
for (size_t i = 8; i < bytes.length; i++) {
|
2023-03-20 22:59:09 +00:00
|
|
|
bytes[i] = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private ulong readSizeBytes(ref ubyte[] bytes) {
|
2023-03-21 13:30:09 +00:00
|
|
|
assert(bytes.length >= 8, "Array length must be at least 8.");
|
|
|
|
LongByteArrayUnion u;
|
|
|
|
for (size_t i = 0; i < 8; i++) u.bytes[i] = bytes[i];
|
|
|
|
return u.longValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
unittest {
|
|
|
|
ubyte[] buffer = new ubyte[16];
|
|
|
|
|
|
|
|
void doAssert(ulong size) {
|
|
|
|
import std.format;
|
|
|
|
writeSizeBytes(buffer, size);
|
|
|
|
ulong r = readSizeBytes(buffer);
|
|
|
|
assert(r == size, format!"Value read: %d does not match expected: %d."(r, size));
|
|
|
|
}
|
|
|
|
|
|
|
|
doAssert(0);
|
|
|
|
doAssert(1);
|
|
|
|
doAssert(42);
|
|
|
|
doAssert(74_092_382_742_030);
|
2023-03-20 22:59:09 +00:00
|
|
|
}
|