-
-
Notifications
You must be signed in to change notification settings - Fork 166
/
Copy pathData.cpp
173 lines (142 loc) · 7.21 KB
/
Data.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#include "Data.hpp"
#include "Attack.hpp"
#include <algorithm>
#include <functional>
#include <iterator>
namespace
{
struct Range
{
auto size() const -> std::size_t
{
return std::distance(begin, end);
}
auto operator<(const Range& other) const -> bool
{
return size() < other.size();
}
std::vector<std::pair<std::size_t, std::uint8_t>>::iterator begin;
std::vector<std::pair<std::size_t, std::uint8_t>>::iterator end;
};
} // namespace
Data::Error::Error(const std::string& description)
: BaseError{"Data error", description}
{
}
Data::Data(std::vector<std::uint8_t> ciphertextArg, std::vector<std::uint8_t> plaintextArg, int offsetArg,
const std::map<int, std::uint8_t>& extraPlaintextArg)
: ciphertext{std::move(ciphertextArg)}
, plaintext{std::move(plaintextArg)}
{
// validate lengths
if (ciphertext.size() < Attack::attackSize)
throw Error{"ciphertext is too small for an attack (minimum length is " + std::to_string(Attack::attackSize) +
")"};
if (ciphertext.size() < plaintext.size())
throw Error{"ciphertext is smaller than plaintext"};
// validate offsets
constexpr auto minimumOffset = -static_cast<int>(encryptionHeaderSize);
if (offsetArg < minimumOffset)
throw Error{"plaintext offset " + std::to_string(offsetArg) + " is too small (minimum is " +
std::to_string(minimumOffset) + ")"};
if (ciphertext.size() < encryptionHeaderSize + offsetArg + plaintext.size())
throw Error{"plaintext offset " + std::to_string(offsetArg) + " is too large"};
if (!extraPlaintextArg.empty() && extraPlaintextArg.begin()->first < minimumOffset)
throw Error{"extra plaintext offset " + std::to_string(extraPlaintextArg.begin()->first) +
" is too small (minimum is " + std::to_string(minimumOffset) + ")"};
if (!extraPlaintextArg.empty() && ciphertext.size() <= encryptionHeaderSize + extraPlaintextArg.rbegin()->first)
throw Error{"extra plaintext offset " + std::to_string(extraPlaintextArg.rbegin()->first) + " is too large"};
// shift offsets to absolute values
offset = encryptionHeaderSize + offsetArg;
std::transform(extraPlaintextArg.begin(), extraPlaintextArg.end(), std::back_inserter(extraPlaintext),
[](const std::pair<int, std::uint8_t>& extra) {
return std::pair{encryptionHeaderSize + extra.first, extra.second};
});
// merge contiguous plaintext with adjacent extra plaintext
{
// Split extra plaintext into three ranges:
// - [extraPlaintext.begin(), before) before contiguous plaintext
// - [before, after) overlapping contiguous plaintext
// - [after, extraPlaintext.end()) after contiguous plaintext
auto before = std::lower_bound(extraPlaintext.begin(), extraPlaintext.end(), std::pair{offset, std::uint8_t{}});
auto after =
std::lower_bound(before, extraPlaintext.end(), std::pair{offset + plaintext.size(), std::uint8_t{}});
// overwrite overlapping plaintext
std::for_each(before, after,
[this](const std::pair<std::size_t, std::uint8_t>& e)
{ plaintext[e.first - offset] = e.second; });
// merge contiguous plaintext with extra plaintext immediately before
while (before != extraPlaintext.begin() && (before - 1)->first == offset - 1)
{
plaintext.insert(plaintext.begin(), (--before)->second);
offset--;
}
// merge contiguous plaintext with extra plaintext immediately after
while (after != extraPlaintext.end() && after->first == offset + plaintext.size())
plaintext.push_back((after++)->second);
// discard merged extra plaintext
extraPlaintext.erase(before, after);
}
// find the longest contiguous sequence in extra plaintext and use is as contiguous plaintext if sensible
{
auto range = Range{extraPlaintext.begin(), extraPlaintext.begin()}; // empty
for (auto it = extraPlaintext.begin(); it != extraPlaintext.end();)
{
auto current = Range{it, ++it};
while (it != extraPlaintext.end() && it->first == (current.end - 1)->first + 1)
current.end = ++it;
range = std::max(range, current);
}
if (plaintext.size() < range.size())
{
const auto plaintextSize = plaintext.size();
const auto rangeOffset = range.begin->first;
// append last bytes from the range to contiguous plaintext
for (auto i = plaintextSize; i < range.size(); i++)
plaintext.push_back(range.begin[i].second);
// remove those bytes from the range
range.end = extraPlaintext.erase(range.begin + plaintextSize, range.end);
if (plaintextSize == 0)
range.begin = range.end;
// rotate extra plaintext so that it will be sorted at the end of this scope
{
auto before =
std::lower_bound(extraPlaintext.begin(), extraPlaintext.end(), std::pair{offset, std::uint8_t{}});
if (offset < rangeOffset)
range = {before, std::rotate(before, range.begin, range.end)};
else
range = {std::rotate(range.begin, range.end, before), before};
}
// swap bytes between the former contiguous plaintext and the beginning of the range
for (auto i = std::size_t{}; i < plaintextSize; i++)
{
range.begin[i].first = offset + i;
std::swap(plaintext[i], range.begin[i].second);
}
offset = rangeOffset;
}
}
// check that there is enough known plaintext
if (plaintext.size() < Attack::contiguousSize)
throw Error{"not enough contiguous plaintext (" + std::to_string(plaintext.size()) +
" bytes available, minimum is " + std::to_string(Attack::contiguousSize) + ")"};
if (plaintext.size() + extraPlaintext.size() < Attack::attackSize)
throw Error{"not enough plaintext (" + std::to_string(plaintext.size() + extraPlaintext.size()) +
" bytes available, minimum is " + std::to_string(Attack::attackSize) + ")"};
// reorder remaining extra plaintext for filtering
{
auto before = std::lower_bound(extraPlaintext.begin(), extraPlaintext.end(), std::pair{offset, std::uint8_t{}});
std::reverse(extraPlaintext.begin(), before);
std::inplace_merge(
extraPlaintext.begin(), before, extraPlaintext.end(),
[this](const std::pair<std::size_t, std::uint8_t>& a, const std::pair<std::size_t, std::uint8_t>& b)
{
constexpr auto absdiff = [](std::size_t x, std::size_t y) { return x < y ? y - x : x - y; };
return absdiff(a.first, offset + Attack::contiguousSize) <
absdiff(b.first, offset + Attack::contiguousSize);
});
}
// compute keystream
std::transform(plaintext.begin(), plaintext.end(), ciphertext.begin() + offset, std::back_inserter(keystream),
std::bit_xor<>());
}