Package keyczar :: Module keys
[hide private]
[frames] | no frames]

Source Code for Module keyczar.keys

  1  #!/usr/bin/python2.4 
  2  # 
  3  # Copyright 2008 Google Inc. 
  4  # 
  5  # Licensed under the Apache License, Version 2.0 (the "License"); 
  6  # you may not use this file except in compliance with the License. 
  7  # You may obtain a copy of the License at 
  8  #  
  9  #      http://www.apache.org/licenses/LICENSE-2.0 
 10  #  
 11  # Unless required by applicable law or agreed to in writing, software 
 12  # distributed under the License is distributed on an "AS IS" BASIS, 
 13  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 14  # See the License for the specific language governing permissions and 
 15  # limitations under the License. 
 16   
 17  """Represents cryptographic keys in Keyczar. 
 18   
 19  Identifies a key by its hash and type. Includes several subclasses 
 20  of base class Key. 
 21   
 22  @author: arkajit.dey@gmail.com (Arkajit Dey) 
 23  """ 
 24   
 25  import hmac 
 26  import math 
 27  import random 
 28  import sha 
 29   
 30  from Crypto.Cipher import AES 
 31  from Crypto.PublicKey import DSA 
 32  from Crypto.PublicKey import RSA 
 33  import simplejson 
 34   
 35  import errors 
 36  import keyczar 
 37  import keyinfo 
 38  import util 
