def rsa_construct(n, e, d=None, p=None, q=None, u=None): """Construct an RSAKey object""" assert isinstance(n, int) assert isinstance(e, int) assert isinstance(d, (int, type(None))) assert isinstance(p, (int, type(None))) assert isinstance(q, (int, type(None))) assert isinstance(u, (int, type(None))) obj = _RSAKey() obj.n = n obj.e = e if d is None: return obj obj.d = d if p is not None and q is not None: obj.p = p obj.q = q else: # Compute factors p and q from the private exponent d. # We assume that n has no more than two factors. # See 8.2.2(i) in Handbook of Applied Cryptography. ktot = d * e - 1 # The quantity d*e-1 is a multiple of phi(n), even, # and can be represented as t*2^s. t = ktot while t % 2 == 0: t = divmod(t, 2)[0] # Cycle through all multiplicative inverses in Zn. # The algorithm is non-deterministic, but there is a 50% chance # any candidate a leads to successful factoring. # See "Digitalized Signatures and Public Key Functions as Intractable # as Factorization", M. Rabin, 1979 spotted = 0 a = 2 while not spotted and a < 100: k = t # Cycle through all values a^{t*2^i}=a^k while k < ktot: cand = pow(a, k, n) # Check if a^k is a non-trivial root of unity (mod n) if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: # We have found a number such that (cand-1)(cand+1)=0 (mod n). # Either of the terms divides n. obj.p = GCD(cand + 1, n) spotted = 1 break k = k * 2 # This value was not any good... let's try another! a = a + 2 if not spotted: raise ValueError( "Unable to compute factors p and q from exponent d.") # Found ! assert ((n % obj.p) == 0) obj.q = divmod(n, obj.p)[0] if u is not None: obj.u = u else: obj.u = inverse(obj.p, obj.q) return obj
def _decrypt(self, M): if (not hasattr(self, 'x')): raise TypeError('Private key not available in this object') r = number.getRandomRange(2, self.p - 1, self._randfunc) a_blind = (M[0] * pow(self.g, r, self.p)) % self.p ax = pow(a_blind, self.x, self.p) plaintext_blind = (M[1] * number.inverse(ax, self.p)) % self.p plaintext = (plaintext_blind * pow(self.y, r, self.p)) % self.p return plaintext
def _verify(self, m, r, s): # SECURITY TODO - We _should_ be computing SHA1(m), but we don't because that's the API. if not (0 < r < self.q) or not (0 < s < self.q): return False w = inverse(s, self.q) u1 = (m * w) % self.q u2 = (r * w) % self.q v = (pow(self.g, u1, self.p) * pow(self.y, u2, self.p) % self.p) % self.q return v == r
def _sign(self, m, k): # alias for _decrypt # SECURITY TODO - We _should_ be computing SHA1(m), but we don't because that's the API. if not self.has_private(): raise TypeError("No private key") if not (1 < k < self.q): raise ValueError("k is not between 2 and q-1") inv_k = inverse(k, self.q) # Compute k**-1 mod q r = pow(self.g, k, self.p) % self.q # r = (g**k mod p) mod q s = (inv_k * (m + self.x * r)) % self.q return (r, s)
def _sign(self, M, K): if (not hasattr(self, 'x')): raise TypeError('Private key not available in this object') p1 = self.p - 1 if (number.GCD(K, p1) != 1): raise ValueError('Bad K value: GCD(K,p-1)!=1') a = pow(self.g, K, self.p) t = (M - self.x * a) % p1 while t < 0: t = t + p1 b = (t * number.inverse(K, p1)) % p1 return (a, b)
def _importKeyDER(self, externKey): """Import an RSA key (public or private half), encoded in DER form.""" try: der = DerSequence() der.decode(externKey, True) # Try PKCS#1 first, for a private key if len(der) == 9 and der.hasOnlyInts() and der[0] == 0: # ASN.1 RSAPrivateKey element del der[ 6:] # Remove d mod (p-1), d mod (q-1), and q^{-1} mod p der.append(inverse(der[4], der[5])) # Add p^{-1} mod q del der[0] # Remove version return self.construct(der[:]) # Keep on trying PKCS#1, but now for a public key if len(der) == 2: # The DER object is an RSAPublicKey SEQUENCE with two elements if der.hasOnlyInts(): return self.construct(der[:]) # The DER object is a SubjectPublicKeyInfo SEQUENCE with two elements: # an 'algorithm' (or 'algorithmIdentifier') SEQUENCE and a 'subjectPublicKey' BIT STRING. # 'algorithm' takes the value given a few lines above. # 'subjectPublicKey' encapsulates the actual ASN.1 RSAPublicKey element. if der[0] == algorithmIdentifier: bitmap = DerObject() bitmap.decode(der[1], True) if bitmap.isType('BIT STRING') and bord( bitmap.payload[0]) == 0x00: der.decode(bitmap.payload[1:], True) if len(der) == 2 and der.hasOnlyInts(): return self.construct(der[:]) # Try unencrypted PKCS#8 if der[0] == 0: # The second element in the SEQUENCE is algorithmIdentifier. # It must say RSA (see above for description). if der[1] == algorithmIdentifier: privateKey = DerObject() privateKey.decode(der[2], True) if privateKey.isType('OCTET STRING'): return self._importKeyDER(privateKey.payload) except ValueError as IndexError: pass raise ValueError("RSA key format is not supported")
def _unblind(self, m, r): # compute m / r (mod n) return inverse(r, self.n) * m % self.n
def exportKey(self, format='PEM', passphrase=None, pkcs=1): """Export this RSA key. :Parameter format: The format to use for wrapping the key. - *'DER'*. Binary encoding, always unencrypted. - *'PEM'*. Textual encoding, done according to `RFC1421`_/`RFC1423`_. Unencrypted (default) or encrypted. - *'OpenSSH'*. Textual encoding, done according to OpenSSH specification. Only suitable for public keys (not private keys). :Type format: string :Parameter passphrase: In case of PEM, the pass phrase to derive the encryption key from. :Type passphrase: string :Parameter pkcs: The PKCS standard to follow for assembling the key. You have two choices: - with **1**, the public key is embedded into an X.509 `SubjectPublicKeyInfo` DER SEQUENCE. The private key is embedded into a `PKCS#1`_ `RSAPrivateKey` DER SEQUENCE. This mode is the default. - with **8**, the private key is embedded into a `PKCS#8`_ `PrivateKeyInfo` DER SEQUENCE. This mode is not available for public keys. PKCS standards are not relevant for the *OpenSSH* format. :Type pkcs: integer :Return: A byte string with the encoded public or private half. :Raise ValueError: When the format is unknown. .. _RFC1421: http://www.ietf.org/rfc/rfc1421.txt .. _RFC1423: http://www.ietf.org/rfc/rfc1423.txt .. _`PKCS#1`: http://www.ietf.org/rfc/rfc3447.txt .. _`PKCS#8`: http://www.ietf.org/rfc/rfc5208.txt """ if passphrase is not None: passphrase = tobytes(passphrase) if format == 'OpenSSH': eb = long_to_bytes(self.e) nb = long_to_bytes(self.n) if bord(eb[0]) & 0x80: eb = bchr(0x00) + eb if bord(nb[0]) & 0x80: nb = bchr(0x00) + nb keyparts = ['ssh-rsa', eb, nb] keystring = ''.join( [struct.pack(">I", len(kp)) + kp for kp in keyparts]) return 'ssh-rsa ' + binascii.b2a_base64(keystring)[:-1] # DER format is always used, even in case of PEM, which simply # encodes it into BASE64. der = DerSequence() if self.has_private(): keyType = {1: 'RSA PRIVATE', 8: 'PRIVATE'}[pkcs] der[:] = [ 0, self.n, self.e, self.d, self.p, self.q, self.d % (self.p - 1), self.d % (self.q - 1), inverse(self.q, self.p) ] if pkcs == 8: derkey = der.encode() der = DerSequence([0]) der.append(algorithmIdentifier) der.append(DerObject('OCTET STRING', derkey).encode()) else: keyType = "PUBLIC" der.append(algorithmIdentifier) bitmap = DerObject('BIT STRING') derPK = DerSequence([self.n, self.e]) bitmap.payload = bchr(0x00) + derPK.encode() der.append(bitmap.encode()) if format == 'DER': return der.encode() if format == 'PEM': pem = b("-----BEGIN " + keyType + " KEY-----\n") objenc = None if passphrase and keyType.endswith('PRIVATE'): # We only support 3DES for encryption import Crypto.Hash.MD5 from Crypto.Cipher import DES3 from Crypto.Protocol.KDF import PBKDF1 salt = self._randfunc(8) key = PBKDF1(passphrase, salt, 16, 1, Crypto.Hash.MD5) key += PBKDF1(key + passphrase, salt, 8, 1, Crypto.Hash.MD5) objenc = DES3.new(key, Crypto.Cipher.DES3.MODE_CBC, salt) pem += b('Proc-Type: 4,ENCRYPTED\n') pem += b('DEK-Info: DES-EDE3-CBC,') + binascii.b2a_hex( salt).upper() + b('\n\n') binaryKey = der.encode() if objenc: # Add PKCS#7-like padding padding = objenc.block_size - len( binaryKey) % objenc.block_size binaryKey = objenc.encrypt(binaryKey + bchr(padding) * padding) # Each BASE64 line can take up to 64 characters (=48 bytes of data) chunks = [ binascii.b2a_base64(binaryKey[i:i + 48]) for i in range(0, len(binaryKey), 48) ] pem += b('').join(chunks) pem += b("-----END " + keyType + " KEY-----") return pem return ValueError( "Unknown key format '%s'. Cannot export the RSA key." % format)
def generate(bits, randfunc, progress_func=None): """Randomly generate a fresh, new ElGamal key. The key will be safe for use for both encryption and signature (although it should be used for **only one** purpose). :Parameters: bits : int Key length, or size (in bits) of the modulus *p*. Recommended value is 2048. randfunc : callable Random number generation function; it should accept a single integer N and return a string of random data N bytes long. progress_func : callable Optional function that will be called with a short string containing the key parameter currently being generated; it's useful for interactive applications where a user is waiting for a key to be generated. :attention: You should always use a cryptographically secure random number generator, such as the one defined in the ``Crypto.Random`` module; **don't** just use the current time and the ``random`` module. :Return: An ElGamal key object (`ElGamalobj`). """ obj = ElGamalobj() # Generate a safe prime p # See Algorithm 4.86 in Handbook of Applied Cryptography if progress_func: progress_func('p\n') while 1: q = int(number.getPrime(bits - 1, randfunc)) obj.p = 2 * q + 1 if number.isPrime(obj.p, randfunc=randfunc): break # Generate generator g # See Algorithm 4.80 in Handbook of Applied Cryptography # Note that the order of the group is n=p-1=2q, where q is prime if progress_func: progress_func('g\n') while 1: # We must avoid g=2 because of Bleichenbacher's attack described # in "Generating ElGamal signatures without knowning the secret key", # 1996 # obj.g = number.getRandomRange(3, obj.p, randfunc) safe = 1 if pow(obj.g, 2, obj.p) == 1: safe = 0 if safe and pow(obj.g, q, obj.p) == 1: safe = 0 # Discard g if it divides p-1 because of the attack described # in Note 11.67 (iii) in HAC if safe and divmod(obj.p - 1, obj.g)[1] == 0: safe = 0 # g^{-1} must not divide p-1 because of Khadir's attack # described in "Conditions of the generator for forging ElGamal # signature", 2011 ginv = number.inverse(obj.g, obj.p) if safe and divmod(obj.p - 1, ginv)[1] == 0: safe = 0 if safe: break # Generate private key x if progress_func: progress_func('x\n') obj.x = number.getRandomRange(2, obj.p - 1, randfunc) # Generate public key y if progress_func: progress_func('y\n') obj.y = pow(obj.g, obj.x, obj.p) return obj