Permalink
Browse files

Add PKCS#1 OAEP encryption, with test cases

  • Loading branch information...
1 parent 260430c commit a2ef0a2bde6f226c458dbf0c218e8fa317f7da59 Legrandin committed Feb 17, 2011
@@ -0,0 +1,228 @@
+# -*- coding: utf-8 -*-
+#
+# Cipher/PKCS1_OAEP.py : PKCS#1 OAEP
+#
+# ===================================================================
+# The contents of this file are dedicated to the public domain. To
+# the extent that dedication to the public domain is not available,
+# everyone is granted a worldwide, perpetual, royalty-free,
+# non-exclusive license to exercise all rights associated with the
+# contents of this file for any purpose whatsoever.
+# No rights are reserved.
+#
+# 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.
+# ===================================================================
+
+"""RSA encryption protocol according to PKCS#1 OAEP
+
+See RFC3447 or the original RSA Labs specification at
+http://www.rsa.com/rsalabs/node.asp?id=2125.
+
+This scheme is more properly called ``RSAES-OAEP``.
+
+As an example, a sender may encrypt a message in this way:
+
+ >>> from Crypto.Cipher import PKCS1_OAEP
+ >>> from Crypto.PublicKey import RSA
+ >>> from Crypto import Random
+ >>>
+ >>> message = 'To be encrypted'
+ >>> key = RSA.importKey('pubkey.der')
+ >>> rng = Random.new().read
+ >>> ciphertext = PKCS1_OAEP.encrypt(message, key, rng)
+
+At the receiver side, decryption can be done using the private part of
+the RSA key:
+
+ >>> key = RSA.importKey('privkey.der')
+ >>> message = PKCS1_OAEP.decrypt(ciphertext, key):
+"""
+
+from __future__ import nested_scopes
+
+__revision__ = "$Id$"
+__all__ = [ 'encrypt', 'decrypt' ]
+
+import Crypto.Signature.PKCS1_PSS
+import Crypto.Hash.SHA
+
+import Crypto.Util.number
+from Crypto.Util.number import ceil_div
+from Crypto.Util.strxor import strxor
+
+import re
+
+def encrypt(message, key, randFunc, 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.
+
+ :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.
+ randFunc : callable
+ An RNG function that accepts as only parameter an integer, and returns
+ a string of random bytes.
+ 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.
+
+ :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.
+
+ :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
+
+ # 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.
+ """
+ # 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]
+ 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
+ lHash1 = db[:hLen]
+ if lHash1!=lHash:
+ valid = 0
+ try:
+ one = re.match('\x00+',db[hLen:]).end()
+ if db[hLen+one]!='\x01':
+ valid = 0
+ except (IndexError, AttributeError):
+ valid = 0
+ if not valid:
+ raise ValueError("Incorrect decryption.")
+ # Step 4
+ return db[hLen+one+1:]
+
@@ -18,33 +18,62 @@
# SOFTWARE.
# ===================================================================
-"""Secret-key encryption algorithms.
+"""Symmetric- and asymmetric-key encryption algorithms.
-Secret-key encryption algorithms transform plaintext in some way that
-is dependent on a key, producing ciphertext. This transformation can
-easily be reversed, if (and, hopefully, only if) one knows the key.
+Encryption algorithms transform plaintext in some way that
+is dependent on a key or key pair, producing ciphertext.
-The encryption modules here all support the interface described in PEP
+Symmetric algorithms
+--------------------
+
+Encryption can easily be reversed, if (and, hopefully, only if)
+one knows the same key.
+In other words, sender and receiver share the same key.
+
+The symmetric encryption modules here all support the interface described in PEP
272, "API for Block Encryption Algorithms".
If you don't know which algorithm to choose, use AES because it's
standard and has undergone a fair bit of examination.
+====================== ====================
+Module name Description
+====================== ====================
Crypto.Cipher.AES Advanced Encryption Standard
Crypto.Cipher.ARC2 Alleged RC2
Crypto.Cipher.ARC4 Alleged RC4
-Crypto.Cipher.Blowfish
-Crypto.Cipher.CAST
-Crypto.Cipher.DES The Data Encryption Standard. Very commonly used
- in the past, but today its 56-bit keys are too small.
+Crypto.Cipher.Blowfish Blowfish
+Crypto.Cipher.CAST CAST
+Crypto.Cipher.DES The Data Encryption Standard.
+ Very commonly used in the past,
+ but today its 56-bit keys are too small.
Crypto.Cipher.DES3 Triple DES.
Crypto.Cipher.XOR The simple XOR cipher.
+====================== ====================
+
+
+Asymmetric algorithms
+---------------------
+
+For asymmetric algorithms, the key to be used for decryption is totally
+different and cannot be derived in a feasible way from the key used
+for encryption. Put differently, sender and receiver each own one half
+of a key pair. The encryption key is often called ``public``` whereas
+the decryption key is called ``private``.
+
+======================== =======================
+Module name Description
+======================== =======================
+Crypto.Cipher.PKCS1_v1.5 PKCS#1 v1.5 encryption, based on RSA key pairs
+Crypto.Cipher.PKCS1_OAEP PKCS#1 OAEP encryption, based on RSA key pairs
+======================== =======================
+
"""
__all__ = ['AES', 'ARC2', 'ARC4',
'Blowfish', 'CAST', 'DES', 'DES3',
'XOR',
- 'PKCS1_v1_5'
+ 'PKCS1_v1_5', 'PKCS1_OAEP'
]
__revision__ = "$Id$"
@@ -36,7 +36,8 @@ def get_tests(config={}):
import test_DES3; tests += test_DES3.get_tests(config=config)
import test_DES; tests += test_DES.get_tests(config=config)
import test_XOR; tests += test_XOR.get_tests(config=config)
- import test_pkcs_15; tests += test_pkcs1_15.get_tests(config=config)
+ import test_pkcs1_15; tests += test_pkcs1_15.get_tests(config=config)
+ import test_pkcs1_oaep; tests += test_pkcs1_oaep.get_tests(config=config)
return tests
if __name__ == '__main__':
Oops, something went wrong.

0 comments on commit a2ef0a2

Please sign in to comment.