class IPAddressCrypt(object): """Anonymize IP addresses keepting prefix consitency. Mapping special purpose ranges to special purpose ranges """ def __init__(self, key, no_anonymize=_no_anonymize, preserve_prefix=None, debug=False): """ Args: key (bytes): 32 bytes key to be passed to CryptoPAn. no_anonymize (function): IPAddress, Bool -> bool return true if address should not be anonymized at all. preserve_prefix (list<ipaddress.ip_network>): List of network prefixes where only the host part should be anonymized. """ self.cp = CryptoPAn(key) self._no_anonymize = no_anonymize self.debug = debug if preserve_prefix is None: self._preserve_prefix = [] else: self._preserve_prefix = preserve_prefix def get_preserve_prefix_net(self, ip): # check for ip versions necessary? preserve = [net for net in self._preserve_prefix if net.version == ip.version] for net in preserve: if ip in net: return net return None def is_preserve_prefix(self, ip): return self.get_preserve_prefix_net(ip) is not None def anonymize(self, ip): """Anonymize ip address""" ip = ip_address(ip) if self._no_anonymize(ip, self.debug): return ip elif self.is_preserve_prefix(ip): net = self.get_preserve_prefix_net(ip) ip_anonymized = ip_address(self.cp.anonymize(ip)) return _overwrite_prefix(ip_anonymized, net) else: ip_anonymized = ip_address(self.cp.anonymize(ip)) # Fail if anonymized IP is accidentally mapped to some special IP if self._no_anonymize(ip_anonymized, debug=False): print("INFO: anonymized ip {} mapped to a special ip which should " "not be anonymized ({}). Please re-run with a different key" .format(ip, ip_anonymized), file=sys.stderr) sys.exit(1) if self.is_preserve_prefix(ip_anonymized): print("INFO: anonymized ip {} mapped to special " "address range ({} in {}). " "Please re-run with a different key" .format(ip, ip_anonymized, self.get_preserve_prefix_net(ip_anonymized)), file=sys.stderr) sys.exit(1) return ip_anonymized
def test_ipv6_prefix_preserving(self): """the same as test_ipv6_prefix_preserving_least_significant_random but shift the ipv4 addresses to higher positions. For each ip, fill the lower bits with random. May take some time to complete.""" cp = CryptoPAn(b''.join([chr(x) for x in self.key])) print("This test may take some time to complete.") for i in range(96): prefix = (random.randint(0, (2**(96 - i)) - 1)) << (32 + i) def to_ip6(ip): ip = int(netaddr.IPAddress(ip, version=4)) ip = netaddr.IPAddress(prefix + (ip << i) + random.randint(0, (2**i) - 1), version=6) return ip.format(netaddr.ipv6_verbose) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long assert len(raw_ip6) == 39 cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws, prefix_offset=96 - i) self.prefix_preserving(anons, prefix_offset=96 - i)
def test_ipv6_prefix_preserving_least_significant_random(self): """Map the testvector ipv4 address into the lower 32 bit of an ipv6 address. Add a random but fixed prefix for all addresses. Check that anonymization is still prefix preserving.""" prefix = (random.randint(0, (2**96) - 1)) << 32 def to_ip6(ip): ip = int(mk_ip_address(ip, version=4)) ip = mk_ip_address(prefix + ip, version=6) return format_ip_verbose(ip) cp = CryptoPAn(bytes(self.key)) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long self.assertEqual(len(raw_ip6), 39) cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws, prefix_offset=96) self.prefix_preserving(anons, prefix_offset=96)
def test_ipv6_prefix_preserving(self): """the same as test_ipv6_prefix_preserving_least_significant_random but shift the ipv4 addresses to higher positions. For each ip, fill the lower bits with random. May take some time to complete.""" cp = CryptoPAn(bytes(self.key)) print("This test may take some time to complete.") for i in range(96): prefix = (random.randint(0, (2**(96 - i)) - 1)) << (32 + i) raws = [] anons = [] for (raw, _) in self.testvector: #to IPv6 by shifting to the left and filling with garbage on the right raw_ip6 = mk_ip_address( prefix + (int(mk_ip_address(raw, version=4)) << i) + random.randint(0, (2**i) - 1), version=6) raw_ip6 = format_ip_verbose(raw_ip6) # the verbose ipv6 string is 39 chars long self.assertEqual(len(raw_ip6), 39) cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws, prefix_offset=96 - i) self.prefix_preserving(anons, prefix_offset=96 - i)
class IPAddressCrypt(object): """Anonymize IP addresses keepting prefix consitency. Mapping special purpose ranges to special purpose ranges """ def __init__(self, key): self.cp = CryptoPAn(key) def is_special_purpose_ipv4(self, ip): for net in special_purpose_ipv4: if ip in net: return True return False def do_not_anonymize(self, ip): ip = netaddr.IPAddress(ip) if ip.version == 4: return self.is_special_purpose_ipv4(ip) else: return False # IPv6 addresses can have MACs embedded # be super conservative and anonymize them all def anonymize(self, ip): if self.do_not_anonymize(ip): # TODO anonymize but completely keep the prefix (i.e. only anonymize the least significant bits) return ip else: ip_anonymized = self.cp.anonymize(ip) if self.do_not_anonymize(ip_anonymized): # TODO: anonymize again until we are in a `good` range? printStdErr( "WARNING: anonymized ip address mapped to special-purpose address range. Please consider re-running with different key" ) return ip_anonymized
def test_ipv6_prefix_preserving(self): """the same as test_ipv6_prefix_preserving_least_significant_random but shift the ipv4 addresses to higher positions. For each ip, fill the lower bits with random. May take some time to complete.""" cp = CryptoPAn(b''.join([chr(x) for x in self.key])) print("This test may take some time to complete.") for i in range(96): prefix = (random.randint(0, (2**(96-i)) - 1)) << (32+i) def to_ip6(ip): ip = int(netaddr.IPAddress(ip, version=4)) ip = netaddr.IPAddress(prefix + (ip<<i) + random.randint(0, (2**i) - 1), version=6) return ip.format(netaddr.ipv6_verbose) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long assert len(raw_ip6) == 39 cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws, prefix_offset=96-i) self.prefix_preserving(anons, prefix_offset=96-i)
def test_ipv6_prefix_preserving_least_significant_random(self): """Map the testvector ipv4 address into the lower 32 bit of an ipv6 address. Add a random but fixed prefix for all addresses. Check that anonymization is still prefix preserving.""" prefix = (random.randint(0, (2**96) - 1)) << 32 def to_ip6(ip): ip = int(netaddr.IPAddress(ip, version=4)) ip = netaddr.IPAddress(prefix + ip, version=6) return ip.format(netaddr.ipv6_verbose) cp = CryptoPAn(b''.join([chr(x) for x in self.key])) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long assert len(raw_ip6) == 39 cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws, prefix_offset=96) self.prefix_preserving(anons, prefix_offset=96)
class IPAddressCrypt(object): """Anonymize IP addresses keepting prefix consitency. Mapping special purpose ranges to special purpose ranges """ def __init__(self, key): self.cp = CryptoPAn(key) def is_special_purpose_ipv4(self, ip): for net in special_purpose_ipv4: if ip in net: return True return False def do_not_anonymize(self, ip): ip = netaddr.IPAddress(ip) if ip.version == 4: return self.is_special_purpose_ipv4(ip) else: return False #IPv6 addresses can have MACs embedded #be super conservative and anonymize them all def anonymize(self, ip): if self.do_not_anonymize(ip): #TODO anonymize but completely keep the prefix (i.e. only anonymize the least significant bits) return ip else: ip_anonymized = self.cp.anonymize(ip) if self.do_not_anonymize(ip_anonymized): #TODO: anonymize again until we are in a `good` range? printStdErr("WARNING: anonymized ip address mapped to special-purpose address range. Please consider re-running with different key") return ip_anonymized
def test_sample_trace(self): cp = CryptoPAn(bytes(self.key)) for (raw, anon) in self.testvector: self.assertEqual(cp.anonymize(raw), anon) print( "sucessfully checked the %d IPv4s of the reference implementation" % len(self.testvector))
def fake_user_id(self, user): faker = Faker() cp = CryptoPAn(current_app.config.get('CRYPTOPAN_KEY')) task_runs = self.db.session.query(TaskRun).filter_by(user_id=user.id) for tr in task_runs: tr.user_id = None tr.user_ip = cp.anonymize(faker.ipv4()) self.db.session.merge(tr) self.db.session.commit()
def get_user_id_or_ip(): """Return the id of the current user if is authenticated. Otherwise returns its IP address (defaults to 127.0.0.1). """ cp = CryptoPAn(current_app.config.get('CRYPTOPAN_KEY')) user_id = current_user.id if current_user.is_authenticated() else None user_ip = cp.anonymize(request.remote_addr or "127.0.0.1") \ if current_user.is_anonymous() else None external_uid = request.args.get('external_uid') return dict(user_id=user_id, user_ip=user_ip, external_uid=external_uid)
def get_user_id_or_ip(): """Return the id of the current user if is authenticated. Otherwise returns its IP address (defaults to 127.0.0.1). """ cp = CryptoPAn(str.encode(current_app.config.get('CRYPTOPAN_KEY'))) user_id = current_user.id if current_user.is_authenticated else None user_ip = cp.anonymize(request.remote_addr or "127.0.0.1") \ if current_user.is_anonymous else None external_uid = request.args.get('external_uid') return dict(user_id=user_id, user_ip=user_ip, external_uid=external_uid)
def test_ipv6_prefix_preserving_prepend_reference(self): """The test vector of the reference implementation is hacky-transformed to IPv6 addresses. The most significant bits of the IPv6 address are simply set to the 32 bit of the IPv4 address. We check that after anonymizing, the thing is still prefix-preserving. Converting back to IPv4 (extracting the 32 most significant bits), the same result as in IPv4 reference anonymization is computed.""" def to_ip6(ip): ip = int(mk_ip_address(ip, version=4)) ip = mk_ip_address(ip << 96, version=6) return format_ip_verbose(ip) cp = CryptoPAn(bytes(self.key)) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long self.assertEqual(len(raw_ip6), 39) cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws) self.prefix_preserving(anons) # get the expected result back if we convert back to ipv4 def from_ip6(ip): ip = mk_ip_address(ip, version=6) ip = format_ip_verbose(ip)[:9] ip = "%s::0" % ip ip = int(mk_ip_address(ip, version=6)) ip = ip >> 96 ip = mk_ip_address(ip, version=4) return "%s" % ip for i in range(len(self.testvector)): anonymized = anons[i] (sanity_check_raw, expected) = self.testvector[i] #sanity check: converting back to IPv4 gives the starting value self.assertEqual(sanity_check_raw, from_ip6(raws[i])) #anonymizing as IPv6 yields the same testvector result self.assertEqual(from_ip6(anonymized), expected)
def test_ipv6_prefix_preserving_prepend_reference(self): """The test vector of the reference implementation is hacky-transformed to IPv6 addresses. The most significant bits of the IPv6 address are simply set to the 32 bit of the IPv4 address. We check that after anonymizing, the thing is still prefix-preserving. Converting back to IPv4 (extracting the 32 most significant bits), the same result as in IPv4 reference anonymization is computed.""" def to_ip6(ip): ip = int(netaddr.IPAddress(ip, version=4)) ip = netaddr.IPAddress(ip << 96, version=6) return ip.format(netaddr.ipv6_verbose) cp = CryptoPAn(b''.join([chr(x) for x in self.key])) raws = [] anons = [] for (raw, _) in self.testvector: raw_ip6 = to_ip6(raw) # the verbose ipv6 string is 39 chars long assert len(raw_ip6) == 39 cp_ip6 = cp.anonymize(raw_ip6) raws.append(raw_ip6) anons.append(cp_ip6) self.prefix_preserving(raws) self.prefix_preserving(anons) # get the expected result back if we convert back to ipv4 def from_ip6(ip): ip = netaddr.IPAddress(ip, version=6) ip = ip.format(netaddr.ipv6_verbose)[:9] ip = "%s::0" % ip ip = int(netaddr.IPAddress(ip, version=6)) ip = ip >> 96 ip = netaddr.IPAddress(ip, version=4) return "%s" % ip for i in range(len(self.testvector)): anonymized = anons[i] (sanity_check_raw, expected) = self.testvector[i] #sanity check: converting back to IPv4 gives the starting value self.assertEqual(sanity_check_raw, from_ip6(raws[i])) #anonymizing as IPv6 yields the same testvector result self.assertEqual(from_ip6(anonymized), expected)
#!/bin/python2 from yacryptopan import CryptoPAn FILE="IP.csv" cp = CryptoPAn('32-char-str-for-AES-key-and-pad.') with open(FILE, "r") as input: flines = input.readlines() for plaintext in flines: plaintext = plaintext.strip() print (cp.anonymize(plaintext))
def test_ipv6_hamming(self): """The hamming distance between entcrypted IPv6 addresses which do not share a common prefix is huge. Where huge means roughly the amount of bits not in the common prefix devided by two. There is a chance of 50% that two perfectly randomly selected bits are equal. Consequently, about 50% should not be equal. Example: Consider the following two ipv6 addresses in binary: ip1 = 10...0 # a one followed by 127 zeros ip2 = 0 The Hamming distance of ip1 and ip2 is one: Only the most significant bit is different. If we encrypt ip1 and ip2, there is a chance for every bit of 50% that the bit was changed. This means, per bit, 25% chance that the bit of both ip1 and ip2 was changed and 25% chance that both bits were not changed. Consequently, for each bit in ip1 and ip2, a 50% chance that the bits are equal after encryption (assuming perfect encryption). Since IPv6 addresses are 128 bit, on average, the Hamming distance of the encrypted ip1 and ip2 should be 64. """ #random key! cp = CryptoPAn(b''.join([chr(random.randint(0,255)) for x in self.key])) print("This test may _sometimes_ fail.") def ipv6_bin(ip): ip = bin(int(netaddr.IPAddress(ip, version=6))) ip = ip[2:] #strip 0b prefix ip = ip.rjust(128, b'0') return ip def hamming_distance(ip1, ip2): difference = 0 for (b1, b2) in zip(ipv6_bin(ip1), ipv6_bin(ip2)): if b1 != b2: difference += 1 return difference self.assertEqual(hamming_distance("::1", "::2"), 2) self.assertEqual(hamming_distance(1, 2), 2) self.assertEqual(hamming_distance(0, 1 << 127), 1) dist = hamming_distance(0, cp.anonymize("::0")) self.assertGreater(dist, 40) dist = hamming_distance(1 << 127, cp.anonymize(netaddr.IPAddress(1 << 127))) self.assertGreaterEqual(dist, 44) self.assertLessEqual(dist, 84) # hamming distance of unencrypted IPs was 1 # encrypted, it should be on average 64!! dist = hamming_distance(cp.anonymize(netaddr.IPAddress(1 << 127)), cp.anonymize("::0")) self.assertGreaterEqual(dist, 44) self.assertLessEqual(dist, 84) print("Running 10000 test, this may take some time, ...") avg_dist = 0 # NOTE: this is a random test, it may occasionally fail for _ in range(10000): rnd = random.randint(0, (2**127) - 1) ip1 = netaddr.IPAddress(rnd) ip2 = netaddr.IPAddress((1 << 127) + rnd) # unencrypted: hamming distance is 1 self.assertEqual(hamming_distance(ip1, ip2), 1) # encrypted: hamming distance high! dist = hamming_distance(cp.anonymize(ip1), cp.anonymize(ip2)) self.assertGreaterEqual(dist, 14) self.assertLessEqual(dist, 114) avg_dist += dist avg_dist = avg_dist / 10000.0 print("Average Hamming distance %s (ideal: 64)" % (avg_dist)) self.assertGreaterEqual(avg_dist, 54) self.assertLessEqual(avg_dist, 74) print("test did not fail")
ip = inputFile.readline() while ip != '': if (ip not in uniqueRealIPMap): #only take unique IPs from the input file uniqueRealIPMap[ip] = 1 ip = inputFile.readline() else: uniqueRealIPMap[ip] += 1 for ip_ in iter(uniqueRealIPMap): uniqueFile.write(ip_) #add real IP to unique_real_IP file ip = ip_ ip = list(ip) #convert the line from string to list ip.pop() #remove '\n' ip = ''.join(ip) #put the list back into a string anonymized_ip = cp.anonymize(ip) #anonymize the IP anonFile.write(anonymized_ip + '\n') #add it to anonymized_IP file frequenciesFile.write(str(uniqueRealIPMap[ip_]) + '\n') #write frequency if printBool: print("ip:", ip) if printBool: print("anon_ip:", anonymized_ip, '\n') inputFile.close() anonFile.close() uniqueFile.close() frequenciesFile.close()
def getCryptopan(address): cp = CryptoPAn( b'\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) return cp.anonymize(address)
class IPAddressCrypt(object): """Anonymize IP addresses keepting prefix consitency. Mapping special purpose ranges to special purpose ranges """ def __init__(self, key, no_anonymize=_no_anonymize, preserve_prefix=None, debug=False): """ Args: key (bytes): 32 bytes key to be passed to CryptoPAn. no_anonymize (function): IPAddress, Bool -> bool return true if address should not be anonymized at all. preserve_prefix (list<ipaddress.ip_network>): List of network prefixes where only the host part should be anonymized. """ self.cp = CryptoPAn(key) self._no_anonymize = no_anonymize self.debug = debug if preserve_prefix is None: self._preserve_prefix = [] else: self._preserve_prefix = preserve_prefix def get_preserve_prefix_net(self, ip): # check for ip versions necessary? preserve = [ net for net in self._preserve_prefix if net.version == ip.version ] for net in preserve: if ip in net: return net return None def is_preserve_prefix(self, ip): return self.get_preserve_prefix_net(ip) is not None def anonymize(self, ip): """Anonymize ip address""" ip = ip_address(ip) if self._no_anonymize(ip, self.debug): return ip elif self.is_preserve_prefix(ip): net = self.get_preserve_prefix_net(ip) ip_anonymized = ip_address(self.cp.anonymize(ip)) return _overwrite_prefix(ip_anonymized, net) else: ip_anonymized = ip_address(self.cp.anonymize(ip)) # Fail if anonymized IP is accidentally mapped to some special IP if self._no_anonymize(ip_anonymized, debug=False): print( "INFO: anonymized ip {} mapped to a special ip which should " "not be anonymized ({}). Please re-run with a different key" .format(ip, ip_anonymized), file=sys.stderr) sys.exit(1) if self.is_preserve_prefix(ip_anonymized): print("INFO: anonymized ip {} mapped to special " "address range ({} in {}). " "Please re-run with a different key".format( ip, ip_anonymized, self.get_preserve_prefix_net(ip_anonymized)), file=sys.stderr) sys.exit(1) return ip_anonymized
def test_sample_trace(self): cp = CryptoPAn(b''.join([chr(x) for x in self.key])) for (raw, anon) in self.testvector: self.assertEqual(cp.anonymize(raw), anon) print("sucessfully checked the %d IPv4s of the reference implementation" % len(self.testvector))
def test_ipv6_hamming(self): """The hamming distance between entcrypted IPv6 addresses which do not share a common prefix is huge. Where huge means roughly the amount of bits not in the common prefix devided by two. There is a chance of 50% that two perfectly randomly selected bits are equal. Consequently, about 50% should not be equal. Example: Consider the following two ipv6 addresses in binary: ip1 = 10...0 # a one followed by 127 zeros ip2 = 0 The Hamming distance of ip1 and ip2 is one: Only the most significant bit is different. If we encrypt ip1 and ip2, there is a chance for every bit of 50% that the bit was changed. This means, per bit, 25% chance that the bit of both ip1 and ip2 was changed and 25% chance that both bits were not changed. Consequently, for each bit in ip1 and ip2, a 50% chance that the bits are equal after encryption (assuming perfect encryption). Since IPv6 addresses are 128 bit, on average, the Hamming distance of the encrypted ip1 and ip2 should be 64. """ #random key! cp = CryptoPAn(bytes([random.randint(0, 255) for _ in range(32)])) print("This test may _sometimes_ fail.") def ipv6_bin(ip): return "{:0128b}".format(int(mk_ip_address(ip, version=6))) def hamming_distance(ip1, ip2): difference = 0 for (b1, b2) in zip(ipv6_bin(ip1), ipv6_bin(ip2)): if b1 != b2: difference += 1 return difference self.assertEqual(hamming_distance("::1", "::2"), 2) self.assertEqual(hamming_distance(1, 2), 2) self.assertEqual(hamming_distance(0, 1 << 127), 1) dist = hamming_distance(0, cp.anonymize("::0")) self.assertGreater(dist, 40) dist = hamming_distance(1 << 127, cp.anonymize(mk_ip_address(1 << 127))) self.assertGreaterEqual(dist, 44) self.assertLessEqual(dist, 84) # hamming distance of unencrypted IPs was 1 # encrypted, it should be on average 64!! dist = hamming_distance(cp.anonymize(mk_ip_address(1 << 127)), cp.anonymize("::0")) self.assertGreaterEqual(dist, 44) self.assertLessEqual(dist, 84) print("Running 10000 test, this may take some time, ...") avg_dist = 0 # NOTE: this is a random test, it may occasionally fail for _ in range(10000): rnd = random.randint(0, (2**127) - 1) ip1 = mk_ip_address(rnd) ip2 = mk_ip_address((1 << 127) + rnd) # unencrypted: hamming distance is 1 self.assertEqual(hamming_distance(ip1, ip2), 1) # encrypted: hamming distance high! dist = hamming_distance(cp.anonymize(ip1), cp.anonymize(ip2)) self.assertGreaterEqual(dist, 14) self.assertLessEqual(dist, 114) avg_dist += dist avg_dist = avg_dist / 10000.0 print("Average Hamming distance %s (ideal: 64)" % (avg_dist)) self.assertGreaterEqual(avg_dist, 54) self.assertLessEqual(avg_dist, 74) print("test did not fail")
#!/usr/bin/env python from __future__ import print_function import sys import time from yacryptopan import CryptoPAn count = int(sys.argv[1]) cp = CryptoPAn(b'32-char-str-for-AES-key-and-pad.') stime = time.time() for i in range(0, count): cp.anonymize('192.0.2.1') dtime = time.time() - stime print("%d anonymizations in %s s" % (count, dtime)) print("rate: %f anonymizations /sec " % (count / dtime))
#!/bin/python2 from yacryptopan import CryptoPAn FILE="IP.csv" cp = CryptoPAn('32-char-str-for-AES-key-and-pad.') with open(FILE, "r") as input: flines = input.readlines() for plaintext in flines: plaintext = plaintext.strip() print "{},{}".format(cp.anonymize(plaintext),plaintext)
from yacryptopan import CryptoPAn cp = CryptoPAn(b'34-char-str-for-AES-key-and-pad.') cp2 = CryptoPAn(b'34-char-str-for-AES-key-and-ped.') ip = '192.168.1.1' a = cp.anonymize(ip) b = cp2.anonymize(ip) print(a) print(b) a2 = cp.anonymize(b) b2 = cp2.anonymize(a) print(a2) print(b2)
#!/bin/python2 from yacryptopan import CryptoPAn FILE = "IP.csv" cp = CryptoPAn('32-char-str-for-AES-key-and-pad.') with open(FILE, "r") as input: flines = input.readlines() for plaintext in flines: plaintext = plaintext.strip() print "{},{}".format(plaintext, cp.anonymize(plaintext))