I'm trying to implement the FMS attack on WEP. I understand that the attack takes advantage of the probability of parts of the RC4 sbox not changing to create "known" sbox states to reverse engineer the key. With many samples, the correct key octet should appear more often than noise.
The value that should add to the frequency count is:
where (I think; the notation is not properly defined)
- B starts at 0
- P.out is the outputted keystream byte
- S is the Sbox
- j is the "pointer" used in the RC4 key scheduling algorithm
In my code, I am generating 6 million data packets: a constant root key and constant plaintext to simulate constant header, and then encrypting with RC4(IV + root_key).encrypt(plaintext)
, without discarding the first 256 octets). The (IV, encrypted_data)
pairs are run through the get_key
function:
uint8_t RC4_ksa(const std::string & k, std::array <uint8_t, 256> & s, const uint16_t octets = 256){
for(uint16_t i = 0; i < 256; i++){
s[i] = i;
}
uint8_t j = 0;
for(uint16_t i = 0; i < octets; i++){
j = (j + s[i] + k[i % k.size()]);
std::swap(s[i], s[j]);
}
return j;
}
std::string get_key(const uint8_t keylen, const std::vector <std::pair <std::string, std::string> > & captured){
std::string rkey = ""; // root key to build
const std::string & pt = header; // "plaintext" with constant header
// recreate root key one octet at a time
for(uint8_t i = 3; i < keylen; i++){
// vote counter for current octet
std::array <unsigned int, 256> votes;
votes.fill(0);
uint8_t most = 0; // most probable index/octet value
// get vote from each "captured" ciphertext
for(std::pair <std::string, std::string> const & c : captured){
const std::string & IV = c.first;
// IV should be of form (i = root key index + 3, 255, some value)
if ((static_cast<uint8_t> (IV[0]) != i) ||
(static_cast<uint8_t> (IV[1]) != 0xff)){
continue; // skip this data
}
const std::string & ct = c.second;
const std::string key = IV + rkey;
// find current packet's vote
std::array <uint8_t, 256> sbox; // SBox after simulating; fill with RC4_ksa
uint8_t j = RC4_ksa(key, sbox, i); // simulate using key in KSA, up to known octets only
uint8_t keybytestream = pt[i - 3] ^ ct[i - 3];
// S^-1[keybytestream]
uint16_t sinv;
for(sinv = 0; sinv < 256; sinv++){
if (sbox[sinv] == keybytestream){
break;
}
}
// get mapping
uint8_t ki = sinv - j - sbox[i];
// add to tally and keep track of which tally is highest
votes[ki]++;
if (votes[ki] > votes[most]){
most = ki;
}
}
// select highest voted value as next key octet
rkey += std::string(1, most);
}
return rkey;
}
I am getting keys that are completely incorrect. I feel that the error is probably a one-off error or something silly like that, but I have asked two people to look at this, and neither person has managed to figure out what is wrong.
Is there something that is blatantly wrong? If not, what is not-so-obviously wrong?