| # Python PRESENT implementation | 
 | # Version: 1.2 | 
 | # Date: 01/27/2021 | 
 | # | 
 | # Version 1.0: Original Version from https://github.com/doegox/python-cryptoplus | 
 | # Version 1.1: Minor modifications to run with Python >= 3.5 | 
 | # Version 1.2: Remove string to int conversions | 
 | # | 
 | # ============================================================================= | 
 | # Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com> | 
 | #                    & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> ) | 
 | # | 
 | # 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: | 
 | # | 
 | # The above copyright notice and this permission notice shall be included in | 
 | # all copies or substantial portions of the Software. | 
 | # | 
 | # 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. | 
 | # ============================================================================= | 
 |  | 
 | """ PRESENT block cipher implementation | 
 |  | 
 | USAGE EXAMPLE: | 
 | --------------- | 
 | Importing: | 
 | ----------- | 
 | >>> from pypresent import Present | 
 |  | 
 | Encrypting with a 80-bit key: | 
 | ------------------------------ | 
 | >>> key = 0x00000000000000000000 | 
 | >>> plain = 0x0000000000000000 | 
 | >>> cipher = Present(key, keylen=80) | 
 | >>> encrypted = cipher.encrypt(plain) | 
 | >>> hex(encrypted) | 
 | '0x5579c1387b228445' | 
 | >>> decrypted = cipher.decrypt(encrypted) | 
 | >>> hex(decrypted) | 
 | '0x0' | 
 |  | 
 | Encrypting with a 128-bit key: | 
 | ------------------------------- | 
 | >>> key = 0x0123456789abcdef0123456789abcdef | 
 | >>> plain = 0x0123456789abcdef | 
 | >>> cipher = Present(key) | 
 | >>> encrypted = cipher.encrypt(plain) | 
 | >>> hex(encrypted) | 
 | '0xe9d28685e671dd6' | 
 | >>> decrypted = cipher.decrypt(encrypted) | 
 | >>> hex(decrypted) | 
 | '0x123456789abcdef' | 
 |  | 
 | fully based on standard specifications: http://www.crypto.ruhr-uni-bochum.de/imperia/md/content/texte/publications/conferences/present_ches2007.pdf | 
 | test vectors: http://www.crypto.ruhr-uni-bochum.de/imperia/md/content/texte/publications/conferences/slides/present_testvectors.zip | 
 | """ # noqa: E501 E261 | 
 |  | 
 |  | 
 | class Present: | 
 |     def __init__(self, key, rounds=32, keylen=128): | 
 |         """Create a PRESENT cipher object | 
 |  | 
 |                 key:    the key as an integer | 
 |                 rounds: the number of rounds as an integer, 32 by default | 
 |                 keylen: length of the key in bits | 
 |                 """ | 
 |         self.rounds = rounds | 
 |         if keylen == 80 and key < 2**80: | 
 |             self.roundkeys = generateRoundkeys80(key, | 
 |                                                  self.rounds) | 
 |         elif keylen == 128 and key < 2**128: | 
 |             self.roundkeys = generateRoundkeys128(key, | 
 |                                                   self.rounds) | 
 |         else: | 
 |             raise ValueError("keylen be 80 or 128") | 
 |  | 
 |     def encrypt(self, block): | 
 |         """Encrypt 1 block (8 bytes) | 
 |  | 
 |                 Input:  plaintext block as raw string | 
 |                 Output: ciphertext block as raw string | 
 |                 """ | 
 |         state = block | 
 |         for i in range(self.rounds - 1): | 
 |             state = addRoundKey(state, self.roundkeys[i]) | 
 |             state = sBoxLayer(state) | 
 |             state = pLayer(state) | 
 |         cipher = addRoundKey(state, self.roundkeys[-1]) | 
 |         return cipher | 
 |  | 
 |     def decrypt(self, block): | 
 |         """Decrypt 1 block (8 bytes) | 
 |  | 
 |                 Input:  ciphertext block as raw string | 
 |                 Output: plaintext block as raw string | 
 |                 """ | 
 |         state = block | 
 |         for i in range(self.rounds - 1): | 
 |             state = addRoundKey(state, self.roundkeys[-i - 1]) | 
 |             state = pLayer_dec(state) | 
 |             state = sBoxLayer_dec(state) | 
 |         decipher = addRoundKey(state, self.roundkeys[0]) | 
 |         return decipher | 
 |  | 
 |     def get_block_size(self): | 
 |         return 8 | 
 |  | 
 |  | 
 | #        0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f | 
 | Sbox = [ | 
 |     0xc, 0x5, 0x6, 0xb, 0x9, 0x0, 0xa, 0xd, 0x3, 0xe, 0xf, 0x8, 0x4, 0x7, 0x1, | 
 |     0x2 | 
 | ] | 
 | Sbox_inv = [Sbox.index(x) for x in range(16)] | 
 | PBox = [ | 
 |     0, 16, 32, 48, 1, 17, 33, 49, 2, 18, 34, 50, 3, 19, 35, 51, 4, 20, 36, 52, | 
 |     5, 21, 37, 53, 6, 22, 38, 54, 7, 23, 39, 55, 8, 24, 40, 56, 9, 25, 41, 57, | 
 |     10, 26, 42, 58, 11, 27, 43, 59, 12, 28, 44, 60, 13, 29, 45, 61, 14, 30, 46, | 
 |     62, 15, 31, 47, 63 | 
 | ] | 
 | PBox_inv = [PBox.index(x) for x in range(64)] | 
 |  | 
 |  | 
 | def generateRoundkeys80(key, rounds): | 
 |     """Generate the roundkeys for a 80-bit key | 
 |  | 
 |         Input: | 
 |                 key:    the key as a 80-bit integer | 
 |                 rounds: the number of rounds as an integer | 
 |         Output: list of 64-bit roundkeys as integers""" | 
 |     roundkeys = [] | 
 |     for i in range(1, rounds + 1):  # (K1 ... K32) | 
 |         # rawkey: used in comments to show what happens at bitlevel | 
 |         # rawKey[0:64] | 
 |         roundkeys.append(key >> 16) | 
 |         # 1. Shift | 
 |         # rawKey[19:len(rawKey)]+rawKey[0:19] | 
 |         key = ((key & (2**19 - 1)) << 61) + (key >> 19) | 
 |         # 2. SBox | 
 |         # rawKey[76:80] = S(rawKey[76:80]) | 
 |         key = (Sbox[key >> 76] << 76) + (key & (2**76 - 1)) | 
 |         # 3. Salt | 
 |         # rawKey[15:20] ^ i | 
 |         key ^= i << 15 | 
 |     return roundkeys | 
 |  | 
 |  | 
 | def generateRoundkeys128(key, rounds): | 
 |     """Generate the roundkeys for a 128-bit key | 
 |  | 
 |         Input: | 
 |                 key:    the key as a 128-bit integer | 
 |                 rounds: the number of rounds as an integer | 
 |         Output: list of 64-bit roundkeys as integers""" | 
 |     roundkeys = [] | 
 |     for i in range(1, rounds + 1):  # (K1 ... K32) | 
 |         # rawkey: used in comments to show what happens at bitlevel | 
 |         roundkeys.append(key >> 64) | 
 |         # 1. Shift | 
 |         key = ((key & (2**67 - 1)) << 61) + (key >> 67) | 
 |         # 2. SBox | 
 |         key = (Sbox[key >> 124] << 124) + (Sbox[ | 
 |             (key >> 120) & 0xF] << 120) + (key & (2**120 - 1)) | 
 |         # 3. Salt | 
 |         # rawKey[62:67] ^ i | 
 |         key ^= i << 62 | 
 |     return roundkeys | 
 |  | 
 |  | 
 | def addRoundKey(state, roundkey): | 
 |     return state ^ roundkey | 
 |  | 
 |  | 
 | def sBoxLayer(state): | 
 |     """SBox function for encryption | 
 |  | 
 |         Input:  64-bit integer | 
 |         Output: 64-bit integer""" | 
 |  | 
 |     output = 0 | 
 |     for i in range(16): | 
 |         output += Sbox[(state >> (i * 4)) & 0xF] << (i * 4) | 
 |     return output | 
 |  | 
 |  | 
 | def sBoxLayer_dec(state): | 
 |     """Inverse SBox function for decryption | 
 |  | 
 |         Input:  64-bit integer | 
 |         Output: 64-bit integer""" | 
 |     output = 0 | 
 |     for i in range(16): | 
 |         output += Sbox_inv[(state >> (i * 4)) & 0xF] << (i * 4) | 
 |     return output | 
 |  | 
 |  | 
 | def pLayer(state): | 
 |     """Permutation layer for encryption | 
 |  | 
 |         Input:  64-bit integer | 
 |         Output: 64-bit integer""" | 
 |     output = 0 | 
 |     for i in range(64): | 
 |         output += ((state >> i) & 0x01) << PBox[i] | 
 |     return output | 
 |  | 
 |  | 
 | def pLayer_dec(state): | 
 |     """Permutation layer for decryption | 
 |  | 
 |         Input:  64-bit integer | 
 |         Output: 64-bit integer""" | 
 |     output = 0 | 
 |     for i in range(64): | 
 |         output += ((state >> i) & 0x01) << PBox_inv[i] | 
 |     return output | 
 |  | 
 |  | 
 | def _test(): | 
 |     import doctest | 
 |     doctest.testmod() | 
 |  | 
 |  | 
 | if __name__ == "__main__": | 
 |     _test() |