├── .gitignore ├── README.md ├── speck.py └── simon.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *~ 3 | .DS_Store 4 | *.pyc 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The two lightweight block ciphers, SIMON and SPECK, designed by NSA. 2 | 3 | For more information, please refer to http://eprint.iacr.org/2013/404.pdf. 4 | -------------------------------------------------------------------------------- /speck.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This code is released under MIT license. 4 | 5 | NUM_ROUNDS = { 6 | (32, 64): 22, 7 | (48, 72): 22, 8 | (48, 96): 23, 9 | (64, 96): 26, 10 | (64, 128): 27, 11 | (96, 96): 28, 12 | (96, 144): 29, 13 | (128, 128): 32, 14 | (128, 192): 33, 15 | (128, 256): 34, 16 | } 17 | 18 | 19 | class SPECK: 20 | """ 21 | one of the two lightweight block ciphers designed by NSA 22 | this one is optimized for software implementation 23 | """ 24 | def __init__(self, block_size, key_size, master_key=None): 25 | assert (block_size, key_size) in NUM_ROUNDS 26 | self.block_size = block_size 27 | self.key_size = key_size 28 | self.__num_rounds = NUM_ROUNDS[(block_size, key_size)] 29 | if block_size == 32 and key_size == 64: 30 | self.__alpha = 7 31 | self.__beta = 2 32 | else: 33 | self.__alpha = 8 34 | self.__beta = 3 35 | self.__dim = block_size / 2 36 | self.__mod = 1 << self.__dim 37 | if master_key is not None: 38 | self.change_key(master_key) 39 | 40 | def __rshift(self, x, i): 41 | assert i in (self.__alpha, self.__beta) 42 | return ((x << (self.__dim - i)) % self.__mod) | (x >> i) 43 | 44 | def __lshift(self, x, i): 45 | assert i in (self.__alpha, self.__beta) 46 | return ((x << i) % self.__mod) | (x >> (self.__dim - i)) 47 | 48 | def __first_feistel(self, x, y): 49 | return y, (self.__rshift(x, self.__alpha) + y) % self.__mod 50 | 51 | def __second_feistel(self, x, y): 52 | return y, self.__lshift(x, self.__beta) ^ y 53 | 54 | # since the feistel used in this cipher is not in "the usual way" 55 | # it cannot be reused in decryption, and we have to write the inverse 56 | def __first_feistel_inv(self, x, y): 57 | return self.__lshift((y - x) % self.__mod, self.__alpha), x 58 | 59 | def __second_feistel_inv(self, x, y): 60 | return self.__rshift(x ^ y, self.__beta), x 61 | 62 | def change_key(self, master_key): 63 | assert 0 <= master_key < (1 << self.key_size) 64 | self.__round_key = [master_key % self.__mod] 65 | master_key >>= self.__dim 66 | llist = [] 67 | for i in range(self.key_size / self.__dim - 1): 68 | llist.append(master_key % self.__mod) 69 | master_key >>= self.__dim 70 | for i in range(self.__num_rounds - 1): 71 | l, r = self.__first_feistel(llist[i], self.__round_key[i]) 72 | r ^= i 73 | l, r = self.__second_feistel(l, r) 74 | llist.append(l) 75 | self.__round_key.append(r) 76 | 77 | def encrypt(self, plaintext): 78 | assert 0 <= plaintext < (1 << self.block_size) 79 | l = plaintext >> self.__dim 80 | r = plaintext % self.__mod 81 | for i in range(self.__num_rounds): 82 | l, r = self.__first_feistel(l, r) 83 | r ^= self.__round_key[i] 84 | l, r = self.__second_feistel(l, r) 85 | ciphertext = (l << self.__dim) | r 86 | assert 0 <= ciphertext < (1 << self.block_size) 87 | return ciphertext 88 | 89 | def decrypt(self, ciphertext): 90 | assert 0 <= ciphertext < (1 << self.block_size) 91 | l = ciphertext >> self.__dim 92 | r = ciphertext % self.__mod 93 | for i in range(self.__num_rounds - 1, -1, -1): 94 | l, r = self.__second_feistel_inv(l, r) 95 | r ^= self.__round_key[i] 96 | l, r = self.__first_feistel_inv(l, r) 97 | plaintext = (l << self.__dim) | r 98 | assert 0 <= plaintext < (1 << self.block_size) 99 | return plaintext 100 | 101 | 102 | if __name__ == '__main__': 103 | test_vectors = ( 104 | # block_size, key_size, key, plaintext, ciphertext 105 | (32, 64, 106 | 0x1918111009080100, 107 | 0x6574694c, 108 | 0xa86842f2), 109 | (48, 72, 110 | 0x1211100a0908020100, 111 | 0x20796c6c6172, 112 | 0xc049a5385adc), 113 | (48, 96, 114 | 0x1a19181211100a0908020100, 115 | 0x6d2073696874, 116 | 0x735e10b6445d), 117 | (64, 96, 118 | 0x131211100b0a090803020100, 119 | 0x74614620736e6165, 120 | 0x9f7952ec4175946c), 121 | (64, 128, 122 | 0x1b1a1918131211100b0a090803020100, 123 | 0x3b7265747475432d, 124 | 0x8c6fa548454e028b), 125 | (96, 96, 126 | 0x0d0c0b0a0908050403020100, 127 | 0x65776f68202c656761737520, 128 | 0x9e4d09ab717862bdde8f79aa), 129 | (96, 144, 130 | 0x1514131211100d0c0b0a0908050403020100, 131 | 0x656d6974206e69202c726576, 132 | 0x2bf31072228a7ae440252ee6), 133 | (128, 128, 134 | 0x0f0e0d0c0b0a09080706050403020100, 135 | 0x6c617669757165207469206564616d20, 136 | 0xa65d9851797832657860fedf5c570d18), 137 | (128, 192, 138 | 0x17161514131211100f0e0d0c0b0a09080706050403020100, 139 | 0x726148206665696843206f7420746e65, 140 | 0x1be4cf3a13135566f9bc185de03c1886), 141 | (128, 256, 142 | 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100, 143 | 0x65736f6874206e49202e72656e6f6f70, 144 | 0x4109010405c0f53e4eeeb48d9c188f43) 145 | ) 146 | 147 | for bsize, ksize, key, plain, cipher in test_vectors: 148 | my_speck = SPECK(bsize, ksize, key) 149 | encrypted = my_speck.encrypt(plain) 150 | assert encrypted == cipher 151 | for i in range(1000): 152 | encrypted = my_speck.encrypt(encrypted) 153 | for i in range(1000): 154 | encrypted = my_speck.decrypt(encrypted) 155 | decrypted = my_speck.decrypt(encrypted) 156 | assert decrypted == plain 157 | 158 | print 'All tests passed' 159 | -------------------------------------------------------------------------------- /simon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # This code is released under MIT license. 4 | 5 | CONFIG = { 6 | (32, 64): (32, 0), 7 | (48, 72): (36, 0), 8 | (48, 96): (36, 1), 9 | (64, 96): (42, 2), 10 | (64, 128): (44, 3), 11 | (96, 96): (52, 2), 12 | (96, 144): (54, 3), 13 | (128, 128): (68, 2), 14 | (128, 192): (69, 3), 15 | (128, 256): (72, 4), 16 | } 17 | 18 | 19 | def get_const_seq(seq_id): 20 | assert seq_id in range(5) 21 | seq = [] 22 | 23 | st = [0, 0, 0, 0, 1] 24 | for i in range(62): 25 | f = st[2] ^ st[4] 26 | # LFSRs not in "the usual way" 27 | if seq_id in (0, 2): 28 | st[3] ^= st[4] 29 | elif seq_id in (1, 3): 30 | st[1] ^= st[0] 31 | res = st.pop() 32 | st.insert(0, f) 33 | if seq_id >= 2: 34 | res ^= i % 2 35 | seq.append(res) 36 | 37 | return tuple(seq) 38 | 39 | 40 | class SIMON: 41 | """ 42 | one of the two lightweight block ciphers designed by NSA 43 | this one is optimized for hardware implementation 44 | """ 45 | def __init__(self, block_size, key_size, master_key=None): 46 | assert (block_size, key_size) in CONFIG 47 | self.block_size = block_size 48 | self.key_size = key_size 49 | self.__num_rounds, seq_id = CONFIG[(block_size, key_size)] 50 | self.__const_seq = get_const_seq(seq_id) 51 | assert len(self.__const_seq) == 62 52 | self.__dim = block_size / 2 53 | self.__mod = 1 << self.__dim 54 | if master_key is not None: 55 | self.change_key(master_key) 56 | 57 | def __lshift(self, x, i=1): 58 | return ((x << i) % self.__mod) | (x >> (self.__dim - i)) 59 | 60 | def __rshift(self, x, i=1): 61 | return ((x << (self.__dim - i)) % self.__mod) | (x >> i) 62 | 63 | def change_key(self, master_key): 64 | assert 0 <= master_key < (1 << self.key_size) 65 | c = (1 << self.__dim) - 4 66 | m = self.key_size / self.__dim 67 | self.__round_key = [] 68 | for i in range(m): 69 | self.__round_key.append(master_key % self.__mod) 70 | master_key >>= self.__dim 71 | for i in range(m, self.__num_rounds): 72 | k = self.__rshift(self.__round_key[-1], 3) 73 | if m == 4: 74 | k ^= self.__round_key[-3] 75 | k ^= self.__rshift(k) ^ self.__round_key[-m] 76 | k ^= c ^ self.__const_seq[(i - m) % 62] 77 | self.__round_key.append(k) 78 | 79 | def __feistel_round(self, l, r, k): 80 | f = (self.__lshift(l) & self.__lshift(l, 8)) ^ self.__lshift(l, 2) 81 | return r ^ f ^ k, l 82 | 83 | def encrypt(self, plaintext): 84 | assert 0 <= plaintext < (1 << self.block_size) 85 | l = plaintext >> self.__dim 86 | r = plaintext % self.__mod 87 | for i in range(self.__num_rounds): 88 | l, r = self.__feistel_round(l, r, self.__round_key[i]) 89 | ciphertext = (l << self.__dim) | r 90 | assert 0 <= ciphertext < (1 << self.block_size) 91 | return ciphertext 92 | 93 | def decrypt(self, ciphertext): 94 | assert 0 <= ciphertext < (1 << self.block_size) 95 | l = ciphertext >> self.__dim 96 | r = ciphertext % self.__mod 97 | for i in range(self.__num_rounds - 1, -1, -1): 98 | r, l = self.__feistel_round(r, l, self.__round_key[i]) 99 | plaintext = (l << self.__dim) | r 100 | assert 0 <= plaintext < (1 << self.block_size) 101 | return plaintext 102 | 103 | 104 | if __name__ == '__main__': 105 | const_seq = ( 106 | (1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 107 | 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 108 | 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0), 109 | (1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 110 | 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 111 | 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0), 112 | (1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 113 | 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 114 | 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1), 115 | (1, 1, 0, 1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 116 | 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 117 | 1, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1), 118 | (1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 119 | 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 0, 120 | 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1), 121 | ) 122 | for i in range(len(const_seq)): 123 | assert const_seq[i] == get_const_seq(i) 124 | 125 | test_vectors = ( 126 | # block_size, key_size, key, plaintext, ciphertext 127 | (32, 64, 128 | 0x1918111009080100, 129 | 0x65656877, 130 | 0xc69be9bb), 131 | (48, 72, 132 | 0x1211100a0908020100, 133 | 0x6120676e696c, 134 | 0xdae5ac292cac), 135 | (48, 96, 136 | 0x1a19181211100a0908020100, 137 | 0x72696320646e, 138 | 0x6e06a5acf156), 139 | (64, 96, 140 | 0x131211100b0a090803020100, 141 | 0x6f7220676e696c63, 142 | 0x5ca2e27f111a8fc8), 143 | (64, 128, 144 | 0x1b1a1918131211100b0a090803020100, 145 | 0x656b696c20646e75, 146 | 0x44c8fc20b9dfa07a), 147 | (96, 96, 148 | 0x0d0c0b0a0908050403020100, 149 | 0x2072616c6c69702065687420, 150 | 0x602807a462b469063d8ff082), 151 | (96, 144, 152 | 0x1514131211100d0c0b0a0908050403020100, 153 | 0x74616874207473756420666f, 154 | 0xecad1c6c451e3f59c5db1ae9), 155 | (128, 128, 156 | 0x0f0e0d0c0b0a09080706050403020100, 157 | 0x63736564207372656c6c657661727420, 158 | 0x49681b1e1e54fe3f65aa832af84e0bbc), 159 | (128, 192, 160 | 0x17161514131211100f0e0d0c0b0a09080706050403020100, 161 | 0x206572656874206e6568772065626972, 162 | 0xc4ac61effcdc0d4f6c9c8d6e2597b85b), 163 | (128, 256, 164 | 0x1f1e1d1c1b1a191817161514131211100f0e0d0c0b0a09080706050403020100, 165 | 0x74206e69206d6f6f6d69732061207369, 166 | 0x8d2b5579afc8a3a03bf72a87efe7b868) 167 | ) 168 | 169 | for bsize, ksize, key, plain, cipher in test_vectors: 170 | my_simon = SIMON(bsize, ksize, key) 171 | encrypted = my_simon.encrypt(plain) 172 | assert encrypted == cipher 173 | for i in range(1000): 174 | encrypted = my_simon.encrypt(encrypted) 175 | for i in range(1000): 176 | encrypted = my_simon.decrypt(encrypted) 177 | decrypted = my_simon.decrypt(encrypted) 178 | assert decrypted == plain 179 | 180 | print 'All tests passed' 181 | --------------------------------------------------------------------------------