├── LICENSE ├── clientNTRU.py ├── protocolNTRU.py ├── instructions.txt ├── README.md ├── irisNTRU.py ├── sortNTRU.py ├── ttpNTRU.py ├── serverNTRU.py ├── biometricNTRU.py └── timingNTRU.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Jascha Kolberg 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /clientNTRU.py: -------------------------------------------------------------------------------- 1 | """client for efficient NTRU iris recognition""" 2 | from irisNTRU import IRIS 3 | from sortNTRU import sortIrisLinesForClient 4 | 5 | 6 | class CLIENT(object): 7 | """docstring for client""" 8 | def __init__(self, public_ring, public_key): 9 | super(CLIENT, self).__init__() 10 | self.ring = public_ring 11 | self.pk = public_key 12 | 13 | def CLIENTencryptProbe(self, id): 14 | # open file 15 | probe_shifted = [] 16 | icode = IRIS.readIrisLinesFromFile(IRIS, id[0], id[1]) 17 | tmp = sortIrisLinesForClient(icode) 18 | probe_shifted += (IRIS.encryptBlocks(IRIS, self.pk, self.ring, tmp)) 19 | 20 | # shift & encrypt 21 | if IRIS.NRSHIFTS > 1: 22 | for i in range(1, IRIS.NRSHIFTS >> 1 | 1): 23 | tmp = sortIrisLinesForClient(IRIS.shiftIrisCode(IRIS, icode, -i)) # shift -i 24 | probe_shifted += (IRIS.encryptBlocks(IRIS, self.pk, self.ring, tmp)) 25 | tmp = sortIrisLinesForClient(IRIS.shiftIrisCode(IRIS, icode, i)) # shift i 26 | probe_shifted += (IRIS.encryptBlocks(IRIS, self.pk, self.ring, tmp)) 27 | 28 | # return 17 encrypted probes 29 | icode.clear() 30 | tmp.clear() 31 | return probe_shifted 32 | 33 | -------------------------------------------------------------------------------- /protocolNTRU.py: -------------------------------------------------------------------------------- 1 | import time 2 | from ttpNTRU import TTP 3 | from clientNTRU import CLIENT 4 | from serverNTRU import SERVER 5 | 6 | ENROLLED = 1 # which sample is in database; nr from 1 to 5 7 | 8 | if __name__ == '__main__': 9 | security_in_bits = 256 # {112, 128, 192, 256} 10 | factor_keep = 0.25 11 | 12 | ttp = TTP(security_in_bits, factor_keep) 13 | # ttp.FACTOR_KEEP = 0.125 # it is possible to adjust this factor during runtime 14 | 15 | id_probe = (174, 5) 16 | 17 | print('factor keep:', ttp.FACTOR_KEEP) 18 | 19 | print('ID probe:', id_probe) 20 | 21 | client1 = CLIENT(ttp.ring, ttp.pk) 22 | 23 | t0 = time.time() 24 | server1 = SERVER(ttp.ring, ttp.pk, ENROLLED) 25 | t1 = time.time() 26 | 27 | probe = client1.CLIENTencryptProbe(id_probe) 28 | t2 = time.time() 29 | print('verification:', server1.SERVERverification(ttp, probe, id_probe)) 30 | t3 = time.time() 31 | print('baseline_verification:', server1.SERVERverificationAll(ttp, probe, id_probe)) 32 | t4 = time.time() 33 | print('identification with factor keep:', ttp.FACTOR_KEEP, ', ID found:', server1.SERVERidentification(ttp, probe)) 34 | t5 = time.time() 35 | 36 | print('server_init: ', t1 - t0) 37 | print('probe_encryption: ', t2 - t1) 38 | print('verification: ', t3 - t2) 39 | print('baseline_verification:', t4 - t3) 40 | print('identification: ', t5 - t4) 41 | -------------------------------------------------------------------------------- /instructions.txt: -------------------------------------------------------------------------------- 1 | 2 | ---------------------------------------- 3 | 4 | This implementation was tested with iris codes from IITD[1] and LG or QSW feature extraction from USIT[2]. 5 | It was executed with Pypy3[3] to achieve a huge speedup in contrast to Python3. 6 | 7 | protocolNTRU.py (main): 8 | - example protocol that shows how to use verification and identification 9 | 10 | clientNTRU.py 11 | - shift/rotate iris code of the probe 12 | - encrypt shifted probe 13 | 14 | serverNTRU.py 15 | - database server 16 | - init database (encrypt all references) 17 | - handle verification and identification requests 18 | - calculate sums of ciphertext blocks 19 | - send encrypted sums to ttp 20 | 21 | ttpNTRU.py 22 | - authentication server 23 | - trusted third party (only party that is in possesion of the secret key to decrypt) 24 | - system thresholds are set here (computed for LG algorithm) 25 | - determine Hamming distance of encrypted sum and compare against threshold 26 | - return comparison decision to server 27 | 28 | irisNTRU.py 29 | - constant values and helper functions for both client and server usage 30 | - similar to a config file 31 | 32 | sortNTRU.py 33 | - wrapper for iris code rearragement, sort most stable bits to the beginning 34 | 35 | biometricNTRU.py 36 | - implementation of the cryptographic functionalities of NTRU 37 | 38 | timingNTRU.py 39 | - function to measure the execution time 40 | 41 | 42 | ------------------------------------------------------------------ 43 | [1] http://www4.comp.polyu.edu.hk/~csajaykr/IITD/Database_Iris.htm 44 | [2] http://wavelab.at/sources/USIT/ 45 | [3] http://pypy.org/features.html 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NTRU Homomorphic Encryption for Iris-code Template Protection 2 | 3 | ### ============ Update ============== 4 | This implementation with its parameters of the NTRU homomorphic encryption scheme can not be considered as secure anymore. We suggest to use the Stehlé-Steinfeld 5 | (StSt) version of NTRU within the PALISADE crypto library instead. Their repository can be found at: https://gitlab.com/palisade/palisade-release 6 | ### ================================== 7 | 8 | The repository provides a reference implementation for Biometric Template Protection based on NTRU Homomorphic Encryption and Iris-codes. Biometric templates are stored and compared in the encrypted domain. To solve the problem of the computational overload linked to the encryption scheme, an early decision making strategy is implemented. However, in order to improve the recognition accuracy, the most consistent bits of the iris-code are moved to the beginning of the template. This allows an accurate block-wise comparison, thereby reducing the execution time. Hence, the resulting system grants template protection in a computationally efficient way. 9 | 10 | 11 | ## Warranty 12 | 13 | This software is provided "as is", without warranty of any kind. Especially the cryptographic functions are not secured against side-channel attacks etc. The whole program serves as a reference implementation on how to apply homomorphic encryption for iris-biometric template protection but qualifies not for being a secure solution. 14 | 15 | 16 | ## Attribution 17 | 18 | Any publications using the code must cite and reference the conference paper [1]. 19 | 20 | 21 | ## References 22 | 23 | * [1] Jascha Kolberg, Pia Bauspieß, Marta Gomez-Barrero, Christian Rathgeb, Markus Dürmuth and Christoph Busch. "Template Protection based on Homomorphic Encryption: Computationally Efficient Application to Iris-Biometric Verification and Identification", in IEEE Workshop on Information Forensics and Security, Delft ,Netherlands, December 2019. 24 | -------------------------------------------------------------------------------- /irisNTRU.py: -------------------------------------------------------------------------------- 1 | from biometricNTRU import NTRUEncrypt 2 | 3 | 4 | class IRIS(object): 5 | """docstring for IRIS""" 6 | """ helper functions and constant values for iris recognition with NTRU """ 7 | # set path to iris codes, e.g.: PATH="../../IITD_IrisCodes/lg_%03d%02d.png.txt" 8 | PATH = "../../../dasec/databases/IITD_IrisCodes/lg_%03d%02d.png.txt" 9 | 10 | """ since this was written for LG/QSW feature extraction[1], we assume filenames with 5 digits """ 11 | """ in format XXXYY with 3 digits (XXX) for subject nr and 2 digits (YY) sample nr with sample nr in the range """ 12 | """ from 1 to 2*NREYES since samples from both eyes of one subject are successive """ 13 | """ [1] http://wavelab.at/sources/USIT/ """ 14 | 15 | NRTRAINING = 24 # nr of subjects in training set 16 | NRENROLMENT = 150 # nr of enrolled subjects 17 | NRIMPOSTER = 50 # nr of imposters 18 | NREYES = 5 # nr of samples / eye 19 | 20 | NRSHIFTS = 17 # nr of shifts (from -8 via 0 to +8) !! must be odd and mininum 1 !! 21 | SIZE_IRISCODE = 5120 # nr of total bits in iris code 22 | ROWS_IRISCODE = 10 # nr of rows in iris code 23 | BITSPERROW_IRISCODE = 512 # bits per row in iris code 24 | 25 | def __init__(self): 26 | super(IRIS, self).__init__() 27 | 28 | def encryptBlocks(self, pk, ring, m): 29 | c = [] 30 | b = 0 31 | for block in range(0, self.SIZE_IRISCODE - ring.N, ring.N): 32 | c.append(NTRUEncrypt(ring, pk, m[block:block + ring.N])) 33 | b += 1 34 | c.append(NTRUEncrypt(ring, pk, m[b * ring.N:])) # the last block is not padded 35 | return c 36 | 37 | # ------------- HELPER FUNCTIONS ------------------------------------------------------- 38 | 39 | def shiftIrisCode(self, original, n): 40 | """ shifts each line of the original iris code by n bits and saves in shifted """ 41 | """ positive shifts are to the right or counter clockwise in the iris """ 42 | """ negative shifts are to the left or clockwise in the iris """ 43 | if n < 0: n += self.BITSPERROW_IRISCODE 44 | shifted = [0] * self.ROWS_IRISCODE 45 | for i in range(self.ROWS_IRISCODE): 46 | shifted[i] = original[i][self.BITSPERROW_IRISCODE - n:] + original[i][:self.BITSPERROW_IRISCODE - n] 47 | return shifted 48 | 49 | def readIrisBitsFromFile(self, subject, sample): 50 | """ opens the file, reads iris code, and writes each bit to iris_code """ 51 | iris_code = [] 52 | f = open(self.PATH % (subject, sample), "rb") 53 | for i in range(self.ROWS_IRISCODE - 1): 54 | for bit in range(self.BITSPERROW_IRISCODE): 55 | iris_code.append(int(f.read(1))) 56 | f.seek(+(self.BITSPERROW_IRISCODE + 2), 1) 57 | for bit in range(self.BITSPERROW_IRISCODE): 58 | iris_code.append(int(f.read(1))) 59 | f.close() 60 | return iris_code 61 | 62 | def readIrisLinesFromFile(self, subject, sample): 63 | """ opens the file, reads iris code, and writes each line i to iris_rows[i] """ 64 | iris_rows = [0] * self.ROWS_IRISCODE 65 | f = open(self.PATH % (subject, sample), "rb") 66 | for i in range(self.ROWS_IRISCODE - 1): 67 | one_row = [] 68 | for bit in range(self.BITSPERROW_IRISCODE): 69 | one_row.append(int(f.read(1))) 70 | f.seek(+(self.BITSPERROW_IRISCODE + 2), 1) 71 | iris_rows[i] = one_row 72 | one_row = [] 73 | for bit in range(self.BITSPERROW_IRISCODE): 74 | one_row.append(int(f.read(1))) 75 | iris_rows[self.ROWS_IRISCODE - 1] = one_row 76 | f.close() 77 | return iris_rows 78 | -------------------------------------------------------------------------------- /sortNTRU.py: -------------------------------------------------------------------------------- 1 | ''' 2 | wrapper to assist a future change of sorting method 3 | ''' 4 | 5 | 6 | def sortIrisLinesForClient(iris_lines): 7 | """ we need to shift the iris code before sorting/rearranging it """ 8 | """ optimised for iris codes composed of 10*512 bits """ 9 | __sorted_list = [] 10 | # sectors right and left (0347) 11 | # lines 647523 12 | __sorted_list += iris_lines[6][0:64] 13 | __sorted_list += iris_lines[6][192:320] 14 | __sorted_list += iris_lines[6][448:512] 15 | __sorted_list += iris_lines[4][0:64] 16 | __sorted_list += iris_lines[4][192:320] 17 | __sorted_list += iris_lines[4][448:512] 18 | __sorted_list += iris_lines[7][0:64] 19 | __sorted_list += iris_lines[7][192:320] 20 | __sorted_list += iris_lines[7][448:512] 21 | __sorted_list += iris_lines[5][0:64] 22 | __sorted_list += iris_lines[5][192:320] 23 | __sorted_list += iris_lines[5][448:512] 24 | __sorted_list += iris_lines[2][0:64] 25 | __sorted_list += iris_lines[2][192:320] 26 | __sorted_list += iris_lines[2][448:512] 27 | __sorted_list += iris_lines[3][0:64] 28 | __sorted_list += iris_lines[3][192:320] 29 | __sorted_list += iris_lines[3][448:512] 30 | # sectors top and bot (1256) 31 | # lines 564273 32 | __sorted_list += iris_lines[5][64:192] 33 | __sorted_list += iris_lines[5][320:448] 34 | __sorted_list += iris_lines[6][64:192] 35 | __sorted_list += iris_lines[6][320:448] 36 | __sorted_list += iris_lines[4][64:192] 37 | __sorted_list += iris_lines[4][320:448] 38 | __sorted_list += iris_lines[2][64:192] 39 | __sorted_list += iris_lines[2][320:448] 40 | __sorted_list += iris_lines[7][64:192] 41 | __sorted_list += iris_lines[7][320:448] 42 | __sorted_list += iris_lines[3][64:192] 43 | __sorted_list += iris_lines[3][320:448] 44 | # lines 8 and 9 45 | # first sectors right and left, then top and bot 46 | __sorted_list += iris_lines[8][0:64] 47 | __sorted_list += iris_lines[8][192:320] 48 | __sorted_list += iris_lines[8][448:512] 49 | __sorted_list += iris_lines[9][0:64] 50 | __sorted_list += iris_lines[9][192:320] 51 | __sorted_list += iris_lines[9][448:512] 52 | __sorted_list += iris_lines[8][64:192] 53 | __sorted_list += iris_lines[8][320:448] 54 | __sorted_list += iris_lines[9][64:192] 55 | __sorted_list += iris_lines[9][320:448] 56 | # lines 1 and 0 complete 57 | __sorted_list += iris_lines[1] 58 | __sorted_list += iris_lines[0] 59 | 60 | return __sorted_list 61 | 62 | 63 | def sortIrisBitsForServer(iris_bits): 64 | """ sorts bits, and returns a list with all sorted bits """ 65 | """ optimised for iris codes composed of 10*512 bits """ 66 | __sorted_list = [] # sorted list 67 | 68 | """sectors left and right 0347""" 69 | # row 6 sectors 0347 70 | __sorted_list += iris_bits[3072:3136] 71 | __sorted_list += iris_bits[3264:3392] 72 | __sorted_list += iris_bits[3520:3584] 73 | # row 4 sectors 0347 74 | __sorted_list += iris_bits[2048:2112] 75 | __sorted_list += iris_bits[2240:2368] 76 | __sorted_list += iris_bits[2496:2560] 77 | # row 7 sectors 0347 78 | __sorted_list += iris_bits[3584:3648] 79 | __sorted_list += iris_bits[3776:3904] 80 | __sorted_list += iris_bits[4032:4096] 81 | # row 5 sectors 0347 82 | __sorted_list += iris_bits[2560:2624] 83 | __sorted_list += iris_bits[2752:2880] 84 | __sorted_list += iris_bits[3008:3072] 85 | # rows 2-3 sectors 0347 86 | __sorted_list += iris_bits[1024:1088] 87 | __sorted_list += iris_bits[1216:1344] 88 | __sorted_list += iris_bits[1472:1600] 89 | __sorted_list += iris_bits[1728:1856] 90 | __sorted_list += iris_bits[1984:2048] 91 | """sectors top and bot 1256""" 92 | # row 5 sectors 1256 93 | __sorted_list += iris_bits[2624:2752] 94 | __sorted_list += iris_bits[2880:3008] 95 | # row 6 sectors 1256 96 | __sorted_list += iris_bits[3136:3264] 97 | __sorted_list += iris_bits[3392:3520] 98 | # row 4 sectors 1256 99 | __sorted_list += iris_bits[2112:2240] 100 | __sorted_list += iris_bits[2368:2496] 101 | # row 2 sectors 1256 102 | __sorted_list += iris_bits[1088:1216] 103 | __sorted_list += iris_bits[1344:1472] 104 | # row 7 sectors 1256 105 | __sorted_list += iris_bits[3648:3776] 106 | __sorted_list += iris_bits[3904:4032] 107 | # row 3 sectors 1256 108 | __sorted_list += iris_bits[1600:1728] 109 | __sorted_list += iris_bits[1856:1984] 110 | """most outer rows at the end""" 111 | # rows 8-9 sectors 0347 112 | __sorted_list += iris_bits[4096:4160] 113 | __sorted_list += iris_bits[4288:4416] 114 | __sorted_list += iris_bits[4544:4672] 115 | __sorted_list += iris_bits[4800:4928] 116 | __sorted_list += iris_bits[5056:5120] 117 | # row 8 sectors 1256 118 | __sorted_list += iris_bits[4160:4288] 119 | __sorted_list += iris_bits[4416:4544] 120 | # row 9 sectors 1256 121 | __sorted_list += iris_bits[4672:4800] 122 | __sorted_list += iris_bits[4928:5056] 123 | # row 1 complete 124 | __sorted_list += iris_bits[512:1024] 125 | # row 0 complete 126 | __sorted_list += iris_bits[:512] 127 | 128 | return __sorted_list 129 | -------------------------------------------------------------------------------- /ttpNTRU.py: -------------------------------------------------------------------------------- 1 | """ version for optimised ntru """ 2 | """ trusted third party with full access to NTRU key """ 3 | from biometricNTRU import * 4 | from irisNTRU import IRIS 5 | 6 | 7 | class TTP(object): 8 | """docstring for TTP""" 9 | 10 | def __init__(self, k, factor_keep): 11 | super(TTP, self).__init__() 12 | # private 13 | self.__key = NTRUKey(NTRUParams(k)) 14 | # public 15 | self.pk = self.__key.h 16 | self.ring = self.__key.ring 17 | 18 | self.FACTOR_KEEP = factor_keep 19 | 20 | self.__hd_all_identify = [] 21 | self.__min_hd_all_identify = [] 22 | 23 | """ thresholds for different block sizes """ 24 | if self.ring.N == 401: 25 | self.__accept = [110, 239, 405, 609, 801, 974, 1155, 1323, 1507, 1672, 1847, 2024, 2197] 26 | self.__reject = [172, 346, 541, 720, 893, 1069, 1233, 1402, 1567, 1755, 1911, 2139, 2274] 27 | self.blocks = 13 28 | elif self.ring.N == 439: 29 | self.__accept = [122, 273, 462, 687, 863, 1083, 1273, 1451, 1652, 1845, 2034, 2197] 30 | self.__reject = [187, 384, 593, 795, 989, 1148, 1328, 1545, 1730, 1896, 2147, 2274] 31 | self.blocks = 12 32 | elif self.ring.N == 593: 33 | self.__accept = [183, 396, 693, 962, 1232, 1471, 1738, 1999, 2197] 34 | self.__reject = [238, 530, 810, 1057, 1278, 1558, 1817, 2119, 2274] 35 | self.blocks = 9 36 | elif self.ring.N == 743: 37 | self.__accept = [214, 539, 888, 1239, 1548, 1854, 2197] 38 | self.__reject = [322, 669, 1001, 1279, 1604, 1937, 2274] 39 | self.blocks = 7 40 | 41 | """ both possible - self.pk(/ring) or the function getPublicKey()/ring """ 42 | def getPublicKey(self): 43 | return self.__key.h 44 | def getPublicRing(self): 45 | return self.__key.ring 46 | 47 | def TTPverify(self, enc_sum): 48 | """ receives all encrypted msg blocks and compares against thresholds to return a fast accept/reject """ 49 | 50 | tmp_hd = [0] * IRIS.NRSHIFTS 51 | ntmp = [0, 0, 0, 0] 52 | for block in range(self.blocks): 53 | for shift in range(IRIS.NRSHIFTS): 54 | mtmp = NTRUDecrypt(self.__key, *enc_sum[shift * self.blocks + block]) 55 | tmp_hd[shift] += mtmp.count(1) 56 | if tmp_hd[shift] < self.__accept[block]: 57 | return True 58 | if min(tmp_hd) > self.__reject[block]: 59 | return False 60 | 61 | tmp_hd.clear() 62 | return False 63 | 64 | def TTPverifyAll(self, enc_sum): 65 | """ gets hd of all blocks but accept for first matching shift """ 66 | """ since verification is quiet fast, we can keep the baseline accuracy by comparing all blocks""" 67 | 68 | tmp_hd = [0] * IRIS.NRSHIFTS 69 | ntmp = [0, 0, 0, 0] 70 | for shift in range(IRIS.NRSHIFTS): 71 | for block in range(self.blocks): 72 | mtmp = NTRUDecrypt(self.__key, *enc_sum[shift * self.blocks + block]) 73 | tmp_hd[shift] += mtmp.count(1) 74 | if tmp_hd[shift] < self.__accept[self.blocks - 1]: 75 | return True 76 | 77 | tmp_hd.clear() 78 | return False 79 | 80 | def TTPidentify(self, sums_of_1_block, block_nr): 81 | """ receives a shuffled list of sums of 1 block and the block number """ 82 | """ decrypts the sums to get the hamming weight, safes the #FACTOR_KEEP lowest hws and returns the indexes of those """ 83 | index_min_hds = [] 84 | ntmp = [0, 0, 0, 0] 85 | 86 | if block_nr == 0: 87 | self.__hd_all_identify.clear() 88 | self.__min_hd_all_identify.clear() 89 | for i in range(len(sums_of_1_block)): 90 | for shift in range(IRIS.NRSHIFTS): 91 | mtmp = NTRUDecrypt(self.__key, *sums_of_1_block[i][shift]) 92 | self.__hd_all_identify.append(mtmp.count(1)) 93 | self.__min_hd_all_identify.append((min(self.__hd_all_identify[i*IRIS.NRSHIFTS:i*IRIS.NRSHIFTS+IRIS.NRSHIFTS]), i)) 94 | 95 | else: 96 | assert(len(sums_of_1_block) == len(self.__min_hd_all_identify)) 97 | for i in range(len(sums_of_1_block)): 98 | old_index = self.__min_hd_all_identify[i][1]*IRIS.NRSHIFTS 99 | for shift in range(IRIS.NRSHIFTS): 100 | mtmp = NTRUDecrypt(self.__key, *sums_of_1_block[i][shift]) 101 | self.__hd_all_identify[old_index+shift] += (mtmp.count(1)) 102 | self.__min_hd_all_identify[i] = (min(self.__hd_all_identify[old_index:old_index+IRIS.NRSHIFTS]), self.__min_hd_all_identify[i][1]) 103 | 104 | # every time 105 | self.__min_hd_all_identify.sort() 106 | if int(len(self.__min_hd_all_identify)*self.FACTOR_KEEP) == 0: self.__min_hd_all_identify = self.__min_hd_all_identify[:1] 107 | else: self.__min_hd_all_identify = self.__min_hd_all_identify[:int(len(self.__min_hd_all_identify)*self.FACTOR_KEEP)] 108 | for i in range(len(self.__min_hd_all_identify)): 109 | index_min_hds.append(self.__min_hd_all_identify[i][1]) 110 | 111 | return index_min_hds 112 | 113 | 114 | ''' 115 | """ this was used to be able to benchmark a constant verification time """ 116 | def BASELINEverify(self, encSum): 117 | """ gets hd of all blocks and shifts, to benchmark a constant time as baseline """ 118 | verify = False 119 | tmp_hd = [0] * IRIS.NRSHIFTS 120 | ntmp = [0,0,0,0] 121 | for block in range(self.blocks): 122 | for shift in range(IRIS.NRSHIFTS): 123 | mtmp = NTRUDecrypt(self.__key, *encSum[shift*self.blocks+block]) 124 | ntmp[0] = mtmp.count(2) 125 | ntmp[1] = mtmp.count(-2) 126 | ntmp[2] = mtmp.count(1) 127 | ntmp[3] = mtmp.count(-1) 128 | tmp_hd[shift] += (ntmp[0] + ntmp[1]) 129 | if min(tmp_hd) < self.__accept[self.blocks-1]: 130 | verify = True 131 | 132 | tmp_hd.clear() 133 | return verify 134 | 135 | ''' 136 | -------------------------------------------------------------------------------- /serverNTRU.py: -------------------------------------------------------------------------------- 1 | from irisNTRU import IRIS 2 | from sortNTRU import sortIrisBitsForServer 3 | import random 4 | import time 5 | 6 | 7 | class SERVER(object): 8 | """docstring for server""" 9 | 10 | def __init__(self, public_ring, public_key, enrol_sample_nr): 11 | super(SERVER, self).__init__() 12 | # self.ring = public_ring # not need except that someone wants to verify whether the pk of client and server 13 | # are the same 14 | # self.pk = public_key 15 | self.enrolled = enrol_sample_nr 16 | # for loading database 17 | self.__allref = [] 18 | self.__idall = [] 19 | 20 | # init Database 21 | for ref in range(IRIS.NRTRAINING + 1, IRIS.NRENROLMENT + IRIS.NRTRAINING + 1): 22 | # encrypt ref 23 | self.__allref += (IRIS.encryptBlocks(IRIS, public_key, public_ring, sortIrisBitsForServer(IRIS.readIrisBitsFromFile(IRIS, ref, self.enrolled)))) # left eye 24 | self.__allref += (IRIS.encryptBlocks(IRIS, public_key, public_ring, sortIrisBitsForServer(IRIS.readIrisBitsFromFile(IRIS, ref, self.enrolled + IRIS.NREYES)))) # right eye 25 | 26 | self.__idall.append((ref, self.enrolled)) 27 | self.__idall.append((ref, self.enrolled + IRIS.NREYES)) 28 | 29 | @staticmethod 30 | def __sum1blockforallshifts(probe, ref_block, block, nr_of_blocks): 31 | sum_all_shifts = [] 32 | for shift in range(IRIS.NRSHIFTS): 33 | sum_all_shifts.append(([probe[shift * nr_of_blocks + block][0][0] + ref_block[0][0]], ref_block[1])) 34 | return sum_all_shifts 35 | 36 | @staticmethod 37 | def __sum2ciphertexts(c1, c2): 38 | c_sum = [] 39 | for block in range(len(c1)): 40 | c_sum.append(([c1[block][0][0] + c2[block][0][0]], c1[block][1])) 41 | return c_sum 42 | 43 | def SERVERverification(self, ttp, probe, id): 44 | """ verifies whether the probe belongs to the enrolled id """ 45 | if not (id[0] in range(IRIS.NRTRAINING + 1, IRIS.NRTRAINING + IRIS.NRENROLMENT + 1) and id[1] in range(1, 11)): 46 | return False 47 | if id[1] > IRIS.NREYES: 48 | ref = self.__allref[ 49 | self.__idall.index((id[0], self.enrolled + IRIS.NREYES)) * ttp.blocks:self.__idall.index((id[0], self.enrolled + IRIS.NREYES)) * ttp.blocks + ttp.blocks] 50 | else: 51 | ref = self.__allref[self.__idall.index((id[0], self.enrolled)) * ttp.blocks:self.__idall.index((id[0], self.enrolled)) * ttp.blocks + ttp.blocks] 52 | 53 | sum_all_shifts = [] 54 | for shift in range(0, IRIS.NRSHIFTS * ttp.blocks, ttp.blocks): 55 | sum_all_shifts += (self.__sum2ciphertexts(probe[shift:shift + ttp.blocks], ref)) 56 | 57 | return ttp.TTPverify(sum_all_shifts) 58 | 59 | def SERVERverificationAll(self, ttp, probe, id): 60 | """ gets the hd of all blocks but accepts after first matching shift for benchmark purposes """ 61 | if not (id[0] in range(IRIS.NRTRAINING + 1, IRIS.NRTRAINING + IRIS.NRENROLMENT + 1) and id[1] in range(1, 11)): 62 | return False 63 | if id[1] > IRIS.NREYES: 64 | ref = self.__allref[ 65 | self.__idall.index((id[0], self.enrolled + IRIS.NREYES)) * ttp.blocks:self.__idall.index((id[0], self.enrolled + IRIS.NREYES)) * ttp.blocks + ttp.blocks] 66 | else: 67 | ref = self.__allref[self.__idall.index((id[0], self.enrolled)) * ttp.blocks:self.__idall.index((id[0], self.enrolled)) * ttp.blocks + ttp.blocks] 68 | 69 | sum_all_shifts = [] 70 | for shift in range(0, IRIS.NRSHIFTS * ttp.blocks, ttp.blocks): 71 | sum_all_shifts += (self.__sum2ciphertexts(probe[shift:shift + ttp.blocks], ref)) 72 | 73 | return ttp.TTPverifyAll(sum_all_shifts) 74 | 75 | def SERVERidentification(self, ttp, probe): 76 | """ sends first block of all sums to ttp, gets indexes back for lowest hds and sends next block """ 77 | """ in the end only 1 index remains and this is the subject with the lowest hd """ 78 | sum_all = [] 79 | index = list(range(IRIS.NRENROLMENT << 1)) 80 | block = 0 81 | 82 | for i in range(0, len(self.__allref), ttp.blocks): 83 | sum_all.append(self.__sum1blockforallshifts(probe, self.__allref[i], block, ttp.blocks)) 84 | 85 | timeseed = time.time() 86 | random.seed(timeseed) 87 | random.shuffle(index) 88 | random.seed(timeseed) 89 | random.shuffle(sum_all) 90 | del timeseed 91 | 92 | keep = ttp.TTPidentify(sum_all, block) 93 | if keep is None: return None 94 | while len(keep) > 1 and block < ttp.blocks - 1: 95 | block += 1 96 | sum_all.clear() 97 | # sum next block 98 | for i in range(len(keep)): 99 | sum_all.append( 100 | self.__sum1blockforallshifts(probe, self.__allref[index[keep[i]] * ttp.blocks + block], block, ttp.blocks)) 101 | 102 | keep = ttp.TTPidentify(sum_all, block) 103 | if keep is None: return None 104 | 105 | sum_all.clear() 106 | """ possible to check whether the lowest HD verifies the reference """ 107 | # if self.SERVERverification(ttp, probe, self.__idall[index[keep[0]]]): return self.__idall[index[keep[0]]] 108 | """ else return ID of lowest HD (or adjust to support returning multiple IDs, however this may need adjusting ttp, too) """ 109 | return self.__idall[index[keep[0]]] 110 | 111 | 112 | ''' 113 | def BASELINEverification(self, ttp, probe, id): 114 | """ gets the hd of all blocks and shifts for benchmark purposes """ 115 | if not (id[0] in range(IRIS.NRTRAINING+1, IRIS.NRTRAINING+IRIS.NRENROLMENT+1) and id[1] in range(1, 11)): 116 | return False 117 | if id[1] > IRIS.NREYES: ref = self.__allref[self.__idall.index((id[0],self.enrolled+IRIS.NREYES))*ttp.blocks:self.__idall.index((id[0],self.enrolled+IRIS.NREYES))*ttp.blocks+ttp.blocks] 118 | else: ref = self.__allref[self.__idall.index((id[0],self.enrolled))*ttp.blocks:self.__idall.index((id[0],self.enrolled))*ttp.blocks+ttp.blocks] 119 | 120 | sum_all_shifts=[] 121 | for shift in range(0,IRIS.NRSHIFTS*ttp.blocks,ttp.blocks): 122 | sum_all_shifts += (self.__sum2ciphertexts(probe[shift:shift+ttp.blocks], ref)) 123 | 124 | return ttp.BASELINEverify(sum_all_shifts 125 | ) 126 | 127 | 128 | ''' 129 | -------------------------------------------------------------------------------- /biometricNTRU.py: -------------------------------------------------------------------------------- 1 | from itertools import chain, starmap, zip_longest 2 | import math, random 3 | 4 | def extgcd(a, b): 5 | u, v, s, t = 1, 0, 0, 1 6 | while b!=0: 7 | q=a//b 8 | a, b = b, a-q*b 9 | u, s = s, u-q*s 10 | v, t = t, v-q*t 11 | return a, u, v 12 | 13 | def invModQ(a,q): 14 | g, u, v=extgcd(a, q) 15 | if g > 1: 16 | print("{} has no inverse mod {}, gcd={}".format(a,q,g)) 17 | return None 18 | return u%q 19 | 20 | class ConvPoly(object): 21 | def __init__(self, coef=[0], N=None): 22 | if N is None: 23 | self.N = len(coef) 24 | self.coef = coef 25 | else: 26 | self.N = N 27 | self.coef = coef + [0]*(N-len(coef)) 28 | def __repr__(self): 29 | return type(self).__name__ + str(self.coef) 30 | def __add__(self, other): 31 | if isinstance(other, type(self)) and (self.N == other.N): 32 | return ConvPoly(list(starmap(sum,zip_longest(zip(self.coef, other.coef))))) 33 | elif isinstance(other, int): 34 | return ConvPoly([self.coef[0]+other] + self.coef[1:]) 35 | else: 36 | return NotImplemented 37 | def __radd__(self, other): 38 | return self + other 39 | def __eq__(self, other): 40 | if self.coef == other.coef: 41 | return True 42 | return False 43 | def __ne__(self, other): 44 | return not (self == other) 45 | def __neg__(self): 46 | return type(self)(list(starmap(lambda x: -x, other.coef), zip_longest(other.q))) 47 | def __sub__(self, other): 48 | return self + (-other) 49 | def __rsub__(self, other): 50 | return self - other 51 | def __mul__(self, other): 52 | if isinstance(other, type(self)) and self.N == other.N: 53 | coefs = [] 54 | for k in range(self.N): 55 | s = 0 56 | for i in range(self.N): 57 | s += self.coef[i] * other.coef[(k-i)%self.N] 58 | coefs.append(s) 59 | return type(self)(coefs) 60 | elif isinstance(other, int): 61 | return type(self)([ other*c for c in self.coef ]) 62 | else: 63 | return NotImplemented 64 | def __rmul__(self, other): 65 | return self*other 66 | 67 | class PolyModQ(object): 68 | def __init__(self, coef, q): 69 | coef = list(starmap(lambda x: x%q, zip_longest(coef))) 70 | for index, val in enumerate(coef[::-1]): 71 | if val != 0: 72 | break 73 | self.coef = coef[:len(coef)-index] 74 | self.degree = len(self.coef)-1 75 | self.q = q 76 | def __repr__(self): 77 | return type(self).__name__ + "(" + str(self.coef) + ", " + str(self.q) + ")" 78 | def __eq__(self, other): 79 | if self.degree == other.degree: 80 | for pair in zip(self.coef, other.coef): 81 | if pair[0] != pair[1]: 82 | return False 83 | return True 84 | return False 85 | def __ne__(self, other): 86 | return not (self == other) 87 | def __neg__(self): 88 | return type(self)(map(lambda x: -x, self.coef), self.q) 89 | def __add__(self, other): 90 | if isinstance(other, type(self)) and self.q == other.q: 91 | if self.degree > other.degree: 92 | return type(self)(map(sum,zip(self.coef, other.coef + [0]*(self.degree-other.degree))),self.q) 93 | elif self.degree < other.degree: 94 | return other + self 95 | else: 96 | return type(self)(map(sum,zip(self.coef, other.coef)),self.q) 97 | elif isinstance(other, ConvPoly): 98 | return self + ConvModQ(other.coef, self.q, self.N) 99 | elif isinstance(other, int): 100 | return type(self)([self.coef[0]+other] + self.coef[1:],self.q) 101 | else: 102 | return NotImplemented 103 | def __radd__(self, other): 104 | return self + other 105 | def __sub__(self, other): 106 | if isinstance(other, type(self)) or isinstance(other, int): 107 | return self + (-other) 108 | else: 109 | return NotImplemented 110 | def __rsub__(self, other): 111 | return self - other 112 | def __mul__(self, other): 113 | if isinstance(other, type(self)) and self.q == other.q: 114 | coef = [0]*(self.degree+other.degree+1) 115 | for index1,c1 in enumerate(self.coef): 116 | for index2,c2 in enumerate(other.coef): 117 | coef[index1+index2] += c1*c2 118 | return type(self)(coef, self.q) 119 | elif isinstance(other, int): 120 | return type(self)([ other*c for c in self.coef], self.q) 121 | else: 122 | return NotImplemented 123 | def __rmul__(self, other): 124 | return self*other 125 | def polydiv(self, denom): 126 | nom = PolyModQ([1]*(self.realdegree(self.coef)+1), self.q) 127 | for i in range(0, nom.realdegree(nom.coef)+1): 128 | nom.coef[i] = self.coef[i] 129 | n = nom.realdegree(nom.coef) 130 | d = denom.realdegree(denom.coef) 131 | if n < d: 132 | return (PolyModQ([0], nom.q), nom) 133 | quot = PolyModQ([1]*(n-d+1), nom.q) 134 | leadcoefinv = invModQ(denom.coef[d], nom.q) 135 | if leadcoefinv == None: 136 | print("The leadcoefinv of the divisor could not create an inverse mod {}".format(nom.q)) #this error should not occur for f,g 137 | return None 138 | for i in reversed(range(0, n-d+1)): 139 | quot.coef[i] = nom.coef[i + d]*leadcoefinv 140 | quot.coef[i] = quot.coef[i]%nom.q 141 | for j in reversed(range(0, d)): 142 | nom.coef[i+j] = nom.coef[i+j] - denom.coef[j]*quot.coef[i] 143 | nom.coef[i+j] = nom.coef[i+j]%nom.q 144 | rem = PolyModQ([1]*d, nom.q) 145 | for j in reversed(range(0, d)): 146 | rem.coef[j] = nom.coef[j] 147 | return (quot, rem) 148 | def realdegree(self, coef): 149 | for i in reversed(range(0, len(coef))): 150 | if self.coef[i] != 0: 151 | return i 152 | return 0 153 | def extgcdPoly(self, b): 154 | a = PolyModQ([1]*(self.realdegree(self.coef)+1), self.q) 155 | for i in range(0, a.realdegree(a.coef)+1): 156 | a.coef[i] = self.coef[i] 157 | u = PolyModQ([1], self.q) 158 | v = PolyModQ([0], self.q) 159 | s = PolyModQ([0], self.q) 160 | t = PolyModQ([1], self.q) 161 | degb = b.realdegree(b.coef) 162 | while degb != 0: 163 | qr = a.polydiv(b) 164 | if qr is None: 165 | return None 166 | q = qr[0] #a//b 167 | a, b = b, a-q*b 168 | degb = b.realdegree(b.coef) 169 | u, s = s, u-q*s 170 | v, t = t, v-q*t 171 | if b.coef[0] == 0: 172 | print("Found a true divisor =", q, ". Hence, computationally impossible to find an iverse of f!") 173 | return None 174 | if invModQ(b.coef[0], self.q) == None: 175 | print("{} has no inverse mod {}, Creating a fresh random f and trying again.".format(b.coef[0], self.q)) 176 | return None 177 | else: 178 | #print("Found inverse!") 179 | u = s 180 | v = t 181 | c = invModQ(b.coef[0], self.q) 182 | for i in range(0, u.realdegree(u.coef)+1): 183 | u.coef[i] = c*u.coef[i]%self.q 184 | for i in range(0, v.realdegree(v.coef)+1): 185 | v.coef[i] = c*v.coef[i]%self.q 186 | return b, u, v 187 | def centerLift(self): 188 | coefs = [] 189 | for c in self.coef: 190 | if c>self.q/2.0: 191 | coefs.append(c-self.q) 192 | else: 193 | coefs.append(c) 194 | return ConvPoly(coefs) 195 | 196 | 197 | class ConvModQ(PolyModQ): 198 | def __init__(self, coef, q, N=None): 199 | coef = list(starmap(lambda x: x%q, zip_longest(coef))) 200 | if N is None: 201 | self.N = len(coef) 202 | self.coef = coef 203 | else: 204 | self.N = N 205 | self.coef = coef + [0]*(N-len(coef)) 206 | self.degree = len(self.coef)-1 207 | self.q = q 208 | def __repr__(self): 209 | return type(self).__name__ + "(" + str(self.coef) + ", " + str(self.q) + ")" 210 | def __mul__(self, other): 211 | if isinstance(other, type(self)) and self.N == other.N: 212 | coefs = [] 213 | for k in range(self.N): 214 | s = 0 215 | for i in range(self.N): 216 | s += self.coef[i] * other.coef[(k-i)%self.N] 217 | coefs.append(s) 218 | return type(self)(coefs, self.q, self.N) 219 | elif isinstance(other, ConvPoly): 220 | other = ConvModQ(other.coef, self.q, self.N) 221 | return self*other 222 | elif isinstance(other, int): 223 | return type(self)([ other*c for c in self.coef ], self.q, self.N) 224 | else: 225 | return NotImplemented 226 | def __div__(self, other): 227 | if isinstance(other, type(self)): 228 | return self*other.inverse() 229 | elif isinstance(other, int): 230 | otherinv = invModQ(other, self.q) 231 | if otherinv is None: 232 | raise Exception("{} not invertible mod {}".format(other, self.q)) 233 | return self*otherinv 234 | else: 235 | return NotImplemented 236 | def modQ(self, q): 237 | return ConvModQ(self.coef, q, self.N) 238 | def extgcdPoly(self, other): 239 | ggTuv = PolyModQ.extgcdPoly(self, other) 240 | if ggTuv == None: 241 | return None 242 | c = ggTuv[1].coef 243 | u = ConvModQ(c, self.q, self.N) 244 | return u 245 | def inverse(self, N=None, debug=False): 246 | if N == None: 247 | N = self.N 248 | Modulus = PolyModQ([-1] + [0]*(N-1) + [1], self.q) 249 | inv = self.extgcdPoly(Modulus) 250 | if inv is None: 251 | return None 252 | return inv 253 | def mult(self, other): 254 | return self.__mul__(other) 255 | 256 | class NTRUParams(object): 257 | def __init__(self, security_in_bits): 258 | if security_in_bits == 112: 259 | self.N=401 260 | self.d1=8 261 | self.d2=8 262 | self.d3=6 263 | self.dg=133 264 | self.df=71 265 | elif security_in_bits == 128: 266 | self.N=439 267 | self.d1=9 268 | self.d2=8 269 | self.d3=5 270 | self.dg=146 271 | self.df=75 272 | elif security_in_bits == 192: 273 | self.N=593 274 | self.d1=10 275 | self.d2=10 276 | self.d3=8 277 | self.dg=197 278 | self.df=84 279 | elif security_in_bits == 256: 280 | self.N=743 281 | self.d1=11 282 | self.d2=11 283 | self.d3=15 284 | self.dg=247 285 | self.df=90 286 | else: 287 | raise Exception("Not implemented. :(") 288 | self.q = 2053 289 | self.p = 2 290 | 291 | class NTRUKey(object): 292 | r=random.SystemRandom() 293 | def __init__(self, ring=None, f=None, g=None, simplef=False): 294 | if ring is None: 295 | ring = NTRUParams(128) 296 | elif isinstance(ring, int): 297 | ring = NTRUParams(ring) 298 | self.ring = ring 299 | if simplef == True: 300 | self.a1 = self.randomTrinary(self.ring.d1+1, self.ring.d1, self.ring.N) 301 | self.a2 = self.randomTrinary(self.ring.d2+1, self.ring.d2, self.ring.N) 302 | self.a3 = self.randomTrinary(self.ring.d3+1, self.ring.d3, self.ring.N) 303 | self.ai = self.a1*self.a2+self.a3 304 | self.f1 = ConvPoly(self.ai.coef) 305 | self.f1p = self.ring.p*self.f1 306 | self.f = 1+self.f1p 307 | self.f = ConvModQ(self.f.coef, self.ring.q) 308 | self.finvp = ConvModQ([1]+ [0]*(self.ring.N-1), self.ring.p) 309 | if f is None and simplef == False: 310 | self.f = self.randomTrinary(self.ring.df+1, self.ring.df,self.ring.N) 311 | if g is None: 312 | self.g = self.randomTrinary(self.ring.dg, self.ring.dg-1, self.ring.N) 313 | self.finvq = self.f.inverse() 314 | if simplef == False: 315 | while self.finvq is None: 316 | print("finv was None. Retrying 1.") 317 | self.f = self.randomTrinary(self.ring.df+1, self.ring.df, self.ring.N) 318 | self.finvq = self.f.inverse() 319 | if simplef == False: 320 | self.finvp = ConvModQ(self.f.centerLift().coef, self.ring.p).inverse() 321 | if simplef == True: 322 | while self.finvq is None: 323 | print("simple finvq was None. Retrying 1.") 324 | self.a1 = self.randomTrinary(self.ring.d1+1, self.ring.d1, self.ring.N) 325 | self.a2 = self.randomTrinary(self.ring.d2+1, self.ring.d2, self.ring.N) 326 | self.a3 = self.randomTrinary(self.ring.d3+1, self.ring.d3, self.ring.N) 327 | self.ai = self.a1*self.a2+self.a3 328 | self.f1 = ConvPoly(self.ai.coef) 329 | self.f1p = self.ring.p*self.f1 330 | self.f = 1+self.f1p 331 | self.f = ConvModQ(self.f.coef, self.ring.q) 332 | self.finvp = ConvModQ([1]+ [0]*(self.ring.N-1), self.ring.p) 333 | while self.finvq is None and simplef == False or self.finvp is None: 334 | print("finv was None. Retrying 2.") 335 | self.f = self.randomTrinary(self.ring.df+1, self.ring.df,self.ring.N) 336 | self.finvq = self.f.inverse() 337 | if self.finvq is None: 338 | continue 339 | self.finvp = ConvModQ(self.f.coef, self.ring.p).inverse() 340 | self.h = self.finvq * self.g * self.ring.p #if p is multiplied into pk h, we save multiplications during the encryption process 341 | def randomTrinary(self, d1,d2,polylength): 342 | arr = [1]*d1 + [-1]*d2 + [0]*(polylength - d1 - d2) 343 | self.r.shuffle(arr) 344 | return ConvModQ(arr, self.ring.q, self.ring.N) 345 | def publicKey(self): 346 | return (self.ring, self.h) 347 | 348 | def chunk(N, iter): 349 | for i in range(int(math.ceil(len(iter)/float(N)))): 350 | yield iter[i*N:(i+1)*N] 351 | 352 | def NTRUBlockEncrypt(ring, h, m): 353 | rvars = [1]*ring.dg + [-1]*ring.dg + [0]*(ring.N-2*ring.dg) 354 | random.shuffle(rvars) 355 | r = ConvModQ(rvars, ring.q) 356 | #c = ring.p*r*h+m #better to multiply p into the pk h, saves 1 multiplication for every encrypted block 357 | c = r*h+m 358 | return c 359 | 360 | def NTRUEncrypt(ring, pub, m): 361 | if len(m) > ring.N: 362 | msplit = [ m for m in chunk(ring.N, m) ] 363 | n = len(msplit[-1]) 364 | m = list(starmap(lambda m: ConvPoly(m, ring.N), zip_longest(msplit))) 365 | else: 366 | n = len(m) 367 | m = [ConvPoly(m, ring.N)] 368 | menc = list(starmap(lambda m: NTRUBlockEncrypt(ring, pub, m), zip_longest(m))) 369 | return (menc,n) 370 | 371 | def NTRUBlockDecrypt(key, c): 372 | a = key.f*c 373 | if key.finvp.realdegree(key.finvp.coef) == 1 and key.finvp.coef[0] ==1: #Fall simplef=True 374 | a = ConvModQ(a.coef, key.ring.p) 375 | return ConvPoly(a.coef) 376 | aprime = a.centerLift() 377 | m = key.finvp*aprime 378 | return m.centerLift() 379 | 380 | def NTRUDecrypt(key, cl, n): 381 | if len(cl)==1: 382 | m = NTRUBlockDecrypt(key, cl[0]) 383 | return m.coef[:n] 384 | cl = list(starmap(lambda c: NTRUBlockDecrypt(key, c), zip_longest(cl))) 385 | mlist = [ c.coef for c in cl[:-1] ] 386 | mlist.append(cl[-1].coef[:n]) 387 | m = list(chain.from_iterable(mlist)) 388 | return m 389 | 390 | def __sum2ciphertexts(c1, c2): 391 | c_sum = [] 392 | for block in range(len(c1)): 393 | c_sum.append(([c1[block][0][0] + c2[block][0][0]], c1[block][1])) 394 | return c_sum 395 | 396 | 397 | -------------------------------------------------------------------------------- /timingNTRU.py: -------------------------------------------------------------------------------- 1 | import time 2 | from clientNTRU import CLIENT 3 | from serverNTRU import SERVER 4 | from ttpNTRU import TTP 5 | 6 | 7 | ''' this may take some time, maybe you want to manually split the timing function ''' 8 | 9 | def printList(llist): 10 | for elem in llist: 11 | print(elem) 12 | print('\n') 13 | 14 | def timing(): 15 | print('timing benchmark\n') 16 | #warming up 17 | ttp_warmup=TTP(256, 0.5) 18 | server_warmup = SERVER(ttp_warmup.ring, ttp_warmup.pk, 1) 19 | 20 | time112=['timing for 112'] 21 | time128=['timing for 128'] 22 | time192=['timing for 192'] 23 | time256=['timing for 256'] 24 | tmp = [] 25 | tmp112 = [] 26 | tmp128 = [] 27 | tmp192 = [] 28 | tmp256 = [] 29 | 30 | factors = [1, 0.5, 0.25, 0.125, 0.0625] 31 | 32 | # use odd value to get median in order to exclude peak values biased by other processes 33 | slowloop = 25 34 | fastloop = 25 #multiple of 5 for all possible enrolled samples 35 | 36 | #start timing 37 | 38 | # TTP 39 | print('TTP') 40 | 41 | #ttp112 42 | tmp.clear() 43 | for i in range(fastloop): 44 | t_start = time.time() 45 | ttp112=TTP(112, 0.5) 46 | t_end = time.time() 47 | tmp.append(t_end-t_start) 48 | tmp.sort() 49 | time112.append('ttp_init: %.6f' % tmp[len(tmp)//2]) 50 | #ttp128 51 | tmp.clear() 52 | for i in range(fastloop): 53 | t_start = time.time() 54 | ttp128=TTP(128, 0.5) 55 | t_end = time.time() 56 | tmp.append(t_end-t_start) 57 | tmp.sort() 58 | time128.append('ttp_init: %.6f' % tmp[len(tmp)//2]) 59 | #ttp192 60 | tmp.clear() 61 | for i in range(fastloop): 62 | t_start = time.time() 63 | ttp192=TTP(192, 0.5) 64 | t_end = time.time() 65 | tmp.append(t_end-t_start) 66 | tmp.sort() 67 | time192.append('ttp_init: %.6f' % tmp[len(tmp)//2]) 68 | #ttp256 69 | tmp.clear() 70 | for i in range(fastloop): 71 | t_start = time.time() 72 | ttp256=TTP(256, 0.5) 73 | t_end = time.time() 74 | tmp.append(t_end-t_start) 75 | tmp.sort() 76 | time256.append('ttp_init: %.6f' % tmp[len(tmp)//2]) 77 | 78 | # CLIENT 79 | print('Client') 80 | 81 | client112 = CLIENT(ttp112.ring, ttp112.pk) 82 | client128 = CLIENT(ttp128.ring, ttp128.pk) 83 | client192 = CLIENT(ttp192.ring, ttp192.pk) 84 | client256 = CLIENT(ttp256.ring, ttp256.pk) 85 | 86 | # SERVER 87 | print('Server') 88 | 89 | #server112 90 | print('Server_112') 91 | tmp.clear() 92 | for i in range(fastloop//5): 93 | t_start = time.time() 94 | server112_1 = SERVER(ttp112.ring, ttp112.pk, 1) 95 | t_end = time.time() 96 | tmp.append(t_end-t_start) 97 | t_start = time.time() 98 | server112_2 = SERVER(ttp112.ring, ttp112.pk, 2) 99 | t_end = time.time() 100 | tmp.append(t_end-t_start) 101 | t_start = time.time() 102 | server112_3 = SERVER(ttp112.ring, ttp112.pk, 3) 103 | t_end = time.time() 104 | tmp.append(t_end-t_start) 105 | t_start = time.time() 106 | server112_4 = SERVER(ttp112.ring, ttp112.pk, 4) 107 | t_end = time.time() 108 | tmp.append(t_end-t_start) 109 | t_start = time.time() 110 | server112_5 = SERVER(ttp112.ring, ttp112.pk, 5) 111 | t_end = time.time() 112 | tmp.append(t_end-t_start) 113 | tmp.sort() 114 | time112.append('server_init: %.3f' % tmp[len(tmp)//2]) 115 | 116 | #server128 117 | print('Server_128') 118 | tmp.clear() 119 | for i in range(fastloop//5): 120 | t_start = time.time() 121 | server128_1 = SERVER(ttp128.ring, ttp128.pk, 1) 122 | t_end = time.time() 123 | tmp.append(t_end-t_start) 124 | t_start = time.time() 125 | server128_2 = SERVER(ttp128.ring, ttp128.pk, 2) 126 | t_end = time.time() 127 | tmp.append(t_end-t_start) 128 | t_start = time.time() 129 | server128_3 = SERVER(ttp128.ring, ttp128.pk, 3) 130 | t_end = time.time() 131 | tmp.append(t_end-t_start) 132 | t_start = time.time() 133 | server128_4 = SERVER(ttp128.ring, ttp128.pk, 4) 134 | t_end = time.time() 135 | tmp.append(t_end-t_start) 136 | t_start = time.time() 137 | server128_5 = SERVER(ttp128.ring, ttp128.pk, 5) 138 | t_end = time.time() 139 | tmp.append(t_end-t_start) 140 | tmp.sort() 141 | time128.append('server_init: %.3f' % tmp[len(tmp)//2]) 142 | 143 | #server192 144 | print('Server_192') 145 | tmp.clear() 146 | for i in range(fastloop//5): 147 | t_start = time.time() 148 | server192_1 = SERVER(ttp192.ring, ttp192.pk, 1) 149 | t_end = time.time() 150 | tmp.append(t_end-t_start) 151 | t_start = time.time() 152 | server192_2 = SERVER(ttp192.ring, ttp192.pk, 2) 153 | t_end = time.time() 154 | tmp.append(t_end-t_start) 155 | t_start = time.time() 156 | server192_3 = SERVER(ttp192.ring, ttp192.pk, 3) 157 | t_end = time.time() 158 | tmp.append(t_end-t_start) 159 | t_start = time.time() 160 | server192_4 = SERVER(ttp192.ring, ttp192.pk, 4) 161 | t_end = time.time() 162 | tmp.append(t_end-t_start) 163 | t_start = time.time() 164 | server192_5 = SERVER(ttp192.ring, ttp192.pk, 5) 165 | t_end = time.time() 166 | tmp.append(t_end-t_start) 167 | tmp.sort() 168 | time192.append('server_init: %.3f' % tmp[len(tmp)//2]) 169 | 170 | #server256 171 | print('Server_256') 172 | tmp.clear() 173 | for i in range(fastloop//5): 174 | t_start = time.time() 175 | server256_1 = SERVER(ttp256.ring, ttp256.pk, 1) 176 | t_end = time.time() 177 | tmp.append(t_end-t_start) 178 | t_start = time.time() 179 | server256_2 = SERVER(ttp256.ring, ttp256.pk, 2) 180 | t_end = time.time() 181 | tmp.append(t_end-t_start) 182 | t_start = time.time() 183 | server256_3 = SERVER(ttp256.ring, ttp256.pk, 3) 184 | t_end = time.time() 185 | tmp.append(t_end-t_start) 186 | t_start = time.time() 187 | server256_4 = SERVER(ttp256.ring, ttp256.pk, 4) 188 | t_end = time.time() 189 | tmp.append(t_end-t_start) 190 | t_start = time.time() 191 | server256_5 = SERVER(ttp256.ring, ttp256.pk, 5) 192 | t_end = time.time() 193 | tmp.append(t_end-t_start) 194 | tmp.sort() 195 | time256.append('server_init: %.3f' % tmp[len(tmp)//2]) 196 | 197 | # PROBE 198 | print('Probe') 199 | 200 | #probe112 201 | tmp.clear() 202 | for i in range(fastloop): 203 | t_start = time.time() 204 | probe112=client112.CLIENTencryptProbe((25,1)) 205 | t_end = time.time() 206 | tmp.append(t_end-t_start) 207 | tmp.sort() 208 | time112.append('probe_enc: %.6f' % tmp[len(tmp)//2]) 209 | 210 | #probe128 211 | tmp.clear() 212 | for i in range(fastloop): 213 | t_start = time.time() 214 | probe128=client128.CLIENTencryptProbe((25,1)) 215 | t_end = time.time() 216 | tmp.append(t_end-t_start) 217 | tmp.sort() 218 | time128.append('probe_enc: %.6f' % tmp[len(tmp)//2]) 219 | 220 | #probe192 221 | tmp.clear() 222 | for i in range(fastloop): 223 | t_start = time.time() 224 | probe192=client192.CLIENTencryptProbe((25,1)) 225 | t_end = time.time() 226 | tmp.append(t_end-t_start) 227 | tmp.sort() 228 | time192.append('probe_enc: %.6f' % tmp[len(tmp)//2]) 229 | 230 | #probe256 231 | tmp.clear() 232 | for i in range(fastloop): 233 | t_start = time.time() 234 | probe256=client256.CLIENTencryptProbe((25,1)) 235 | t_end = time.time() 236 | tmp.append(t_end-t_start) 237 | tmp.sort() 238 | time256.append('probe_enc: %.6f' % tmp[len(tmp)//2]) 239 | 240 | tmp.clear() 241 | tmp112.clear() 242 | tmp128.clear() 243 | tmp192.clear() 244 | tmp256.clear() 245 | 246 | # VERIFICATION 247 | print('Verification') 248 | 249 | for subj in range(25,176): 250 | for sample in range(1,11): 251 | #print('veri', subj, sample) 252 | probe112 = client112.CLIENTencryptProbe((subj, sample)) 253 | probe128 = client128.CLIENTencryptProbe((subj, sample)) 254 | probe192 = client192.CLIENTencryptProbe((subj, sample)) 255 | probe256 = client256.CLIENTencryptProbe((subj, sample)) 256 | 257 | 258 | if sample % 5 != 1: 259 | t_start = time.time() 260 | server112_1.SERVERverification(ttp112, probe112, (subj, sample)) 261 | t_end = time.time() 262 | tmp112.append(t_end-t_start) 263 | t_start = time.time() 264 | server128_1.SERVERverification(ttp128, probe128, (subj, sample)) 265 | t_end = time.time() 266 | tmp128.append(t_end-t_start) 267 | t_start = time.time() 268 | server192_1.SERVERverification(ttp192, probe192, (subj, sample)) 269 | t_end = time.time() 270 | tmp192.append(t_end-t_start) 271 | t_start = time.time() 272 | server256_1.SERVERverification(ttp256, probe256, (subj, sample)) 273 | t_end = time.time() 274 | tmp256.append(t_end-t_start) 275 | if sample % 5 != 2: 276 | t_start = time.time() 277 | server112_2.SERVERverification(ttp112, probe112, (subj, sample)) 278 | t_end = time.time() 279 | tmp112.append(t_end-t_start) 280 | server128_2.SERVERverification(ttp128, probe128, (subj, sample)) 281 | t_end = time.time() 282 | tmp128.append(t_end-t_start) 283 | t_start = time.time() 284 | server192_2.SERVERverification(ttp192, probe192, (subj, sample)) 285 | t_end = time.time() 286 | tmp192.append(t_end-t_start) 287 | t_start = time.time() 288 | server256_2.SERVERverification(ttp256, probe256, (subj, sample)) 289 | t_end = time.time() 290 | tmp256.append(t_end-t_start) 291 | if sample % 5 != 3: 292 | t_start = time.time() 293 | server112_3.SERVERverification(ttp112, probe112, (subj, sample)) 294 | t_end = time.time() 295 | tmp112.append(t_end-t_start) 296 | server128_3.SERVERverification(ttp128, probe128, (subj, sample)) 297 | t_end = time.time() 298 | tmp128.append(t_end-t_start) 299 | t_start = time.time() 300 | server192_3.SERVERverification(ttp192, probe192, (subj, sample)) 301 | t_end = time.time() 302 | tmp192.append(t_end-t_start) 303 | t_start = time.time() 304 | server256_3.SERVERverification(ttp256, probe256, (subj, sample)) 305 | t_end = time.time() 306 | tmp256.append(t_end-t_start) 307 | if sample % 5 != 4: 308 | t_start = time.time() 309 | server112_4.SERVERverification(ttp112, probe112, (subj, sample)) 310 | t_end = time.time() 311 | tmp112.append(t_end-t_start) 312 | server128_4.SERVERverification(ttp128, probe128, (subj, sample)) 313 | t_end = time.time() 314 | tmp128.append(t_end-t_start) 315 | t_start = time.time() 316 | server192_4.SERVERverification(ttp192, probe192, (subj, sample)) 317 | t_end = time.time() 318 | tmp192.append(t_end-t_start) 319 | t_start = time.time() 320 | server256_4.SERVERverification(ttp256, probe256, (subj, sample)) 321 | t_end = time.time() 322 | tmp256.append(t_end-t_start) 323 | if sample % 5 != 0: 324 | t_start = time.time() 325 | server112_5.SERVERverification(ttp112, probe112, (subj, sample)) 326 | t_end = time.time() 327 | tmp112.append(t_end-t_start) 328 | server128_5.SERVERverification(ttp128, probe128, (subj, sample)) 329 | t_end = time.time() 330 | tmp128.append(t_end-t_start) 331 | t_start = time.time() 332 | server192_5.SERVERverification(ttp192, probe192, (subj, sample)) 333 | t_end = time.time() 334 | tmp192.append(t_end-t_start) 335 | t_start = time.time() 336 | server256_5.SERVERverification(ttp256, probe256, (subj, sample)) 337 | t_end = time.time() 338 | tmp256.append(t_end-t_start) 339 | 340 | 341 | tmp112.sort() 342 | time112.append('veri: %.6f' % tmp112[len(tmp112)//2]) 343 | tmp128.sort() 344 | time128.append('veri: %.6f' % tmp128[len(tmp128)//2]) 345 | tmp192.sort() 346 | time192.append('veri: %.6f' % tmp192[len(tmp192)//2]) 347 | tmp256.sort() 348 | time256.append('veri: %.6f' % tmp256[len(tmp256)//2]) 349 | 350 | tmp112.clear() 351 | tmp128.clear() 352 | tmp192.clear() 353 | tmp256.clear() 354 | 355 | # BASELINE VERIFICATION ALL BLOCKS 356 | print('Baseline Verification') 357 | 358 | for subj in range(25,176): 359 | for sample in range(1,11): 360 | #print('veri', subj, sample) 361 | probe112 = client112.CLIENTencryptProbe((subj, sample)) 362 | probe128 = client128.CLIENTencryptProbe((subj, sample)) 363 | probe192 = client192.CLIENTencryptProbe((subj, sample)) 364 | probe256 = client256.CLIENTencryptProbe((subj, sample)) 365 | 366 | 367 | if sample % 5 != 1: 368 | t_start = time.time() 369 | server112_1.SERVERverificationAll(ttp112, probe112, (subj, sample)) 370 | t_end = time.time() 371 | tmp112.append(t_end-t_start) 372 | t_start = time.time() 373 | server128_1.SERVERverificationAll(ttp128, probe128, (subj, sample)) 374 | t_end = time.time() 375 | tmp128.append(t_end-t_start) 376 | t_start = time.time() 377 | server192_1.SERVERverificationAll(ttp192, probe192, (subj, sample)) 378 | t_end = time.time() 379 | tmp192.append(t_end-t_start) 380 | t_start = time.time() 381 | server256_1.SERVERverificationAll(ttp256, probe256, (subj, sample)) 382 | t_end = time.time() 383 | tmp256.append(t_end-t_start) 384 | if sample % 5 != 2: 385 | t_start = time.time() 386 | server112_2.SERVERverificationAll(ttp112, probe112, (subj, sample)) 387 | t_end = time.time() 388 | tmp112.append(t_end-t_start) 389 | server128_2.SERVERverificationAll(ttp128, probe128, (subj, sample)) 390 | t_end = time.time() 391 | tmp128.append(t_end-t_start) 392 | t_start = time.time() 393 | server192_2.SERVERverificationAll(ttp192, probe192, (subj, sample)) 394 | t_end = time.time() 395 | tmp192.append(t_end-t_start) 396 | t_start = time.time() 397 | server256_2.SERVERverificationAll(ttp256, probe256, (subj, sample)) 398 | t_end = time.time() 399 | tmp256.append(t_end-t_start) 400 | if sample % 5 != 3: 401 | t_start = time.time() 402 | server112_3.SERVERverificationAll(ttp112, probe112, (subj, sample)) 403 | t_end = time.time() 404 | tmp112.append(t_end-t_start) 405 | server128_3.SERVERverificationAll(ttp128, probe128, (subj, sample)) 406 | t_end = time.time() 407 | tmp128.append(t_end-t_start) 408 | t_start = time.time() 409 | server192_3.SERVERverificationAll(ttp192, probe192, (subj, sample)) 410 | t_end = time.time() 411 | tmp192.append(t_end-t_start) 412 | t_start = time.time() 413 | server256_3.SERVERverificationAll(ttp256, probe256, (subj, sample)) 414 | t_end = time.time() 415 | tmp256.append(t_end-t_start) 416 | if sample % 5 != 4: 417 | t_start = time.time() 418 | server112_4.SERVERverificationAll(ttp112, probe112, (subj, sample)) 419 | t_end = time.time() 420 | tmp112.append(t_end-t_start) 421 | server128_4.SERVERverificationAll(ttp128, probe128, (subj, sample)) 422 | t_end = time.time() 423 | tmp128.append(t_end-t_start) 424 | t_start = time.time() 425 | server192_4.SERVERverificationAll(ttp192, probe192, (subj, sample)) 426 | t_end = time.time() 427 | tmp192.append(t_end-t_start) 428 | t_start = time.time() 429 | server256_4.SERVERverificationAll(ttp256, probe256, (subj, sample)) 430 | t_end = time.time() 431 | tmp256.append(t_end-t_start) 432 | if sample % 5 != 0: 433 | t_start = time.time() 434 | server112_5.SERVERverificationAll(ttp112, probe112, (subj, sample)) 435 | t_end = time.time() 436 | tmp112.append(t_end-t_start) 437 | server128_5.SERVERverificationAll(ttp128, probe128, (subj, sample)) 438 | t_end = time.time() 439 | tmp128.append(t_end-t_start) 440 | t_start = time.time() 441 | server192_5.SERVERverificationAll(ttp192, probe192, (subj, sample)) 442 | t_end = time.time() 443 | tmp192.append(t_end-t_start) 444 | t_start = time.time() 445 | server256_5.SERVERverificationAll(ttp256, probe256, (subj, sample)) 446 | t_end = time.time() 447 | tmp256.append(t_end-t_start) 448 | 449 | 450 | tmp112.sort() 451 | time112.append('veri_baseline: %.6f' % tmp112[len(tmp112)//2]) 452 | tmp128.sort() 453 | time128.append('veri_baseline: %.6f' % tmp128[len(tmp128)//2]) 454 | tmp192.sort() 455 | time192.append('veri_baseline: %.6f' % tmp192[len(tmp192)//2]) 456 | tmp256.sort() 457 | time256.append('veri_baseline: %.6f' % tmp256[len(tmp256)//2]) 458 | 459 | tmp112.clear() 460 | tmp128.clear() 461 | tmp192.clear() 462 | tmp256.clear() 463 | 464 | # IDENTIFICATION 465 | print('Identification') 466 | 467 | 468 | for keep in factors: 469 | ttp112.FACTOR_KEEP = keep 470 | ttp128.FACTOR_KEEP = keep 471 | ttp192.FACTOR_KEEP = keep 472 | ttp256.FACTOR_KEEP = keep 473 | #id112 474 | print('id_factor_keep', keep, '112') 475 | tmp.clear() 476 | for i in range(slowloop): 477 | t_start = time.time() 478 | server112_1.SERVERidentification(ttp112, probe112) 479 | t_end = time.time() 480 | tmp.append(t_end-t_start) 481 | tmp.sort() 482 | time112.append('id_factor_keep %.3f: %0.3f' % (keep, tmp[len(tmp)//2])) 483 | #id128 484 | print('id_factor_keep', keep, '128') 485 | tmp.clear() 486 | for i in range(slowloop): 487 | t_start = time.time() 488 | server128_1.SERVERidentification(ttp128, probe128) 489 | t_end = time.time() 490 | tmp.append(t_end-t_start) 491 | tmp.sort() 492 | time128.append('id_factor_keep %.3f: %0.3f' % (keep, tmp[len(tmp)//2])) 493 | #id192 494 | print('id_factor_keep', keep, '192') 495 | tmp.clear() 496 | for i in range(slowloop): 497 | t_start = time.time() 498 | server192_1.SERVERidentification(ttp192, probe192) 499 | t_end = time.time() 500 | tmp.append(t_end-t_start) 501 | tmp.sort() 502 | time192.append('id_factor_keep %.3f: %0.3f' % (keep, tmp[len(tmp)//2])) 503 | #id256 504 | print('id_factor_keep', keep, '256') 505 | tmp.clear() 506 | for i in range(slowloop): 507 | t_start = time.time() 508 | server256_1.SERVERidentification(ttp256, probe256) 509 | t_end = time.time() 510 | tmp.append(t_end-t_start) 511 | tmp.sort() 512 | time256.append('id_factor_keep %.3f: %0.3f' % (keep, tmp[len(tmp)//2])) 513 | 514 | 515 | 516 | tmp.clear() 517 | print('done\n') 518 | 519 | 520 | # print all results 521 | printList(time112) 522 | printList(time128) 523 | printList(time192) 524 | printList(time256) 525 | 526 | return 1 527 | 528 | 529 | if __name__ == '__main__': 530 | timing() --------------------------------------------------------------------------------