As we are not able to properly encrypt the traffic, let's make the vote-code interception more uncomfortable and effort-intensive.
The traffic interception tool may catch the options list when it is deployed to a client and the vote-code when a user votes.
By randomizing vote-codes on a daily basis we'll make it hard enough to track the votes history of an individual machine or at scale. It is not impossible but becomes a costly IT that should be hard to justify.
This combined with the fact that the vote may only be tight to a machine but not the individual makes the obfuscation mechanism sufficient for the use case.
In [1]:
from itertools import permutations
from hashlib import md5
from time import time
from random import shuffle, choice
class VoteCodeObfuscator(object):
"""Provides the obfuscation means for the interceptable traffic"""
def __init__(self, options=None, variants=None):
"""provides basic interfaces for unit testing"""
self.coded_options = None
self.variants = variants
self.options = options
def encode_options(self):
"""Create pseudo-random option keys"""
salted = []
for i, opt in enumerate(self.options):
salted.append(md5(str(i) + str(time())).hexdigest())
self.coded_options = salted
def create_variants(self):
"""Creates vote-code combinations"""
temp_variants = []
salted = []
for opt in self.options:
salted.append(md5(opt + str(time())).hexdigest())
shuffle(self.options)
for variant in permutations(salted):
temp_variants.append(variant)
self.variants = dict(zip(
[md5(str(x)).hexdigest() for x in temp_variants],
temp_variants))
def deploy(self):
"""Returns a random variant for deployment"""
key = choice(self.variants.keys())
return {"key":key, "options":self.variants[key]}
def decode(self, combination_key, coded_option):
"""Returns the decoded option code
If the inputs are expired returns False"""
if combination_key not in self.variants.keys():
return False
variant = self.variants[combination_key]
if coded_option not in variant:
return False
return self.options[variant.index(coded_option)]
In [2]:
moods_obfuscator = VoteCodeObfuscator(
options=["opt1", "opt2", "opt3", "opt4"])
moods_obfuscator.create_variants()
Each time a client requiests moods widget deployment, the server will reply with a random variant like below:
In [3]:
moods_obfuscator.deploy()
Out[3]:
When a user votes, the client sends back the variant code and the option code. A worker picks up the message and decodes it to get the actual opton code. An option code is then used to perform +1 operation on the column within the daily metrics that matches the option key
In [4]:
print moods_obfuscator.decode(
'b55645bd979c9dc9f1a8e18289d4f4d8',
'396f87befcc04b908055d403f5c4ee49')
if a user sends the vote from a widget session that was deployed say a day ago decoding will fail and return False. The same obfuscation logic is used for the "why-bad" poll.
In [5]:
print moods_obfuscator.decode(
'968c48a1a75ff8beabd6f4ecf0cbf0dc',
'edda6cc6dbc632384664964259d6b694')