Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/Crypto/Hash/CMAC.py: 63%

122 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-07 07:03 +0000

1# -*- coding: utf-8 -*- 

2# 

3# Hash/CMAC.py - Implements the CMAC algorithm 

4# 

5# =================================================================== 

6# The contents of this file are dedicated to the public domain. To 

7# the extent that dedication to the public domain is not available, 

8# everyone is granted a worldwide, perpetual, royalty-free, 

9# non-exclusive license to exercise all rights associated with the 

10# contents of this file for any purpose whatsoever. 

11# No rights are reserved. 

12# 

13# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 

14# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 

15# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 

16# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 

17# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 

18# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 

19# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 

20# SOFTWARE. 

21# =================================================================== 

22 

23"""CMAC (Cipher-based Message Authentication Code) algorithm 

24 

25CMAC is a MAC defined in `NIST SP 800-38B`_ and in RFC4493_ (for AES only) 

26and constructed using a block cipher. It was originally known as `OMAC1`_. 

27 

28The algorithm is sometimes named *X-CMAC* where *X* is the name 

29of the cipher (e.g. AES-CMAC). 

30 

31This is an example showing how to *create* an AES-CMAC: 

32 

33 >>> from Crypto.Hash import CMAC 

34 >>> from Crypto.Cipher import AES 

35 >>> 

36 >>> secret = b'Sixteen byte key' 

37 >>> cobj = CMAC.new(secret, ciphermod=AES) 

38 >>> cobj.update(b'Hello') 

39 >>> print cobj.hexdigest() 

40 

41And this is an example showing how to *check* an AES-CMAC: 

42 

43 >>> from Crypto.Hash import CMAC 

44 >>> from Crypto.Cipher import AES 

45 >>> 

46 >>> # We have received a message 'msg' together 

47 >>> # with its MAC 'mac' 

48 >>> 

49 >>> secret = b'Sixteen byte key' 

50 >>> cobj = CMAC.new(secret, ciphermod=AES) 

51 >>> cobj.update(msg) 

52 >>> try: 

53 >>> cobj.verify(mac) 

54 >>> print "The message '%s' is authentic" % msg 

55 >>> except ValueError: 

56 >>> print "The message or the key is wrong" 

57 

58.. _`NIST SP 800-38B`: http://csrc.nist.gov/publications/nistpubs/800-38B/SP_800-38B.pdf 

59.. _RFC4493: http://www.ietf.org/rfc/rfc4493.txt 

60.. _OMAC1: http://www.nuee.nagoya-u.ac.jp/labs/tiwata/omac/omac.html 

61""" 

62 

63__all__ = ['new', 'digest_size', 'CMAC' ] 

64 

65import sys 

66if sys.version_info[0] == 2 and sys.version_info[1] == 1: 

67 from Crypto.Util.py21compat import * 

68from Crypto.Util.py3compat import * 

69 

70from binascii import unhexlify 

71 

72from Crypto.Util.strxor import strxor 

73from Crypto.Util.number import long_to_bytes, bytes_to_long 

74 

75#: The size of the authentication tag produced by the MAC. 

76digest_size = None 

77 

78def _shift_bytes(bs, xor_lsb=0): 

79 num = (bytes_to_long(bs)<<1) ^ xor_lsb 

80 return long_to_bytes(num, len(bs))[-len(bs):] 

81 

82class _SmoothMAC(object): 

83 """Turn a MAC that only operates on aligned blocks of data 

84 into a MAC with granularity of 1 byte.""" 

85 

86 def __init__(self, block_size, msg=b(""), min_digest=0): 

87 self._bs = block_size 

88 #: Data waiting to be MAC-ed 

89 self._buffer = [] 

90 self._buffer_len = 0 

91 #: Data received via update() 

92 self._total_len = 0 

93 #: Minimum amount of bytes required by the final digest step 

94 self._min_digest = min_digest 

95 #: Block MAC object 

96 self._mac = None 

97 #: Cached digest 

98 self._tag = None 

99 if msg: 

100 self.update(msg) 

101 

102 def can_reduce(self): 

103 return (self._mac is not None) 

104 

105 def get_len(self): 

106 return self._total_len 

107 

108 def zero_pad(self): 

109 if self._buffer_len & (self._bs-1): 

110 npad = self._bs - self._buffer_len & (self._bs-1) 

111 self._buffer.append(bchr(0)*npad) 

