diff options
author | Legrandin <gooksankoo@hoiptorrow.mailexpire.com> | 2011-10-11 23:47:59 +0200 |
---|---|---|
committer | Legrandin <gooksankoo@hoiptorrow.mailexpire.com> | 2011-10-11 23:53:43 +0200 |
commit | cee93d88d0c7283ff8c8c376ed47253900e5a49e (patch) | |
tree | ce3206710d7dbd621d13c4173f5f5057275bc923 | |
parent | 075f0726aa9bee63173ba91c465e6c1fa57a6e1d (diff) | |
download | pycrypto-cee93d88d0c7283ff8c8c376ed47253900e5a49e.tar.gz |
Restructure both PKCS#1 ciphers as objects, to make them more uniform with other ciphers in the module.
-rw-r--r-- | lib/Crypto/Cipher/PKCS1_OAEP.py | 348 | ||||
-rw-r--r-- | lib/Crypto/Cipher/PKCS1_v1_5.py | 266 | ||||
-rw-r--r-- | lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py | 23 | ||||
-rw-r--r-- | lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py | 28 |
4 files changed, 365 insertions, 300 deletions
diff --git a/lib/Crypto/Cipher/PKCS1_OAEP.py b/lib/Crypto/Cipher/PKCS1_OAEP.py index 889333a..4c43707 100644 --- a/lib/Crypto/Cipher/PKCS1_OAEP.py +++ b/lib/Crypto/Cipher/PKCS1_OAEP.py @@ -33,13 +33,15 @@ As an example, a sender may encrypt a message in this way: >>> >>> message = 'To be encrypted' >>> key = RSA.importKey(open('pubkey.der').read()) - >>> ciphertext = PKCS1_OAEP.encrypt(message, key) + >>> cipher = PKCS1_OAEP.new(key) + >>> ciphertext = cipher.encrypt(message) At the receiver side, decryption can be done using the private part of the RSA key: >>> key = RSA.importKey(open('privkey.der').read()) - >>> message = PKCS1_OAEP.decrypt(ciphertext, key): + >>> cipher = PKCS1_OAP.new(key) + >>> message = cipher.decrypt(ciphertext) :undocumented: __revision__, __package__ @@ -50,7 +52,7 @@ the RSA key: from __future__ import nested_scopes __revision__ = "$Id$" -__all__ = [ 'encrypt', 'decrypt' ] +__all__ = [ 'new' ] import Crypto.Signature.PKCS1_PSS import Crypto.Hash.SHA @@ -59,170 +61,194 @@ import Crypto.Util.number from Crypto.Util.number import ceil_div from Crypto.Util.strxor import strxor -def encrypt(message, key, hashAlgo=None, mgfunc=None, label=''): - """Produce the PKCS#1 OAEP encryption of a message. - - This function is named ``RSAES-OAEP-ENCRYPT``, and is specified in - section 7.1.1 of RFC3447. +class PKCS1OAEP_Cipher: + """This cipher can perform PKCS#1 v1.5 OAEP encryption or decryption.""" + + def __init__(self, key, hashAlgo, mgfunc, label): + """Initialize this PKCS#1 OAEP cipher object. + + :Parameters: + key : an RSA key object + If a private half is given, both encryption and decryption are possible. + If a public half is given, only encryption is possible. + hashAlgo : hash object + The hash function to use. This can be a module under `Crypto.Hash` + or an existing hash object created from any of such modules. If not specified, + `Crypto.Hash.SHA` (that is, SHA-1) is used. + mgfunc : callable + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + If not specified, the standard MGF1 is used (a safe choice). + label : string + A label to apply to this particular encryption. If not specified, + an empty string is used. Specifying a label does not improve + security. + + :attention: Modify the mask generation function only if you know what you are doing. + Sender and receiver must use the same one. + """ + self._key = key + + if hashAlgo: + self._hashObj = hashAlgo + else: + self._hashObj = Crypto.Hash.SHA + + if mgfunc: + self._mgf = mgfunc + else: + self._mgf = lambda x,y: Crypto.Signature.PKCS1_PSS.MGF1(x,y,self._hashObj) + + self._label = label + + def can_encrypt(self): + """Return True/1 if this cipher object can be used for encryption.""" + return self._key.can_encrypt() + + def can_decrypt(self): + """Return True/1 if this cipher object can be used for decryption.""" + return self._key.can_decrypt() + + def encrypt(self, message): + """Produce the PKCS#1 OAEP encryption of a message. + + This function is named ``RSAES-OAEP-ENCRYPT``, and is specified in + section 7.1.1 of RFC3447. + + :Parameters: + message : string + The message to encrypt, also known as plaintext. It can be of + variable length, but not longer than the RSA modulus (in bytes) + minus 2, minus twice the hash output size. + + :Return: A string, the ciphertext in which the message is encrypted. + It is as long as the RSA modulus (in bytes). + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + message. + """ + # TODO: Verify the key is RSA + + randFunc = self._key._randfunc + + # See 7.1.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + hLen = self._hashObj.digest_size + mLen = len(message) + + # Step 1b + ps_len = k-mLen-2*hLen-2 + if ps_len<0: + raise ValueError("Plaintext is too long.") + # Step 2a + lHash = self._hashObj.new(self._label).digest() + # Step 2b + ps = '\x00'*ps_len + # Step 2c + db = lHash + ps + '\x01' + message + # Step 2d + ros = randFunc(hLen) + # Step 2e + dbMask = self._mgf(ros, k-hLen-1) + # Step 2f + maskedDB = strxor(db, dbMask) + # Step 2g + seedMask = self._mgf(maskedDB, hLen) + # Step 2h + maskedSeed = strxor(ros, seedMask) + # Step 2i + em = '\x00' + maskedSeed + maskedDB + # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) + m = self._key.encrypt(em, 0)[0] + # Complete step 3c (I2OSP) + c = '\x00'*(k-len(m)) + m + return c + + def decrypt(self, ct): + """Decrypt a PKCS#1 OAEP ciphertext. + + This function is named ``RSAES-OAEP-DECRYPT``, and is specified in + section 7.1.2 of RFC3447. + + :Parameters: + ct : string + The ciphertext that contains the message to recover. + + :Return: A string, the original message. + :Raise ValueError: + If the ciphertext length is incorrect, or if the decryption does not + succeed. + :Raise TypeError: + If the RSA key has no private half. + """ + # TODO: Verify the key is RSA + + # See 7.1.2 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + hLen = self._hashObj.digest_size + + # Step 1b and 1c + if len(ct) != k or k<hLen+2: + raise ValueError("Ciphertext with incorrect length.") + # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP) + m = self._key.decrypt(ct) + # Complete step 2c (I2OSP) + em = '\x00'*(k-len(m)) + m + # Step 3a + lHash = self._hashObj.new(self._label).digest() + # Step 3b + y = em[0] + # y must be 0, but we MUST NOT check it here in order not to + # allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143) + maskedSeed = em[1:hLen+1] + maskedDB = em[hLen+1:] + # Step 3c + seedMask = self._mgf(maskedDB, hLen) + # Step 3d + seed = strxor(maskedSeed, seedMask) + # Step 3e + dbMask = self._mgf(seed, k-hLen-1) + # Step 3f + db = strxor(maskedDB, dbMask) + # Step 3g + valid = 1 + one = db[hLen:].find('\x01') + lHash1 = db[:hLen] + if lHash1!=lHash: + valid = 0 + if one<0: + valid = 0 + if y!='\x00': + valid = 0 + if not valid: + raise ValueError("Incorrect decryption.") + # Step 4 + return db[hLen+one+1:] + +def new(key, hashAlgo=None, mgfunc=None, label=''): + """Return a cipher object `PKCS1OAEP_Cipher` that can be used to perform PKCS#1 OAEP encryption or decryption. :Parameters: - message : string - The message to encrypt, also known as plaintext. It can be of - variable length, but not longer than the RSA modulus (in bytes) - minus 2, minus twice the hash output size. key : RSA key object - The key to use to encrypt the message. This is a `Crypto.PublicKey.RSA` - object. + The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object. + Decryption is only possible if *key* is a private RSA key. hashAlgo : hash object - The hash function to use. This can be a module under `Crypto.Hash` - or an existing hash object created from any of such modules. If not specified, - `Crypto.Hash.SHA` (that is, SHA-1) is used. + The hash function to use. This can be a module under `Crypto.Hash` + or an existing hash object created from any of such modules. If not specified, + `Crypto.Hash.SHA` (that is, SHA-1) is used. mgfunc : callable - A mask generation function that accepts two parameters: a string to - use as seed, and the lenth of the mask to generate, in bytes. - If not specified, the standard MGF1 is used (a safe choice). + A mask generation function that accepts two parameters: a string to + use as seed, and the lenth of the mask to generate, in bytes. + If not specified, the standard MGF1 is used (a safe choice). label : string - A label to apply to this particular encryption. If not specified, - an empty string is used. Specifying a label does not improve - security. - - :Return: A string, the ciphertext in which the message is encrypted. - It is as long as the RSA modulus (in bytes). - :Raise ValueError: - If the RSA key length is not sufficiently long to deal with the given - message. - + A label to apply to this particular encryption. If not specified, + an empty string is used. Specifying a label does not improve + security. + :attention: Modify the mask generation function only if you know what you are doing. - The receiver must use the same one too. - """ - # TODO: Verify the key is RSA - - randFunc = key._randfunc - - # See 7.1.1 in RFC3447 - modBits = Crypto.Util.number.size(key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes - if hashAlgo: - hashObj = hashAlgo - else: - hashObj = Crypto.Hash.SHA - hLen = hashObj.digest_size - mLen = len(message) - if mgfunc: - mgf = mgfunc - else: - mgf = lambda x,y: Crypto.Signature.PKCS1_PSS.MGF1(x,y,hashObj) - - # Step 1b - ps_len = k-mLen-2*hLen-2 - if ps_len<0: - raise ValueError("Plaintext is too long.") - # Step 2a - lHash = hashObj.new(label).digest() - # Step 2b - ps = '\x00'*ps_len - # Step 2c - db = lHash + ps + '\x01' + message - # Step 2d - ros = randFunc(hLen) - # Step 2e - dbMask = mgf(ros, k-hLen-1) - # Step 2f - maskedDB = strxor(db, dbMask) - # Step 2g - seedMask = mgf(maskedDB, hLen) - # Step 2h - maskedSeed = strxor(ros, seedMask) - # Step 2i - em = '\x00' + maskedSeed + maskedDB - # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) - m = key.encrypt(em, 0)[0] - # Complete step 3c (I2OSP) - c = '\x00'*(k-len(m)) + m - return c - -def decrypt(ct, key, hashAlgo=None, mgfunc=None, label=''): - """Decrypt a PKCS#1 OAEP ciphertext. - - This function is named ``RSAES-OAEP-DECRYPT``, and is specified in - section 7.1.2 of RFC3447. - - :Parameters: - ct : string - The ciphertext that contains the message to recover. - key : RSA key object - The key to use to verify the message. This is a `Crypto.PublicKey.RSA` - object. It must have its private half. - hashAlgo : hash object - The hash function to use. This can be a module under `Crypto.Hash` - or an existing hash object created from any of such modules. - If not specified, `Crypto.Hash.SHA` (that is, SHA-1) is used. - mgfunc : callable - A mask generation function that accepts two parameters: a string to - use as seed, and the lenth of the mask to generate, in bytes. - If not specified, the standard MGF1 is used. The sender must have - used the same function. - label : string - A label to apply to this particular encryption. If not specified, - an empty string is used. The sender must have used the same label. - - :Return: A string, the original message. - :Raise ValueError: - If the ciphertext length is incorrect, or if the decryption does not - succeed. - :Raise TypeError: - If the RSA key has no private half. + Sender and receiver must use the same one. """ - # TODO: Verify the key is RSA - - # See 7.1.2 in RFC3447 - modBits = Crypto.Util.number.size(key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes - if hashAlgo: - hashObj = hashAlgo - else: - hashObj = Crypto.Hash.SHA - hLen = hashObj.digest_size - if mgfunc: - mgf = mgfunc - else: - mgf = lambda x,y: Crypto.Signature.PKCS1_PSS.MGF1(x,y,hashObj) - - # Step 1b and 1c - if len(ct) != k or k<hLen+2: - raise ValueError("Ciphertext with incorrect length.") - # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP) - m = key.decrypt(ct) - # Complete step 2c (I2OSP) - em = '\x00'*(k-len(m)) + m - # Step 3a - lHash = hashObj.new(label).digest() - # Step 3b - y = em[0] - # y must be 0, but we MUST NOT check it here in order not to - # allow attacks like Manger's (http://dl.acm.org/citation.cfm?id=704143) - maskedSeed = em[1:hLen+1] - maskedDB = em[hLen+1:] - # Step 3c - seedMask = mgf(maskedDB, hLen) - # Step 3d - seed = strxor(maskedSeed, seedMask) - # Step 3e - dbMask = mgf(seed, k-hLen-1) - # Step 3f - db = strxor(maskedDB, dbMask) - # Step 3g - valid = 1 - one = db[hLen:].find('\x01') - lHash1 = db[:hLen] - if lHash1!=lHash: - valid = 0 - if one<0: - valid = 0 - if y!='\x00': - valid = 0 - if not valid: - raise ValueError("Incorrect decryption.") - # Step 4 - return db[hLen+one+1:] + return PKCS1OAEP_Cipher(key, hashAlgo, mgfunc, label) diff --git a/lib/Crypto/Cipher/PKCS1_v1_5.py b/lib/Crypto/Cipher/PKCS1_v1_5.py index 8d02afe..748a327 100644 --- a/lib/Crypto/Cipher/PKCS1_v1_5.py +++ b/lib/Crypto/Cipher/PKCS1_v1_5.py @@ -38,8 +38,8 @@ As an example, a sender may encrypt a message in this way: >>> h = SHA.new(message) >>> >>> key = RSA.importKey(open('pubkey.der').read()) - >>> - >>> ciphertext = PKCS1_v1_5.encrypt(message+h.digest(), key) + >>> cipher = PKCS1_v1_5.new(key) + >>> ciphertext = cipher.encrypt(message+h.digest()) At the receiver side, decryption can be done using the private part of the RSA key: @@ -52,7 +52,8 @@ the RSA key: >>> dsize = SHA.digest_size >>> sentinel = Random.new().read(15+dsize) # Let's assume that average data length is 15 >>> - >>> message = PKCS1_v1_5.decrypt(ciphertext, key, sentinel) + >>> cipher = PKCS1_v1_5.new(key) + >>> message = cipher.decrypt(ciphertext, sentinel) >>> >>> digest = SHA.new(message[:-dsize]).digest() >>> if digest==message[-dsize:]: # Note how we DO NOT look for the sentinel @@ -67,129 +68,158 @@ the RSA key: """ __revision__ = "$Id$" -__all__ = [ 'encrypt', 'decrypt' ] +__all__ = [ 'new' ] from Crypto.Util.number import ceil_div import Crypto.Util.number -def encrypt(message, key): - """Produce the PKCS#1 v1.5 encryption of a message. - - This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and is specified in - section 7.2.1 of RFC3447. +class PKCS115_Cipher: + """This cipher can perform PKCS#1 v1.5 RSA encryption or decryption.""" - :Parameters: - message : string - The message to encrypt, also known as plaintext. It can be of - variable length, but not longer than the RSA modulus (in bytes) minus 11. - key : RSA key object - The key to use to encrypt the message. This is a `Crypto.PublicKey.RSA` - object. - - :Return: A string, the ciphertext in which the message is encrypted. - It is as long as the RSA modulus (in bytes). - :Raise ValueError: - If the RSA key length is not sufficiently long to deal with the given - message. - """ - # TODO: Verify the key is RSA - - randFunc = key._randfunc - - # See 7.2.1 in RFC3447 - modBits = Crypto.Util.number.size(key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes - mLen = len(message) - - # Step 1 - if mLen > k-11: - raise ValueError("Plaintext is too long.") - # Step 2a - class nonZeroRandByte: - def __init__(self, rf): self.rf=rf - def __call__(self, c): - while c=='\x00': c=self.rf(1) - return c - ps = "".join(map(nonZeroRandByte(randFunc), randFunc(k-mLen-3))) - # Step 2b - em = '\x00\x02' + ps + '\x00' + message - # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) - m = key.encrypt(em, 0)[0] - # Complete step 3c (I2OSP) - c = '\x00'*(k-len(m)) + m - return c - -def decrypt(ct, key, sentinel): - """Decrypt a PKCS#1 v1.5 ciphertext. - - This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in - section 7.2.2 of RFC3447. + def __init__(self, key): + """Initialize this PKCS#1 v1.5 cipher object. + + :Parameters: + key : an RSA key object + If a private half is given, both encryption and decryption are possible. + If a public half is given, only encryption is possible. + """ + self._key = key + + def can_encrypt(self): + """Return True/1 if this cipher object can be used for encryption.""" + return self._key.can_encrypt() + + def can_decrypt(self): + """Return True/1 if this cipher object can be used for decryption.""" + return self._key.can_decrypt() + + def encrypt(self, message): + """Produce the PKCS#1 v1.5 encryption of a message. + + This function is named ``RSAES-PKCS1-V1_5-ENCRYPT``, and is specified in + section 7.2.1 of RFC3447. + For a complete example see `Crypto.Cipher.PKCS1_v1_5`. + + :Parameters: + message : string + The message to encrypt, also known as plaintext. It can be of + variable length, but not longer than the RSA modulus (in bytes) minus 11. + + :Return: A string, the ciphertext in which the message is encrypted. + It is as long as the RSA modulus (in bytes). + :Raise ValueError: + If the RSA key length is not sufficiently long to deal with the given + message. + + """ + # TODO: Verify the key is RSA + + randFunc = self._key._randfunc + + # See 7.2.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + mLen = len(message) + + # Step 1 + if mLen > k-11: + raise ValueError("Plaintext is too long.") + # Step 2a + class nonZeroRandByte: + def __init__(self, rf): self.rf=rf + def __call__(self, c): + while c=='\x00': c=self.rf(1) + return c + ps = "".join(map(nonZeroRandByte(randFunc), randFunc(k-mLen-3))) + # Step 2b + em = '\x00\x02' + ps + '\x00' + message + # Step 3a (OS2IP), step 3b (RSAEP), part of step 3c (I2OSP) + m = self._key.encrypt(em, 0)[0] + # Complete step 3c (I2OSP) + c = '\x00'*(k-len(m)) + m + return c + + def decrypt(self, ct, sentinel): + """Decrypt a PKCS#1 v1.5 ciphertext. + + This function is named ``RSAES-PKCS1-V1_5-DECRYPT``, and is specified in + section 7.2.2 of RFC3447. + For a complete example see `Crypto.Cipher.PKCS1_v1_5`. + + :Parameters: + ct : string + The ciphertext that contains the message to recover. + sentinel : string + The string to return to indicate that an error was detected during decryption. + + :Return: A string. It is either the original message or the ``sentinel`` (in case of an error). + :Raise ValueError: + If the ciphertext length is incorrect + :Raise TypeError: + If the RSA key has no private half. + + :attention: + You should **never** let the party who submitted the ciphertext know that + this function returned the ``sentinel`` value. + Armed with such knowledge (for a fair amount of carefully crafted but invalid ciphertexts), + an attacker is able to recontruct the plaintext of any other encryption that were carried out + with the same RSA public key (see `Bleichenbacher's`__ attack). + + In general, it should not be possible for the other party to distinguish + whether processing at the server side failed because the value returned + was a ``sentinel`` as opposed to a random, invalid message. + + In fact, the second option is not that unlikely: encryption done according to PKCS#1 v1.5 + embeds no good integrity check. There is roughly one chance + in 2^16 for a random ciphertext to be returned as a valid message + (although random looking). + + It is therefore advisabled to: + + 1. Select as ``sentinel`` a value that resembles a plausable random, invalid message. + 2. Not report back an error as soon as you detect a ``sentinel`` value. + Put differently, you should not explicitly check if the returned value is the ``sentinel`` or not. + 3. Cover all possible errors with a single, generic error indicator. + 4. Embed into the definition of ``message`` (at the protocol level) a digest (e.g. ``SHA-1``). + It is recommended for it to be the rightmost part ``message``. + 5. Where possible, monitor the number of errors due to ciphertexts originating from the same party, + and slow down the rate of the requests from such party (or even blacklist it altogether). + + **If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.** + + .. __: http://www.bell-labs.com/user/bleichen/papers/pkcs.ps + + """ + + # TODO: Verify the key is RSA + + # See 7.2.1 in RFC3447 + modBits = Crypto.Util.number.size(self._key.n) + k = ceil_div(modBits,8) # Convert from bits to bytes + + # Step 1 + if len(ct) != k: + raise ValueError("Ciphertext with incorrect length.") + # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP) + m = self._key.decrypt(ct) + # Complete step 2c (I2OSP) + em = '\x00'*(k-len(m)) + m + # Step 3 + sep = em.find('\x00',2) + if not em.startswith('\x00\x02') or sep<10: + return sentinel + # Step 4 + return em[sep+1:] + +def new(key): + """Return a cipher object `PKCS115_Cipher` that can be used to perform PKCS#1 v1.5 encryption or decryption. :Parameters: - ct : string - The ciphertext that contains the message to recover. key : RSA key object - The key to use to verify the message. This is a `Crypto.PublicKey.RSA` - object. It must have its private half. - sentinel : string - The string to return to indicate that an error was detected during decryption. - - :Return: A string. It is either the original message or the ``sentinel`` (in case of an error). - :Raise ValueError: - If the ciphertext length is incorrect - :Raise TypeError: - If the RSA key has no private half. - - :attention: - You should **never** let the party who submitted the ciphertext know that - this function returned the ``sentinel`` value. - Armed with such knowledge (for a fair amount of carefully crafted but invalid ciphertexts), - an attacker is able to recontruct the plaintext of any other encryption that were carried out - with the same RSA public key (see `Bleichenbacher's`__ attack). - - In general, it should not be possible for the other party to distinguish - whether processing at the server side failed because the value returned - was a ``sentinel`` as opposed to a random, invalid message. - - In fact, the second option is not that unlikely: encryption done according to PKCS#1 v1.5 - embeds no good integrity check. There is roughly one chance - in 2^16 for a random ciphertext to be returned as a valid message - (although random looking). - - It is therefore advisabled to: - - 1. Select as ``sentinel`` a value that resembles a plausable random, invalid message. - 2. Not report back an error as soon as you detect a ``sentinel`` value. - Put differently, you should not explicitly check if the returned value is the ``sentinel`` or not. - 3. Cover all possible errors with a single, generic error indicator. - 4. Embed into the definition of ``message`` (at the protocol level) a digest (e.g. ``SHA-1``). - It is recommended for it to be the rightmost part ``message``. - 5. Where possible, monitor the number of errors due to ciphertexts originating from the same party, - and slow down the rate of the requests from such party (or even blacklist it altogether). - - **If you are designing a new protocol, consider using the more robust PKCS#1 OAEP.** - - .. __: http://www.bell-labs.com/user/bleichen/papers/pkcs.ps + The key to use to encrypt or decrypt the message. This is a `Crypto.PublicKey.RSA` object. + Decryption is only possible if *key* is a private RSA key. """ - - # TODO: Verify the key is RSA - - # See 7.2.1 in RFC3447 - modBits = Crypto.Util.number.size(key.n) - k = ceil_div(modBits,8) # Convert from bits to bytes - - # Step 1 - if len(ct) != k: - raise ValueError("Ciphertext with incorrect length.") - # Step 2a (O2SIP), 2b (RSADP), and part of 2c (I2OSP) - m = key.decrypt(ct) - # Complete step 2c (I2OSP) - em = '\x00'*(k-len(m)) + m - # Step 3 - sep = em.find('\x00',2) - if not em.startswith('\x00\x02') or sep<10: - return sentinel - # Step 4 - return em[sep+1:] + return PKCS115_Cipher(key) diff --git a/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py b/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py index 1609770..566cbf5 100644 --- a/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py +++ b/lib/Crypto/SelfTest/Cipher/test_pkcs1_15.py @@ -115,44 +115,47 @@ HKukWBcq9f/UOmS0oEhai/6g+Uf7VHJdWaeO5LzuvwU= return r # The real test key._randfunc = randGen(t2b(test[3])) - ct = PKCS.encrypt(test[1], key) + cipher = PKCS.new(key) + ct = cipher.encrypt(test[1]) self.assertEqual(ct, t2b(test[2])) def testEncrypt2(self): # Verify that encryption fail if plaintext is too long pt = '\x00'*(128-11+1) - self.assertRaises(ValueError, PKCS.encrypt, pt, self.key1024) + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.encrypt, pt) def testVerify1(self): for test in self._testData: # Build the key key = RSA.importKey(test[0]) # The real test - pt = PKCS.decrypt(t2b(test[2]), key, "---") + cipher = PKCS.new(key) + pt = cipher.decrypt(t2b(test[2]), "---") self.assertEqual(pt, test[1]) def testVerify2(self): # Verify that decryption fails if ciphertext is not as long as # RSA modulus - self.assertRaises(ValueError, PKCS.decrypt, '\x00'*127, - self.key1024, "---") - self.assertRaises(ValueError, PKCS.decrypt, '\x00'*129, - self.key1024, "---") + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.decrypt, '\x00'*127, "---") + self.assertRaises(ValueError, cipher.decrypt, '\x00'*129, "---") # Verify that decryption fails if there are less then 8 non-zero padding # bytes pt = '\x00\x02' + '\xFF'*7 + '\x00' + '\x45'*118 ct = self.key1024.encrypt(pt, 0)[0] ct = '\x00'*(128-len(ct)) + ct - self.assertEqual("---", PKCS.decrypt(ct, self.key1024, "---")) + self.assertEqual("---", cipher.decrypt(ct, "---")) def testEncryptVerify1(self): # Encrypt/Verify messages of length [0..RSAlen-11] # and therefore padding [8..117] for pt_len in xrange(0,128-11+1): pt = self.rng(pt_len) - ct = PKCS.encrypt(pt, self.key1024) - pt2 = PKCS.decrypt(ct, self.key1024, "---") + cipher = PKCS.new(self.key1024) + ct = cipher.encrypt(pt) + pt2 = cipher.decrypt(ct, "---") self.assertEqual(pt,pt2) diff --git a/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py b/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py index ad1fd91..ff43d58 100644 --- a/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py +++ b/lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py @@ -281,13 +281,15 @@ class PKCS1_OAEP_Tests(unittest.TestCase): return r # The real test key._randfunc = randGen(t2b(test[3])) - ct = PKCS.encrypt(t2b(test[1]), key, test[4]) + cipher = PKCS.new(key, test[4]) + ct = cipher.encrypt(t2b(test[1])) self.assertEqual(ct, t2b(test[2])) def testEncrypt2(self): # Verify that encryption fails if plaintext is too long pt = '\x00'*(128-2*20-2+1) - self.assertRaises(ValueError, PKCS.encrypt, pt, self.key1024) + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.encrypt, pt) def testDecrypt1(self): # Verify decryption using all test vectors @@ -296,14 +298,15 @@ class PKCS1_OAEP_Tests(unittest.TestCase): comps = [ long(rws(test[0][x]),16) for x in ('n','e','d') ] key = RSA.construct(comps) # The real test - pt = PKCS.decrypt(t2b(test[2]), key, test[4]) + cipher = PKCS.new(key, test[4]) + pt = cipher.decrypt(t2b(test[2])) self.assertEqual(pt, t2b(test[1])) def testDecrypt2(self): # Simplest possible negative tests for ct_size in (127,128,129): - self.assertRaises(ValueError, PKCS.decrypt, '\x00'*ct_size, - self.key1024) + cipher = PKCS.new(self.key1024) + self.assertRaises(ValueError, cipher.decrypt, '\x00'*ct_size) def testEncryptDecrypt1(self): # Encrypt/Decrypt messages of length [0..128-2*20-2] @@ -327,16 +330,18 @@ class PKCS1_OAEP_Tests(unittest.TestCase): asked = 0 pt = self.rng(40) self.key1024._randfunc = localRng - ct = PKCS.encrypt(pt, self.key1024, hashmod) - self.assertEqual(PKCS.decrypt(ct, self.key1024, hashmod), pt) + cipher = PKCS.new(self.key1024, hashmod) + ct = cipher.encrypt(pt) + self.assertEqual(cipher.decrypt(ct), pt) self.assertTrue(asked > hashmod.digest_size) def testEncryptDecrypt2(self): # Verify that OAEP supports labels pt = self.rng(35) xlabel = self.rng(22) - ct = PKCS.encrypt(pt, self.key1024, label=xlabel) - self.assertEqual(PKCS.decrypt(ct, self.key1024, label=xlabel), pt) + cipher = PKCS.new(self.key1024, label=xlabel) + ct = cipher.encrypt(pt) + self.assertEqual(cipher.decrypt(ct), pt) def testEncryptDecrypt3(self): # Verify that encrypt() uses the custom MGF @@ -348,9 +353,10 @@ class PKCS1_OAEP_Tests(unittest.TestCase): return '\x00'*maskLen mgfcalls = 0 pt = self.rng(32) - ct = PKCS.encrypt(pt, self.key1024, mgfunc=newMGF) + cipher = PKCS.new(self.key1024, mgfunc=newMGF) + ct = cipher.encrypt(pt) self.assertEqual(mgfcalls, 2) - self.assertEqual(PKCS.decrypt(ct, self.key1024, mgfunc=newMGF), pt) + self.assertEqual(cipher.decrypt(ct), pt) def get_tests(config={}): tests = [] |