39 40 #TODO: Note that simplejson deals in Unicode strings. So perhaps we should 41 #modify all Read() methods to wrap data obtained from simplejson with str(). 42 #Currently, only problem arose with base64 conversions -- this was dealt with 43 #directly in the encode/decode methods. Luckily 'hello' == u'hello'. 44 45 -def GenKey(type, size=None):
46 """ 47 Generates a key of the given type and length. 48 49 @param type: the type of key to generate 50 @type type: L{keyinfo.KeyType} 51 52 @param size: the length in bits of the key to be generated 53 @type size: integer 54 55 @return: the generated key of the given type and size 56 57 @raise KeyczarError: if type is a public key or unsupported. 58 """ 59 if size is None: 60 size = type.default_size 61 try: 62 return {keyinfo.AES: AesKey.Generate, 63 keyinfo.HMAC_SHA1: HmacKey.Generate, 64 keyinfo.DSA_PRIV: DsaPrivateKey.Generate, 65 keyinfo.RSA_PRIV: RsaPrivateKey.Generate}[type](size) 66 except KeyError: 67 if type == keyinfo.DSA_PUB or type == keyinfo.RSA_PUB: 68 msg = "Public keys of type %s must be exported from private keys." 69 else: 70 msg = "Unsupported key type: %s" 71 raise errors.KeyczarError(msg % type)
72
73 -def ReadKey(type, key):
74 """ 75 Reads a key of the given type from a JSON string representation. 76 77 @param type: the type of key to read 78 @type type: L{keyinfo.KeyType} 79 80 @param key: the JSON string representation of the key 81 @type key: string 82 83 @return: the key object read from the JSON string 84 85 @raise KeyczarError: if type is unsupported 86 """ 87 try: 88 return {keyinfo.AES: AesKey.Read, 89 keyinfo.HMAC_SHA1: HmacKey.Read, 90 keyinfo.DSA_PRIV: DsaPrivateKey.Read, 91 keyinfo.RSA_PRIV: RsaPrivateKey.Read, 92 keyinfo.DSA_PUB: DsaPublicKey.Read, 93 keyinfo.RSA_PUB: RsaPublicKey.Read}[type](key) 94 except KeyError: 95 raise errors.KeyczarError("Unsupported key type: %s" % type)
96
97 -class Key(object):
98 99 """Parent class for Keyczar Keys.""" 100
101 - def __init__(self, type):
102 self.type = type 103 self.__size = self.type.default_size # initially default
104
105 - def __SetSize(self, new_size):
106 if self.type.IsValidSize(new_size): 107 self.__size = new_size
108
109 - def _GetKeyString(self):
110 """Return the key as a string. Abstract method."""
111
112 - def __GetKeyString(self):
113 """Indirect getter for the key string.""" 114 return self._GetKeyString()
115
116 - def _Hash(self):
117 """Compute and return the hash id of this key. Can override default hash.""" 118 fullhash = util.Hash(util.IntToBytes(len(self.key_bytes)), self.key_bytes) 119 return util.Encode(fullhash[:keyczar.KEY_HASH_SIZE])
120
121 - def __Hash(self):
122 """Indirect getter for hash.""" 123 return self._Hash()
124 125 hash = property(__Hash, doc="""The hash id of the key.""") 126 size = property(lambda self: self.__size, __SetSize, 127 doc="""The size of the key in bits.""") 128 key_string = property(__GetKeyString, doc="""The key as a Base64 string.""") 129 key_bytes = property(lambda self: util.Decode(self.key_string), 130 doc="""The key as bytes.""") 131
132 - def Header(self):
133 """Return the 5-byte header string including version byte, 4-byte hash.""" 134 return chr(keyczar.VERSION) + util.Decode(self.hash)
135
136 -class SymmetricKey(Key):
137 """Parent class for symmetric keys such as AES, HMAC-SHA1""" 138
139 - def __init__(self, type, key_string):
140 Key.__init__(self, type) 141 self.__key_string = key_string
142
143 - def _GetKeyString(self):
144 """Return the key as a string.""" 145 return self.__key_string
146
147 -class AsymmetricKey(Key):
148 """Parent class for asymmetric keys.""" 149
150 - def __init__(self, type, params):
151 Key.__init__(self, type) 152 self._params = params
153
154 -class AesKey(SymmetricKey):
155 """Represents AES symmetric private keys.""" 156
157 - def __init__(self, key_string, hmac_key, size=keyinfo.AES.default_size, 158 mode=keyinfo.CBC):
159 SymmetricKey.__init__(self, keyinfo.AES, key_string) 160 self.hmac_key = hmac_key 161 self.block_size = len(self.key_bytes) 162 self.size = size 163 self.mode = mode
164
165 - def __str__(self):
166 return simplejson.dumps({"mode": str(self.mode), 167 "size": self.size, 168 "aesKeyString": self.key_string, 169 "hmacKey": simplejson.loads(str(self.hmac_key))})
170
171 - def _Hash(self):
172 fullhash = util.Hash(util.IntToBytes(len(self.key_bytes)), self.key_bytes, 173 util.IntToBytes(keyczar.KEY_HASH_SIZE), 174 util.Decode(self.hmac_key.hash)) 175 return util.Encode(fullhash[:keyczar.KEY_HASH_SIZE])
176 177 @staticmethod
178 - def Generate(size=keyinfo.AES.default_size):
179 """ 180 Return a newly generated AES key. 181 182 @param size: length of key in bits to generate 183 @type size: integer 184 185 @return: an AES key 186 @rtype: L{AesKey} 187 """ 188 key_bytes = util.RandBytes(size / 8) 189 key_string = util.Encode(key_bytes) 190 hmac_key = HmacKey.Generate() # use default HMAC-SHA1 key size 191 return AesKey(key_string, hmac_key, size)
192 193 @staticmethod
194 - def Read(key):
195 """ 196 Reads an AES key from a JSON string representation of it. 197 198 @param key: a JSON representation of an AES key 199 @type key: string 200 201 @return: an AES key 202 @rtype: L{AesKey} 203 """ 204 aes = simplejson.loads(key) 205 hmac = aes['hmacKey'] 206 return AesKey(aes['aesKeyString'], 207 HmacKey(hmac['hmacKeyString'], hmac['size']), 208 aes['size'], keyinfo.GetMode(aes['mode']))
209
210 - def __Pad(self, data):
211 """ 212 Returns the data padded using PKCS5. 213 214 For a block size B and data with N bytes in the last block, PKCS5 215 pads the data with B-N bytes of the value B-N. 216 217 @param data: data to be padded 218 @type data: string 219 220 @return: PKCS5 padded string 221 @rtype: string 222 """ 223 pad = self.block_size - len(data) % self.block_size 224 return data + pad * chr(pad)
225
226 - def __UnPad(self, padded):
227 """ 228 Returns the unpadded version of a data padded using PKCS5. 229 230 @param padded: string padded with PKCS5 231 @type padded: string 232 233 @return: original, unpadded string 234 @rtype: string 235 """ 236 pad = ord(padded[-1]) 237 return padded[:-pad]
238
239 - def Encrypt(self, data):
240 """ 241 Return ciphertext byte string containing Header|IV|Ciph|Sig. 242 243 @param data: plaintext to be encrypted. 244 @type data: string 245 246 @return: raw byte string ciphertext formatted to have Header|IV|Ciph|Sig. 247 @rtype: string 248 """ 249 data = self.__Pad(data) 250 iv_bytes = util.RandBytes(self.block_size) 251 ciph_bytes = AES.new(self.key_bytes, AES.MODE_CBC, iv_bytes).encrypt(data) 252 msg_bytes = self.Header() + iv_bytes + ciph_bytes 253 sig_bytes = self.hmac_key.Sign(msg_bytes) # Sign bytes 254 return msg_bytes + sig_bytes
255
256 - def Decrypt(self, input_bytes):
257 """ 258 Decrypts the given ciphertext. 259 260 @param input_bytes: raw byte string formatted as Header|IV|Ciph|Sig where 261 Sig is the signature over the entire payload (Header|IV|Ciph). 262 @type input_bytes: string 263 264 @return: plaintext message 265 @rtype: string 266 267 @raise ShortCiphertextError: if the ciphertext is too short to have IV & Sig 268 @raise InvalidSignatureError: if the signature doesn't correspond to payload 269 """ 270 data_bytes = input_bytes[keyczar.HEADER_SIZE:] # remove header 271 if len(data_bytes) < self.block_size + util.HLEN: # IV + sig 272 raise errors.ShortCiphertextError(len(data_bytes)) 273 274 iv_bytes = data_bytes[:self.block_size] # first block of bytes is the IV 275 ciph_bytes = data_bytes[self.block_size:-util.HLEN] 276 sig_bytes = data_bytes[-util.HLEN:] # last 20 bytes are sig 277 if not self.hmac_key.Verify(input_bytes[:-util.HLEN], sig_bytes): 278 raise errors.InvalidSignatureError() 279 280 plain = AES.new(self.key_bytes, AES.MODE_CBC, iv_bytes).decrypt(ciph_bytes) 281 return self.__UnPad(plain)
282
283 -class HmacKey(SymmetricKey):
284 """Represents HMAC-SHA1 symmetric private keys.""" 285
286 - def __init__(self, key_string, size=keyinfo.HMAC_SHA1.default_size):
289
290 - def __str__(self):
291 return simplejson.dumps({"size": self.size, 292 "hmacKeyString": self.key_string})
293 294 @staticmethod
295 - def Generate(size=keyinfo.HMAC_SHA1.default_size):
296 """ 297 Return a newly generated HMAC-SHA1 key. 298 299 @param size: length of key in bits to generate 300 @type size: integer 301 302 @return: an HMAC-SHA1 key 303 @rtype: L{HmacKey} 304 """ 305 key_bytes = util.RandBytes(size / 8) 306 key_string = util.Encode(key_bytes) 307 return HmacKey(key_string, size)
308 309 @staticmethod
310 - def Read(key):
311 """ 312 Reads an HMAC-SHA1 key from a JSON string representation of it. 313 314 @param key: a JSON representation of an HMAC-SHA1 key 315 @type key: string 316 317 @return: an HMAC-SHA1 key 318 @rtype: L{HmacKey} 319 """ 320 mac = simplejson.loads(key) 321 return HmacKey(mac['hmacKeyString'], mac['size'])
322
323 - def Sign(self, msg):
324 """ 325 Return raw byte string of signature on the message. 326 327 @param msg: message to be signed 328 @type msg: string 329 330 @return: raw byte string signature 331 @rtype: string 332 """ 333 return hmac.new(self.key_bytes, msg, sha).digest()
334
335 - def Verify(self, msg, sig_bytes):
336 """ 337 Return True if the signature corresponds to the message. 338 339 @param msg: message that has been signed 340 @type msg: string 341 342 @param sig_bytes: raw byte string of the signature 343 @type sig_bytes: string 344 345 @return: True if signature is valid for message. False otherwise. 346 @rtype: boolean 347 """ 348 return self.Sign(msg) == sig_bytes
349
350 -class PrivateKey(AsymmetricKey):
351 """Represents private keys in Keyczar for asymmetric key pairs.""" 352
353 - def __init__(self, type, params, pkcs8, pub):
354 AsymmetricKey.__init__(self, type, params) 355 self.pkcs8 = pkcs8 356 self.public_key = pub
357
358 - def __str__(self):
359 return simplejson.dumps({"publicKey": simplejson.loads( 360 str(self.public_key)), 361 "pkcs8": self.pkcs8, 362 "size": self.size})
363
364 - def _GetKeyString(self):
365 return self.pkcs8
366
367 - def _Hash(self):
368 return self.public_key.hash
369
370 -class PublicKey(AsymmetricKey):
371 """Represents public keys in Keyczar for asymmetric key pairs.""" 372
373 - def __init__(self, type, params, x509):
374 AsymmetricKey.__init__(self, type, params) 375 self.x509 = x509
376
377 - def __str__(self):
378 return simplejson.dumps({"x509": self.x509, "size": self.size})
379
380 - def _GetKeyString(self):
381 return self.x509
382
383 -class DsaPrivateKey(PrivateKey):
384 """Represents DSA private keys in an asymmetric DSA key pair.""" 385
386 - def __init__(self, params, pkcs8, pub, key, 387 size=keyinfo.DSA_PRIV.default_size):
388 PrivateKey.__init__(self, keyinfo.DSA_PRIV, params, pkcs8, pub) 389 self.key = key 390 self.size = size
391 392 @staticmethod
393 - def Generate(size=keyinfo.DSA_PRIV.default_size):
394 """ 395 Return a newly generated DSA private key. 396 397 @param size: length of key in bits to generate 398 @type size: integer 399 400 @return: a DSA private key 401 @rtype: L{DsaPrivateKey} 402 """ 403 key = DSA.generate(size, util.RandBytes) 404 params = {'g': key.g, 'p': key.p, 'q': key.q, 'y': key.y, 'x': key.x} 405 pubkey = key.publickey() 406 pub_params = {'g': pubkey.g, 'p': pubkey.p, 'q': pubkey.q, 'y': pubkey.y} 407 pub = DsaPublicKey(pub_params, util.ExportDsaX509(pub_params), pubkey, size) 408 return DsaPrivateKey(params, util.ExportDsaPkcs8(params), pub, key, size)
409 410 @staticmethod
411 - def Read(key):
412 """ 413 Reads a DSA private key from a JSON string representation of it. 414 415 @param key: a JSON representation of a DSA private key 416 @type key: string 417 418 @return: an DSA private key 419 @rtype: L{DsaPrivateKey} 420 """ 421 dsa = simplejson.loads(key) 422 pub = DsaPublicKey.Read(simplejson.dumps(dsa['publicKey'])) 423 params = util.ParsePkcs8(dsa['pkcs8']) 424 key = DSA.construct((pub._params['y'], params['g'], params['p'], params['q'], 425 params['x'])) 426 return DsaPrivateKey(params, dsa['pkcs8'], pub, key, dsa['size'])
427
428 - def Sign(self, msg):
429 """ 430 Return raw byte string of signature on the message. 431 432 @param msg: message to be signed 433 @type msg: string 434 435 @return: byte string formatted as an ASN.1 sequnce of r and s 436 @rtype: string 437 """ 438 k = random.randint(2, self.key.q-1) # need to chose a random k per-message 439 (r, s) = self.key.sign(util.Hash(msg), k) 440 return util.MakeDsaSig(r, s)
441
442 - def Verify(self, msg, sig):
443 """@see: L{DsaPublicKey.Verify}""" 444 return self.public_key.Verify(msg, sig)
445
446 -class RsaPrivateKey(PrivateKey):
447 """Represents RSA private keys in an asymmetric RSA key pair.""" 448
449 - def __init__(self, params, pkcs8, pub, key, 450 size=keyinfo.RSA_PRIV.default_size):
451 PrivateKey.__init__(self, keyinfo.RSA_PRIV, params, pkcs8, pub) 452 self.key = key # instance of PyCrypto RSA key 453 self.size = size
454
455 - def __Decode(self, em, p=""):
456 if len(p) >= 2**61 or len(em) < 2 * util.HLEN + 2: 457 # 2^61 = the input limit for SHA-1 458 raise errors.KeyczarError("OAEP Decoding Error") 459 # PyCrypto strips all leading zeros, can't check it 460 masked_seed = em[:util.HLEN] 461 masked_db = em[util.HLEN:] 462 seed_mask = util.MGF(masked_db, util.HLEN) 463 seed = util.Xor(masked_seed, seed_mask) 464 db_mask = util.MGF(seed, len(em) - util.HLEN) # em already stripped of 0 465 db = util.Xor(masked_db, db_mask) 466 ph = db[:util.HLEN] 467 one = db.find(chr(1), util.HLEN) 468 if ph != util.Hash(p) or one == -1: 469 raise errors.KeyczarError("OAEP Decoding Error") 470 return db[one+1:] # the message
471 472 @staticmethod
473 - def Generate(size=keyinfo.RSA_PRIV.default_size):
474 """ 475 Return a newly generated RSA private key. 476 477 @param size: length of key in bits to generate 478 @type size: integer 479 480 @return: a RSA private key 481 @rtype: L{RsaPrivateKey} 482 """ 483 key = RSA.generate(size, util.RandBytes) 484 params = {'n': key.n, 'e': key.e, 'd': key.d, 'p': key.q, 'q': key.p, 485 'dp': key.d % (key.q - 1), 'dq': key.d % (key.p - 1), 486 'invq': key.u} 487 #NOTE: PyCrypto stores p < q, u = p^{-1} mod q 488 #But OpenSSL and PKCS8 stores q < p, invq = q^{-1} mod p 489 #So we have to reverse the p and q values 490 pubkey = key.publickey() 491 pub_params = {'n': pubkey.n, 'e': pubkey.e} 492 pub = RsaPublicKey(pub_params, util.ExportRsaX509(pub_params), pubkey, size) 493 return RsaPrivateKey(params, util.ExportRsaPkcs8(params), pub, key, size)
494 495 @staticmethod
496 - def Read(key):
497 """ 498 Reads a RSA private key from a JSON string representation of it. 499 500 @param key: a JSON representation of a RSA private key 501 @type key: string 502 503 @return: a RSA private key 504 @rtype: L{RsaPrivateKey} 505 """ 506 rsa = simplejson.loads(key) 507 pub = RsaPublicKey.Read(simplejson.dumps(rsa['publicKey'])) 508 params = util.ParsePkcs8(rsa['pkcs8']) 509 key = RSA.construct((params['n'], params['e'], params['d'], 510 params['q'], params['p'], params['invq'])) 511 return RsaPrivateKey(params, rsa['pkcs8'], pub, key, rsa['size'])
512
513 - def Encrypt(self, data):
514 """@see: L{RsaPublicKey.Encrypt}""" 515 return self.public_key.Encrypt(data)
516
517 - def Decrypt(self, input_bytes):
518 """ 519 Decrypts the given ciphertext. 520 521 @param input_bytes: raw byte string formatted as Header|Ciphertext. 522 @type input_bytes: string 523 524 @return: plaintext message 525 @rtype: string 526 """ 527 ciph_bytes = input_bytes[keyczar.HEADER_SIZE:] 528 decrypted = self.key.decrypt(ciph_bytes) 529 return self.__Decode(decrypted)
530
531 - def Sign(self, msg):
532 """ 533 Return raw byte string of signature on the SHA-1 hash of the message. 534 535 @param msg: message to be signed 536 @type msg: string 537 538 @return: string representation of long int signature over message 539 @rtype: string 540 """ 541 emsa_encoded = util.MakeEmsaMessage(msg, self.size) 542 return util.BigIntToBytes(self.key.sign(emsa_encoded, None)[0])
543
544 - def Verify(self, msg, sig):
545 """@see: L{RsaPublicKey.Verify}""" 546 return self.public_key.Verify(msg, sig)
547
548 -class DsaPublicKey(PublicKey):
549 550 """Represents DSA public keys in an asymmetric DSA key pair.""" 551
552 - def __init__(self, params, x509, key, size=keyinfo.DSA_PUB.default_size):
553 PublicKey.__init__(self, keyinfo.DSA_PUB, params, x509) 554 self.key = key 555 self.size = size
556 557 @staticmethod
558 - def Read(key):
559 """ 560 Reads a DSA public key from a JSON string representation of it. 561 562 @param key: a JSON representation of a DSA public key 563 @type key: string 564 565 @return: a DSA public key 566 @rtype: L{DsaPublicKey} 567 """ 568 dsa = simplejson.loads(key) 569 params = util.ParseX509(dsa['x509']) 570 pubkey = DSA.construct((params['y'], params['g'], params['p'], params['q'])) 571 return DsaPublicKey(params, dsa['x509'], pubkey, dsa['size'])
572
573 - def Verify(self, msg, sig):
574 """ 575 Return True if the signature corresponds to the message. 576 577 @param msg: message that has been signed 578 @type msg: string 579 580 @param sig: raw byte string of the signature formatted as an ASN.1 sequence 581 of r and s 582 @type sig: string 583 584 @return: True if signature is valid for message. False otherwise. 585 @rtype: boolean 586 """ 587 try: 588 (r, s) = util.ParseDsaSig(sig) 589 return self.key.verify(util.Hash(msg), (r, s)) 590 except errors.KeyczarError: 591 # if signature is not in correct format 592 return False
593
594 -class RsaPublicKey(PublicKey):
595 """Represents RSA public keys in an asymmetric RSA key pair.""" 596
597 - def __init__(self, params, x509, key, size=keyinfo.RSA_PUB.default_size):
598 PublicKey.__init__(self, keyinfo.RSA_PUB, params, x509) 599 self.key = key 600 self.size = size
601
602 - def __Encode(self, msg, p=""):
603 if len(p) >= 2**61: # the input limit for SHA-1 604 raise errors.KeyczarError("OAEP parameter string too long.") 605 k = int(math.floor(math.log(self._params['n'], 256)) + 1) # num bytes in n 606 if len(msg) > k - 2 * util.HLEN - 2: 607 raise errors.KeyczarError("Message too long to OAEP encode.") 608 ph = util.Hash(p) 609 ps = (k - len(msg) - 2 * util.HLEN - 2) * chr(0) # zero byte string 610 db = "".join([ph, ps, chr(1), msg]) 611 seed = util.RandBytes(util.HLEN) 612 db_mask = util.MGF(seed, k - util.HLEN - 1) 613 masked_db = util.Xor(db, db_mask) 614 seed_mask = util.MGF(masked_db, util.HLEN) 615 masked_seed = util.Xor(seed, seed_mask) 616 return "".join([chr(0), masked_seed, masked_db])
617 618 @staticmethod
619 - def Read(key):
620 """ 621 Reads a RSA public key from a JSON string representation of it. 622 623 @param key: a JSON representation of a RSA public key 624 @type key: string 625 626 @return: a RSA public key 627 @rtype: L{RsaPublicKey} 628 """ 629 rsa = simplejson.loads(key) 630 params = util.ParseX509(rsa['x509']) 631 pubkey = RSA.construct((params['n'], params['e'])) 632 return RsaPublicKey(params, rsa['x509'], pubkey, rsa['size'])
633
634 - def Encrypt(self, data):
635 """ 636 Return a raw byte string of the ciphertext in the form Header|Ciph. 637 638 @param data: message to be encrypted 639 @type data: string 640 641 @return: ciphertext formatted as Header|Ciph 642 @rtype: string 643 """ 644 data = self.__Encode(data) 645 ciph_bytes = self.key.encrypt(data, None)[0] # PyCrypto returns 1-tuple 646 return self.Header() + ciph_bytes
647
648 - def Verify(self, msg, sig):
649 """ 650 Return True if the signature corresponds to the message. 651 652 @param msg: message that has been signed 653 @type msg: string 654 655 @param sig: string representation of long int signature 656 @type sig: string 657 658 @return: True if signature is valid for the message hash. False otherwise. 659 @rtype: boolean 660 """ 661 try: 662 return self.key.verify(util.MakeEmsaMessage(msg, self.size), (util.BytesToInt(sig),)) 663 except ValueError: 664 # if sig is not a long, it's invalid 665 return False
666