112 self._buffer_len += npad 

113 

114 def update(self, data): 

115 # Optimization (try not to copy data if possible) 

116 if self._buffer_len==0 and self.can_reduce() and\ 

117 self._min_digest==0 and len(data)%self._bs==0: 

118 self._update(data) 

119 self._total_len += len(data) 

120 return 

121 

122 self._buffer.append(data) 

123 self._buffer_len += len(data) 

124 self._total_len += len(data) 

125 

126 # Feed data into MAC 

127 blocks, rem = divmod(self._buffer_len, self._bs) 

128 if rem<self._min_digest: 

129 blocks -= 1 

130 if blocks>0 and self.can_reduce(): 

131 aligned_data = blocks*self._bs 

132 buf = b("").join(self._buffer) 

133 self._update(buf[:aligned_data]) 

134 self._buffer = [ buf[aligned_data:] ] 

135 self._buffer_len -= aligned_data 

136 

137 def _deep_copy(self, target): 

138 # Copy everything by self._mac, since we don't know how to 

139 target._buffer = self._buffer[:] 

140 for m in [ '_bs', '_buffer_len', '_total_len', '_min_digest', '_tag' ]: 

141 setattr(target, m, getattr(self, m)) 

142 

143 def _update(self, data_block): 

144 """Delegate to the implementation the update 

145 of the MAC state given some new *block aligned* data.""" 

146 raise NotImplementedError("_update() must be still implemented") 

147 

148 def _digest(self, left_data): 

149 """Delegate to the implementation the computation 

150 of the final MAC given the current MAC state 

151 and the last piece of data (not block aligned).""" 

152 raise NotImplementedError("_digest() must be still implemented") 

153 

154 def digest(self): 

155 if self._tag: 

156 return self._tag 

157 if self._buffer_len>0: 

158 self.update(b("")) 

159 left_data = b("").join(self._buffer) 

160 self._tag = self._digest(left_data) 

161 return self._tag 

162 

163class CMAC(_SmoothMAC): 

164 """Class that implements CMAC""" 

165 

166 #: The size of the authentication tag produced by the MAC. 

167 digest_size = None 

168 

169 def __init__(self, key, msg = None, ciphermod = None): 

170 """Create a new CMAC object. 

171 

172 :Parameters: 

173 key : byte string 

174 secret key for the CMAC object. 

175 The key must be valid for the underlying cipher algorithm. 

176 For instance, it must be 16 bytes long for AES-128. 

177 msg : byte string 

178 The very first chunk of the message to authenticate. 

179 It is equivalent to an early call to `update`. Optional. 

180 ciphermod : module 

181 A cipher module from `Crypto.Cipher`. 

182 The cipher's block size must be 64 or 128 bits. 

183 It is recommended to use `Crypto.Cipher.AES`. 

184 """ 

185 

186 if ciphermod is None: 

187 raise TypeError("ciphermod must be specified (try AES)") 

188 

189 _SmoothMAC.__init__(self, ciphermod.block_size, msg, 1) 

190 

191 self._key = key 

192 self._factory = ciphermod 

193 

194 # Section 5.3 of NIST SP 800 38B 

195 if ciphermod.block_size==8: 

196 const_Rb = 0x1B 

197 elif ciphermod.block_size==16: 

198 const_Rb = 0x87 

199 else: 

200 raise TypeError("CMAC requires a cipher with a block size of 8 or 16 bytes, not %d" % 

201 (ciphermod.block_size,)) 

202 self.digest_size = ciphermod.block_size 

203 

204 # Compute sub-keys 

205 cipher = ciphermod.new(key, ciphermod.MODE_ECB) 

206 l = cipher.encrypt(bchr(0)*ciphermod.block_size) 

207 if bord(l[0]) & 0x80: 

208 self._k1 = _shift_bytes(l, const_Rb) 

209 else: 

210 self._k1 = _shift_bytes(l) 

211 if bord(self._k1[0]) & 0x80: 

212 self._k2 = _shift_bytes(self._k1, const_Rb) 

213 else: 

214 self._k2 = _shift_bytes(self._k1) 

215 

216 # Initialize CBC cipher with zero IV 

217 self._IV = bchr(0)*ciphermod.block_size 

218 self._mac = ciphermod.new(key, ciphermod.MODE_CBC, self._IV) 

219 

220 def update(self, msg): 

