Hallo hustbear,
vielen Dank für Deine umfangreiche und hilfreiche Antwort.
hustbaer schrieb:
* Der Code in deiner encrypt_file Funktion behandelt das File-Ende nicht korrekt, wenn die Filegrösse ohne Rest durch die Blockgrösse dividierbar ist. Dann merkt dein Code nämlich erst nach dem Lesen des letzten Blocks dass kein vollständiger Block mehr gelesen werden konnte, aber gcount ist dann 0, d.h. du codierst einen leeren Block.
Wenn ich Dich richtig verstehe meinst du damit, dass bei fileSize % blockSize == 0 ein ganzer Block padding bytes angefügt wird, also ca. so:
.... | 08 08 08 08 08 08 08 08 |
Wenn ich dieses padding Verfahren richtig verstanden habe, dann ist das so gewollt:
[...] The padding string PS will satisfy one of the following statements:
PS = 01, if ||M|| mod 8 = 7 ;
PS = 02 02, if ||M|| mod 8 = 6 ;
...
PS = 08 08 08 08 08 08 08 08, if ||M|| mod 8 = 0.
https://tools.ietf.org/html/rfc2898
In anderen Worten, selbst bei einer leeren Datei werden die 8 padding bytes mitverschlüsselt. So habe ich das zuminest interpretiert.
Hier meine korrigierte Version
using Key = std::array<uint32_t, 4>; // 128bit key
using Block = std::array<uint32_t, 2>; // 64bit blocks
static constexpr std::size_t blockSize{ 8 };
static constexpr uint32_t delta{ 0x9E3779B9 };
static constexpr unsigned defaultCycles{ 32 };
static const Key defaultKey{ 1, 2, 3, 4 };
void encipher(Block& data, const Key& key, unsigned cycles = defaultCycles) {
auto& v0 = data[0];
auto& v1 = data[1];
uint32_t sum{};
while (cycles--) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
}
void decipher(Block& data, const Key& key, unsigned cycles = defaultCycles) {
auto& v0 = data[0];
auto& v1 = data[1];
uint32_t sum{ delta * cycles };
while (cycles--) {
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}
}
std::ostream& write_block(std::ostream& os, const Block& block)
{
return os.write(reinterpret_cast<const char*>(block.data()), blockSize);
}
std::ostream& write_padded_block(std::ostream& os, const Block& block)
{
auto bytes = reinterpret_cast<const unsigned char*>(block.data());
auto padCount = bytes[blockSize - 1];
return os.write(reinterpret_cast<const char*>(block.data()), blockSize - padCount);
}
std::istream& read_block(std::istream& is, Block& block)
{
return is.read(reinterpret_cast<char*>(block.data()), blockSize);
}
void pad_block(Block& block, unsigned numPadded)
{
assert(numPadded < 256);
auto bytes = reinterpret_cast<unsigned char*>(block.data());
for (auto i = blockSize - numPadded; i < blockSize; ++i)
bytes[i] = static_cast<unsigned char>(numPadded);
}
void write_encrypted_blocks(const std::string& path, const std::vector<Block>& blocks)
{
std::ofstream out{ path, std::ios::binary };
for (const auto& b : blocks)
write_block(out, b);
if (!out) throw std::runtime_error{ "Unable to write file: " + path };
}
void write_decrypted_blocks(const std::string& path, const std::vector<Block>& blocks)
{
std::ofstream out{ path, std::ios::binary };
for (std::size_t i = 0; i < blocks.size() - 1; ++i)
write_block(out, blocks[i]);
write_padded_block(out, blocks.back());
if (!out) throw std::runtime_error{ "Unable to write file: " + path };
}
std::vector<Block> read_and_encrypt_blocks(std::istream& is, const Key& key)
{
std::vector<Block> blocks(1);
while (read_block(is, blocks.back())) {
encipher(blocks.back(), key);
blocks.emplace_back();
}
pad_block(blocks.back(), static_cast<unsigned>(blockSize - is.gcount()));
encipher(blocks.back(), key);
return blocks;
}
std::vector<Block> read_and_decrypt_blocks(std::istream& is, const Key& key)
{
std::vector<Block> blocks;
for (Block block; read_block(is, block); ) {
decipher(block, key);
blocks.emplace_back(block);
}
return blocks;
}
void encrypt_file(const std::string& path, const Key& key)
{
std::ifstream file{ path, std::ios::binary };
if (!file) throw std::runtime_error{ "Unable to open file: " + path };
auto blocks = read_and_encrypt_blocks(file, key);
file.close();
write_encrypted_blocks(path, blocks);
}
void decrypt_file(const std::string& path, const Key& key)
{
std::ifstream file{ path, std::ios::binary };
if (!file) throw std::runtime_error{ "Unable to open file: " + path };
auto blocks = read_and_decrypt_blocks(file, key);
file.close();
write_decrypted_blocks(path, blocks);
}