def undigest(self, blocks): """undigest(blocks : [string]) : string Perform the reverse package transformation on a list of message blocks. Note that the ciphermodule used for both transformations must be the same. blocks is a list of strings of bit length equal to the ciphermodule's block_size. """ # better have at least 2 blocks, for the padbytes package and the hash # block accumulator if len(blocks) < 2: raise ValueError("List must be at least length 2.") # blocks is a list of strings. We need to deal with them as long # integers blocks = list(map(bytes_to_long, blocks)) # Calculate the well-known key, to which the hash blocks are # encrypted, and create the hash cipher. K0 = self.__K0digit * self.__key_size hcipher = self.__newcipher(K0) block_size = self.__ciphermodule.block_size # Since we have all the blocks (or this method would have been called # prematurely), we can calculate all the hash blocks. hashes = [] for i in range(1, len(blocks)): mticki = blocks[i-1] ^ i hi = hcipher.encrypt(long_to_bytes(mticki, block_size)) hashes.append(bytes_to_long(hi)) # now we can calculate K' (key). remember the last block contains # m's' which we don't include here key = blocks[-1] ^ reduce(operator.xor, hashes) # and now we can create the cipher object mcipher = self.__newcipher(long_to_bytes(key, self.__key_size)) # And we can now decode the original message blocks parts = [] for i in range(1, len(blocks)): cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) mi = blocks[i-1] ^ bytes_to_long(cipherblock) parts.append(mi) # The last message block contains the number of pad bytes appended to # the original text string, such that its length was an even multiple # of the cipher's block_size. This number should be small enough that # the conversion from long integer to integer should never overflow padbytes = int(parts[-1]) text = b('').join(map(long_to_bytes, parts[:-1])) return text[:-padbytes]
def MGF1(mgfSeed, maskLen, hash): """Mask Generation Function, described in B.2.1""" T = b("") for counter in range(ceil_div(maskLen, hash.digest_size)): c = long_to_bytes(counter, 4) T = T + hash.new(mgfSeed + c).digest() assert (len(T) >= maskLen) return T[:maskLen]
def _lengthOctets(self, payloadLen): """Return a byte string that encodes the given payload length (in bytes) in a format suitable for a DER length tag (L). """ if payloadLen > 127: encoding = long_to_bytes(payloadLen) return bchr(len(encoding) + 128) + encoding return bchr(payloadLen)
def generateQ(randfunc): S = randfunc(20) hash1 = SHA.new(S).digest() hash2 = SHA.new(long_to_bytes(bytes_to_long(S) + 1)).digest() q = bignum(0) for i in range(0, 20): c = bord(hash1[i]) ^ bord(hash2[i]) if i == 0: c = c | 128 if i == 19: c = c | 1 q = q * 256 + c while (not isPrime(q)): q = q + 2 if pow(2, 159) < q < pow(2, 160): return S, q raise RuntimeError('Bad q value generated')
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 Crypro.Hash.MD5 from Crypro.Cipher import DES3 from Crypro.Protocol.KDF import PBKDF1 salt = self._randfunc(8) key = PBKDF1(passphrase, salt, 16, 1, Crypro.Hash.MD5) key += PBKDF1(key + passphrase, salt, 8, 1, Crypro.Hash.MD5) objenc = DES3.new(key, Crypro.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 encode(self): """Return a complete INTEGER DER element, fully encoded as a TLV.""" self.payload = long_to_bytes(self.value) if bord(self.payload[0]) > 127: self.payload = bchr(0x00) + self.payload return DerObject.encode(self)
def digest(self, text): """digest(text:string) : [string] Perform the All-or-Nothing package transform on the given string. Output is a list of message blocks describing the transformed text, where each block is a string of bit length equal to the ciphermodule's block_size. """ # generate a random session key and K0, the key used to encrypt the # hash blocks. Rivest calls this a fixed, publically-known encryption # key, but says nothing about the security implications of this key or # how to choose it. key = self._inventkey(self.__key_size) K0 = self.__K0digit * self.__key_size # we need two cipher objects here, one that is used to encrypt the # message blocks and one that is used to encrypt the hashes. The # former uses the randomly generated key, while the latter uses the # well-known key. mcipher = self.__newcipher(key) hcipher = self.__newcipher(K0) # Pad the text so that its length is a multiple of the cipher's # block_size. Pad with trailing spaces, which will be eliminated in # the undigest() step. block_size = self.__ciphermodule.block_size padbytes = block_size - (len(text) % block_size) text = text + b(' ') * padbytes # Run through the algorithm: # s: number of message blocks (size of text / block_size) # input sequence: m1, m2, ... ms # random key K' (`key' in the code) # Compute output sequence: m'1, m'2, ... m's' for s' = s + 1 # Let m'i = mi ^ E(K', i) for i = 1, 2, 3, ..., s # Let m's' = K' ^ h1 ^ h2 ^ ... hs # where hi = E(K0, m'i ^ i) for i = 1, 2, ... s # # The one complication I add is that the last message block is hard # coded to the number of padbytes added, so that these can be stripped # during the undigest() step s = divmod(len(text), block_size)[0] blocks = [] hashes = [] for i in range(1, s+1): start = (i-1) * block_size end = start + block_size mi = text[start:end] assert len(mi) == block_size cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) mticki = bytes_to_long(mi) ^ bytes_to_long(cipherblock) blocks.append(mticki) # calculate the hash block for this block hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) hashes.append(bytes_to_long(hi)) # Add the padbytes length as a message block i = i + 1 cipherblock = mcipher.encrypt(long_to_bytes(i, block_size)) mticki = padbytes ^ bytes_to_long(cipherblock) blocks.append(mticki) # calculate this block's hash hi = hcipher.encrypt(long_to_bytes(mticki ^ i, block_size)) hashes.append(bytes_to_long(hi)) # Now calculate the last message block of the sequence 1..s'. This # will contain the random session key XOR'd with all the hash blocks, # so that for undigest(), once all the hash blocks are calculated, the # session key can be trivially extracted. Calculating all the hash # blocks requires that all the message blocks be received, thus the # All-or-Nothing algorithm succeeds. mtick_stick = bytes_to_long(key) ^ reduce(operator.xor, hashes) blocks.append(mtick_stick) # we convert the blocks to strings since in Python, byte sequences are # always represented as strings. This is more consistent with the # model that encryption and hash algorithms always operate on strings. return [long_to_bytes(i,self.__ciphermodule.block_size) for i in blocks]