221 """Continue authentication of a message by consuming the next chunk of data. 

222 

223 Repeated calls are equivalent to a single call with the concatenation 

224 of all the arguments. In other words: 

225 

226 >>> m.update(a); m.update(b) 

227 

228 is equivalent to: 

229 

230 >>> m.update(a+b) 

231 

232 :Parameters: 

233 msg : byte string 

234 The next chunk of the message being authenticated 

235 """ 

236 

237 _SmoothMAC.update(self, msg) 

238 

239 def _update(self, data_block): 

240 self._IV = self._mac.encrypt(data_block)[-self._mac.block_size:] 

241 

242 def copy(self): 

243 """Return a copy ("clone") of the MAC object. 

244 

245 The copy will have the same internal state as the original MAC 

246 object. 

247 This can be used to efficiently compute the MAC of strings that 

248 share a common initial substring. 

249 

250 :Returns: A `CMAC` object 

251 """ 

252 obj = CMAC(self._key, ciphermod=self._factory) 

253 

254 _SmoothMAC._deep_copy(self, obj) 

255 obj._mac = self._factory.new(self._key, self._factory.MODE_CBC, self._IV) 

256 for m in [ '_tag', '_k1', '_k2', '_IV']: 

257 setattr(obj, m, getattr(self, m)) 

258 return obj 

259 

260 def digest(self): 

261 """Return the **binary** (non-printable) MAC of the message that has 

262 been authenticated so far. 

263 

264 This method does not change the state of the MAC object. 

265 You can continue updating the object after calling this function. 

266 

267 :Return: A byte string of `digest_size` bytes. It may contain non-ASCII 

268 characters, including null bytes. 

269 """ 

270 return _SmoothMAC.digest(self) 

271 

272 def _digest(self, last_data): 

273 if len(last_data)==self._bs: 

274 last_block = strxor(last_data, self._k1) 

275 else: 

276 last_block = strxor(last_data+bchr(128)+ 

277 bchr(0)*(self._bs-1-len(last_data)), self._k2) 

278 tag = self._mac.encrypt(last_block) 

279 return tag 

280 

281 def hexdigest(self): 

282 """Return the **printable** MAC of the message that has been 

283 authenticated so far. 

284 

285 This method does not change the state of the MAC object. 

286 

287 :Return: A string of 2* `digest_size` bytes. It contains only 

288 hexadecimal ASCII digits. 

289 """ 

290 return "".join(["%02x" % bord(x) 

291 for x in tuple(self.digest())]) 

292 

293 def verify(self, mac_tag): 

294 """Verify that a given **binary** MAC (computed by another party) is valid. 

295 

296 :Parameters: 

297 mac_tag : byte string 

298 The expected MAC of the message. 

299 :Raises ValueError: 

300 if the MAC does not match. It means that the message 

301 has been tampered with or that the MAC key is incorrect. 

302 """ 

303 

304 mac = self.digest() 

305 res = 0 

306 # Constant-time comparison 

307 for x,y in zip(mac, mac_tag): 

308 res |= bord(x) ^ bord(y) 

309 if res or len(mac_tag)!=self.digest_size: 

310 raise ValueError("MAC check failed") 

311 

312 def hexverify(self, hex_mac_tag): 

313 """Verify that a given **printable** MAC (computed by another party) is valid. 

314 

315 :Parameters: 

316 hex_mac_tag : string 

317 The expected MAC of the message, as a hexadecimal string. 

318 :Raises ValueError: 

319 if the MAC does not match. It means that the message 

320 has been tampered with or that the MAC key is incorrect. 

321 """ 

322 

323 self.verify(unhexlify(tobytes(hex_mac_tag))) 

324 

325def new(key, msg = None, ciphermod = None): 

326 """Create a new CMAC object. 

327 

328 :Parameters: 

329 key : byte string 

330 secret key for the CMAC object. 

331 The key must be valid for the underlying cipher algorithm. 

332 For instance, it must be 16 bytes long for AES-128. 

333 msg : byte string 

334 The very first chunk of the message to authenticate. 

335 It is equivalent to an early call to `CMAC.update`. Optional. 

336 ciphermod : module 

337 A cipher module from `Crypto.Cipher`. 

338 The cipher's block size must be 64 or 128 bits. 

339 Default is `Crypto.Cipher.AES`. 

340 

341 :Returns: A `CMAC` object 

342 """ 

343 return CMAC(key, msg, ciphermod)