Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/core/passwords.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,44 @@ String encryptString(String &plaintext, const String &password_str) {
return out;
}

String encryptFileHeader() {
// Same header encryptString() writes, up to (and including) the "Data: " prefix.
// The hex payload that follows may be appended incrementally across chunks.
String out = "Filetype: Bruce Encrypted File\nVersion: 1\n";
out += "Algo: XOR\n";
out += "KeyDerivationAlgo: MD5\n";
out += "KeyDerivationPasses: 10\n";
out += "Data: ";
return out;
}

String encryptChunkToHex(const uint8_t *data, size_t len, const String &password_str, size_t streamOffset) {
// Derive the same 16-byte key xorEncryptDecryptMD5() uses (MD5 of the password, 10
// passes), so a chunk-encrypted file decrypts identically to a single-shot one.
MD5Builder md5;
String hash = password_str;
for (int i = 0; i < 10; i++) {
md5.begin();
md5.add(hash);
md5.calculate();
}
uint8_t md5Hash[16];
md5.getBytes(md5Hash);

// Fixed-width "XX " per byte: the reader parses the Data line in 3-char groups, so
// every byte must be two uppercase hex digits followed by a separator.
static const char hexDigits[] = "0123456789ABCDEF";
String out;
out.reserve(len * 3);
for (size_t i = 0; i < len; i++) {
uint8_t b = data[i] ^ md5Hash[(streamOffset + i) % 16];
out += hexDigits[(b >> 4) & 0x0F];
out += hexDigits[b & 0x0F];
out += ' ';
}
return out;
}

/* OLD:
String decryptString(String& cypertext, const String& password_str)

Expand Down
7 changes: 7 additions & 0 deletions src/core/passwords.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@

String encryptString(String &plaintext, const String &password_str);

// Streaming helpers for chunked encryption (e.g. web uploads). The XOR keystream is
// position-based, so a file can be encrypted chunk by chunk as long as each chunk knows
// its absolute offset in the plaintext. encryptFileHeader() emits the header once,
// ending with "Data: "; encryptChunkToHex() appends the hex bytes for one chunk.
String encryptFileHeader();
String encryptChunkToHex(const uint8_t *data, size_t len, const String &password_str, size_t streamOffset);

String decryptString(String &cypertext, const String &password_str);

String readDecryptedFile(FS &fs, String filepath);
20 changes: 11 additions & 9 deletions src/core/wifi/webInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,20 @@ void handleUpload(
// Serial.println("Failed to open file for writing: " + uploadFolder + "/" + filename);
goto RETRY;
}
if (request->hasArg("password") && request->_tempFile) {
// Write the encrypted-file header once; the hex payload is streamed per chunk.
String header = encryptFileHeader();
request->_tempFile.write((const uint8_t *)header.c_str(), header.length());
}
}

if (len) {
if (request->hasArg("password")) {
// encryption requested
static int chunck_no = 0;
if (chunck_no != 0) {
// TODO: handle multiple chunks
request->send(404, "text/html", "file is too big");
return;
} else chunck_no += 1;
// Encrypt this chunk incrementally. The XOR keystream is position-based, so
// `index` (the chunk's offset in the plaintext) keeps it continuous across
// chunks without buffering the whole file in RAM.
String enc_password = request->arg("password");
String plaintext = String((char *)data).substring(0, len);
String cyphertxt = encryptString(plaintext, enc_password);
String cyphertxt = encryptChunkToHex(data, len, enc_password, index);
if (cyphertxt == "") { return; }
if (request->_tempFile)
request->_tempFile.write((const uint8_t *)cyphertxt.c_str(), cyphertxt.length());
Expand All @@ -260,6 +260,8 @@ void handleUpload(
}
}
if (final) {
// Terminate the encrypted Data line before closing, matching encryptString().
if (request->hasArg("password") && request->_tempFile) request->_tempFile.write((const uint8_t *)"\n", 1);
// close the file handle as the upload is now done
if (request->_tempFile) request->_tempFile.close();
}
Expand Down