Slight change in copyright notice
[python-cryptoplus.git] / src / CryptoPlus / Cipher / pypresent.py
blobb483d82bd5f07e73bc1e0279e4dde6c3f907d51c
1 # Python PRESENT implementation
2 # Version: 1.0
3 # Date: 13/10/2008
5 # =============================================================================
6 # Copyright (c) 2008 Christophe Oosterlynck <christophe.oosterlynck_AT_gmail.com>
7 # & NXP ( Philippe Teuwen <philippe.teuwen_AT_nxp.com> )
9 # Permission is hereby granted, free of charge, to any person obtaining a copy
10 # of this software and associated documentation files (the "Software"), to deal
11 # in the Software without restriction, including without limitation the rights
12 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 # copies of the Software, and to permit persons to whom the Software is
14 # furnished to do so, subject to the following conditions:
16 # The above copyright notice and this permission notice shall be included in
17 # all copies or substantial portions of the Software.
19 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 # THE SOFTWARE.
26 # =============================================================================
28 """ PRESENT block cipher implementation
30 USAGE EXAMPLE:
31 ---------------
32 Importing:
33 -----------
34 >>> from pypresent import Present
36 Encrypting with a 80-bit key:
37 ------------------------------
38 >>> key = "00000000000000000000".decode('hex')
39 >>> plain = "0000000000000000".decode('hex')
40 >>> cipher = Present(key)
41 >>> encrypted = cipher.encrypt(plain)
42 >>> encrypted.encode('hex')
43 '5579c1387b228445'
44 >>> decrypted = cipher.decrypt(encrypted)
45 >>> decrypted.encode('hex')
46 '0000000000000000'
48 Encrypting with a 128-bit key:
49 -------------------------------
50 >>> key = "0123456789abcdef0123456789abcdef".decode('hex')
51 >>> plain = "0123456789abcdef".decode('hex')
52 >>> cipher = Present(key)
53 >>> encrypted = cipher.encrypt(plain)
54 >>> encrypted.encode('hex')
55 '0e9d28685e671dd6'
56 >>> decrypted = cipher.decrypt(encrypted)
57 >>> decrypted.encode('hex')
58 '0123456789abcdef'
60 fully based on standard specifications: http://www.crypto.ruhr-uni-bochum.de/imperia/md/content/texte/publications/conferences/present_ches2007.pdf
61 test vectors: http://www.crypto.ruhr-uni-bochum.de/imperia/md/content/texte/publications/conferences/slides/present_testvectors.zip
62 """
63 class Present:
65 def __init__(self,key,rounds=32):
66 """Create a PRESENT cipher object
68 key: the key as a 128-bit or 80-bit rawstring
69 rounds: the number of rounds as an integer, 32 by default
70 """
71 self.rounds = rounds
72 if len(key) * 8 == 80:
73 self.roundkeys = generateRoundkeys80(string2number(key),self.rounds)
74 elif len(key) * 8 == 128:
75 self.roundkeys = generateRoundkeys128(string2number(key),self.rounds)
76 else:
77 raise ValueError, "Key must be a 128-bit or 80-bit rawstring"
79 def encrypt(self,block):
80 """Encrypt 1 block (8 bytes)
82 Input: plaintext block as raw string
83 Output: ciphertext block as raw string
84 """
85 state = string2number(block)
86 for i in xrange (self.rounds-1):
87 state = addRoundKey(state,self.roundkeys[i])
88 state = sBoxLayer(state)
89 state = pLayer(state)
90 cipher = addRoundKey(state,self.roundkeys[-1])
91 return number2string_N(cipher,8)
93 def decrypt(self,block):
94 """Decrypt 1 block (8 bytes)
96 Input: ciphertext block as raw string
97 Output: plaintext block as raw string
98 """
99 state = string2number(block)
100 for i in xrange (self.rounds-1):
101 state = addRoundKey(state,self.roundkeys[-i-1])
102 state = pLayer_dec(state)
103 state = sBoxLayer_dec(state)
104 decipher = addRoundKey(state,self.roundkeys[0])
105 return number2string_N(decipher,8)
107 def get_block_size(self):
108 return 8
110 # 0 1 2 3 4 5 6 7 8 9 a b c d e f
111 Sbox= [0xc,0x5,0x6,0xb,0x9,0x0,0xa,0xd,0x3,0xe,0xf,0x8,0x4,0x7,0x1,0x2]
112 Sbox_inv = [Sbox.index(x) for x in xrange(16)]
113 PBox = [0,16,32,48,1,17,33,49,2,18,34,50,3,19,35,51,
114 4,20,36,52,5,21,37,53,6,22,38,54,7,23,39,55,
115 8,24,40,56,9,25,41,57,10,26,42,58,11,27,43,59,
116 12,28,44,60,13,29,45,61,14,30,46,62,15,31,47,63]
117 PBox_inv = [PBox.index(x) for x in xrange(64)]
119 def generateRoundkeys80(key,rounds):
120 """Generate the roundkeys for a 80-bit key
122 Input:
123 key: the key as a 80-bit integer
124 rounds: the number of rounds as an integer
125 Output: list of 64-bit roundkeys as integers"""
126 roundkeys = []
127 for i in xrange(1,rounds+1): # (K1 ... K32)
128 # rawkey: used in comments to show what happens at bitlevel
129 # rawKey[0:64]
130 roundkeys.append(key >>16)
131 #1. Shift
132 #rawKey[19:len(rawKey)]+rawKey[0:19]
133 key = ((key & (2**19-1)) << 61) + (key >> 19)
134 #2. SBox
135 #rawKey[76:80] = S(rawKey[76:80])
136 key = (Sbox[key >> 76] << 76)+(key & (2**76-1))
137 #3. Salt
138 #rawKey[15:20] ^ i
139 key ^= i << 15
140 return roundkeys
142 def generateRoundkeys128(key,rounds):
143 """Generate the roundkeys for a 128-bit key
145 Input:
146 key: the key as a 128-bit integer
147 rounds: the number of rounds as an integer
148 Output: list of 64-bit roundkeys as integers"""
149 roundkeys = []
150 for i in xrange(1,rounds+1): # (K1 ... K32)
151 # rawkey: used in comments to show what happens at bitlevel
152 roundkeys.append(key >>64)
153 #1. Shift
154 key = ((key & (2**67-1)) << 61) + (key >> 67)
155 #2. SBox
156 key = (Sbox[key >> 124] << 124)+(Sbox[(key >> 120) & 0xF] << 120)+(key & (2**120-1))
157 #3. Salt
158 #rawKey[62:67] ^ i
159 key ^= i << 62
160 return roundkeys
162 def addRoundKey(state,roundkey):
163 return state ^ roundkey
165 def sBoxLayer(state):
166 """SBox function for encryption
168 Input: 64-bit integer
169 Output: 64-bit integer"""
171 output = 0
172 for i in xrange(16):
173 output += Sbox[( state >> (i*4)) & 0xF] << (i*4)
174 return output
176 def sBoxLayer_dec(state):
177 """Inverse SBox function for decryption
179 Input: 64-bit integer
180 Output: 64-bit integer"""
181 output = 0
182 for i in xrange(16):
183 output += Sbox_inv[( state >> (i*4)) & 0xF] << (i*4)
184 return output
186 def pLayer(state):
187 """Permutation layer for encryption
189 Input: 64-bit integer
190 Output: 64-bit integer"""
191 output = 0
192 for i in xrange(64):
193 output += ((state >> i) & 0x01) << PBox[i]
194 return output
196 def pLayer_dec(state):
197 """Permutation layer for decryption
199 Input: 64-bit integer
200 Output: 64-bit integer"""
201 output = 0
202 for i in xrange(64):
203 output += ((state >> i) & 0x01) << PBox_inv[i]
204 return output
206 def string2number(i):
207 """ Convert a string to a number
209 Input: string (big-endian)
210 Output: long or integer
212 return int(i.encode('hex'),16)
214 def number2string_N(i, N):
215 """Convert a number to a string of fixed size
217 i: long or integer
218 N: length of string
219 Output: string (big-endian)
221 s = '%0*x' % (N*2, i)
222 return s.decode('hex')
224 def _test():
225 import doctest
226 doctest.testmod()
228 if __name__ == "__main__":
229 _test()