|  | # 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() |