def from_primes(cls, p, q): n = p * q t = (p - 1) * (q - 1) e = 65537 # Handle the (normally very, very low probability) special # case that n is a small number - this may the case for # some test code using small primes if e >= n: e = n / 2 + n % 2 while t % e == 0: e += 1 # The value 56 is hardcoded for a high probability test - # however - see versile.crypto.math._SMALL_PRIMES doc - # with that variable set properly, the test is expected to # nearly always be resolved based on deterministic Euler # sieve tests while not is_prime(e, 56): # HARDCODED e += 1 d = mod_inv(e, t) if d is None: raise VCryptoException('Could not generate key') if (d * e) % t == 1: keydata = (n, e, d, p, q) return _VLocalRSAKey(keydata) else: raise VCryptoException('Could not generate key')
def __init__(self, num_transform, in_size, out_size, iv, mode, encrypt, rand): self.__num_transform = num_transform self.__in_size = in_size self.__out_size = out_size self.__encrypt = encrypt self.__rand = rand super(_VNumBlockTransform, self).__init__(in_size) if encrypt: self.__plainsize = in_size self.__ciphersize = out_size else: self.__plainsize = out_size self.__ciphersize = in_size # iv always uses plaintext blocksize if not isinstance(iv, bytes) or len(iv) != self.__plainsize: raise VCryptoException('Invalid initialization vector') self.__iv = iv if mode == 'cbc': self.__transform = self.__transform_cbc else: raise VCryptoException('Mode not supported')
def __init__(self, keydata, encrypt): def _convert(num): if num is not None: return long(num) else: return None self.__keydata = [_convert(item) for item in keydata] n, e, d = self.__keydata[:3] if encrypt: if e is None: raise VCryptoException('Encrypt requires public key') py_key = RSA.construct((n, e)) self.__transform = lambda num: py_key.encrypt(num, None)[0] else: if d is None: raise VCryptoException('Decrypt requires private key') if e is not None: py_key = RSA.construct((n, e, d)) self.__transform = lambda num: py_key.decrypt(num) else: # Rephrase as 'encrypt', PyCrypto does not have private-only py_key = RSA.construct((n, d)) self.__transform = lambda num: py_key.encrypt(num, None)[0]
def load(self, keydata): if not isinstance(keydata, bytes): raise VCryptoException('Keydata must be in bytes format') min_l, max_l, size_inc = self.constraints() if not min_l <= len(keydata) <= max_l or len(keydata) % size_inc: raise VCryptoException('Invalid key length') return self._gen_key(keydata)
def export_spki_public_key(cls, key, fmt=VX509Format.PEM_BLOCK): """Exports a X.509 SPKI public key. :param key: key to export :type key: :class:`versile.crypto.VAsymmetricKey` :param fmt: format to export to :type fmt: :class:`VX509Format` constant :returns: exported key data Exports an encoding of the key's :term:`ASN.1` representation defined by X.509 SubjectPublicKeyInfo encoding format, associated with the PEM header 'BEGIN PUBLIC KEY'. .. note:: For the :term:`PKCS#1`\ encoding format (PEM header 'BEGIN RSA PUBLIC KEY'), see :meth:`export_public_key`\ . """ if not isinstance(key, VAsymmetricKey): raise TypeError('Invalid key type') if key.cipher_name != 'rsa': raise VCryptoException('Encoding not supported') if key.has_public: n, e = key.keydata[:2] # Create spki structure from versile.crypto.x509.asn1def.cert import SubjectPublicKeyInfo asn1 = SubjectPublicKeyInfo().create() # - algorithm alg = AlgorithmIdentifier().create() _alg_id = VObjectIdentifier(1, 2, 840, 113549, 1, 1, 1) alg.append(_alg_id, name='algorithm') alg.append(VASN1Null(), name='parameters') asn1.append(alg, name='algorithm') # - keydata spk = cls.export_public_key(key, fmt=VX509Format.DER) spk = VBitfield.from_octets(spk) asn1.append(spk, name='subjectPublicKey') if not asn1.validate(): raise VCryptoException('ASN.1 structure validation error') if fmt == VX509Format.ASN1: return asn1 der = asn1.encode_der() if fmt == VX509Format.DER: return der if fmt == VX509Format.PEM: if _pyver == 2: return _s2b(base64.encodestring(_b2s(der))) else: return base64.encodebytes(der) elif fmt == VX509Format.PEM_BLOCK: return encode_pem_block(b'PUBLIC KEY', der) else: raise VCryptoException('Invalid encoding')
def __init__(self, keydata, iv, mode, encrypt, blocksize): super(_PyBlockTransform, self).__init__(blocksize=blocksize) if not isinstance(keydata, bytes) or not 1 <= len(keydata) <= 56: raise VCryptoException('Invalid key data') elif not isinstance(iv, bytes) or len(iv) != self.blocksize: raise VCryptoException('Invalid initialization vector') self.__encrypt = bool(encrypt) self.__cipher = self._pycrypto_cipher(keydata, iv, mode)
def __block_transform(self, block, in_size, out_size): if len(block) != in_size: raise VCryptoException('Invalid input block size') num = bytes_to_posint(block) trans_num = self.__num_transform(num) trans_block = posint_to_bytes(trans_num) trans_block_len = len(trans_block) if trans_block_len > out_size: raise VCryptoException('Invalid output block size') elif trans_block_len < out_size: pad_num = out_size - trans_block_len trans_block = pad_num * b'\x00' + trans_block return trans_block
def __init__(self, keydata, iv, mode, encrypt): super(_VLocalBlowfishTransform, self).__init__(blocksize=8) self.__cipher = Blowfish(keydata) if not isinstance(iv, bytes) or len(iv) != self.blocksize: raise VCryptoException('Invalid initialization vector') self.__iv = iv if mode == 'cbc': self.__transform = self.__transform_cbc elif mode == 'ofb': self.__transform = self.__transform_ofb else: raise VCryptoException('Mode not supported') self.__encrypt = bool(encrypt)
def raw_generate(self, bits, sec_id_data): if bits < 512 or bits % 8: raise VCryptoException('Invalid number of bits') try: hmac_input = sec_id_data.encode('utf8') except: raise VCryptoException('Could not UTF-8 encode sec_id_data') hmac_input += b':Scheme:dia' + _val2b(bits) crypto = VLocalCrypto() p_rand = VPseudoRandomHMAC(hash_cls=crypto.sha256, secret=b'', seed=hmac_input) return crypto.rsa.key_factory.generate(p_rand, bits // 8)
def export_public_key(cls, key, fmt=VX509Format.PEM_BLOCK): """Exports a public key in PKCS#1 format. :param key: key to export :type key: :class:`versile.crypto.VAsymmetricKey` :param fmt: format to export to :type fmt: :class:`VX509Format` constant :returns: exported key data Exports an encoding of the key's :term:`ASN.1` representation defined by :term:`PKCS#1`\, associated with the PEM header 'BEGIN RSA PUBLIC KEY'. .. note:: For the X.509 SubjectPublicKeyInfo encoding format (PEM header 'BEGIN PUBLIC KEY'), see :meth:`export_spki_public_key`\ . """ if not isinstance(key, VAsymmetricKey): raise TypeError('Invalid key type') if key.cipher_name == 'rsa': if key.has_public: n, e = key.keydata[:2] asn1 = RSAPublicKey().create() asn1.append(n, name='modulus') asn1.append(e, name='publicExponent') if not asn1.validate(): raise VCryptoException('ASN.1 structure validation error') if fmt == VX509Format.ASN1: return asn1 der = asn1.encode_der() if fmt == VX509Format.DER: return der if fmt == VX509Format.PEM: if _pyver == 2: return _s2b(base64.encodestring(_b2s(der))) else: return base64.encodebytes(der) elif fmt == VX509Format.PEM_BLOCK: return encode_pem_block(b'RSA PUBLIC KEY', der) else: raise VCryptoException('Invalid encoding') else: raise VCryptoException('Encoding not supported')
def __transform_cbc(self, data): len_data = len(data) if len_data % self.__in_size: raise VCryptoException('Data not block aligned') result = [] start = 0 while start < len_data: end = start + self.__in_size block = data[start:end] if self.__encrypt: if _pyver == 2: indata = b''.join([ _s2b(_b_chr(_b_ord(a) ^ _b_ord(b))) for a, b in zip(block, self.__iv) ]) else: indata = bytes([a ^ b for a, b in zip(block, self.__iv)]) # Appending random data, similar to RSAES-PKCS1-V1_5-ENCRYPT _rand_data = self.__rand(8) indata = b''.join((b'\x02', indata, b'\x00', _rand_data)) cipher = self.__block_transform(indata, self.__in_size + 10, self.__out_size) # Can only take plaintext size bytes as the carry-on mask self.__iv = cipher[:(self.__plainsize)] result.append(cipher) else: deciphered = self.__block_transform(block, self.__in_size, self.__out_size + 10) if _pyver == 2: if deciphered[0] != b'\x02' or deciphered[-9] != b'\x00': raise VCryptoException('Invalid RSA ciphertext') else: if deciphered[0] != 0x02 or deciphered[-9] != 0x00: raise VCryptoException('Invalid RSA ciphertext') deciphered = deciphered[1:-8] if _pyver == 2: plaintext = b''.join([ _s2b(_b_chr(_b_ord(a) ^ _b_ord(b))) for a, b in zip(deciphered, self.__iv) ]) else: plaintext = bytes( [a ^ b for a, b in zip(deciphered, self.__iv)]) # Can only take plaintext size bytes as the carry-on mask self.__iv = block[:(self.__plainsize)] result.append(plaintext) start += self.__in_size return b''.join(result)
def public_ca(self, **kargs): """Returns the :term:`Public CA` keypair and certificate. :param unsafe: must be set to True :type unsafe: bool :returns: (CA keypair, CA certificate) :rtype: (\ :class:`versile.crypto.VAsymmetricKey`\ , :class:`versile.crypto.x509.cert.VX509Certificate`\ ) :raises: :exc:`versile.crypto.VCryptoException` The keyword argument *unsafe* must be set to True when calling this method, otherwise an exception is raised. This is a safety mechanism to help prevent accidental use of :term:`Public CA` crededentials. Using the key for encryption or certificate signing is inherently unsafe as the full :term:`Public CA` keypair is publicly available. .. warning:: The :term:`Public CA` keypair should not be used for anything except signing non-certified keys for protocols which require a CA signature due to technical constraints """ if 'unsafe' not in kargs or not kargs['unsafe']: raise VCryptoException('The "unsafe" keyword must be set.') _keyloader = VX509Crypto.import_private_key ca_key = _keyloader(_STD_CA_KEYDATA, fmt=VX509Format.PEM_BLOCK) _certloader = VX509Certificate.import_cert ca_cert = _certloader(_STD_CA_CERTDATA, fmt=VX509Format.PEM_BLOCK) return ca_key, ca_cert
def __init__(self, keylen): if keylen not in (16, 24, 32): raise VCryptoException('Not a supported AES key length') else: self.__keylen = keylen super_init = super(_PyAES, self).__init__ super_init('aes%s' % self.__keylen, ('cbc', 'ofb'), True)
def _coeff(self): if self.__coeff is None: p, q = self.__keydata[3], self.__keydata[4] if p is None or q is None: raise VCryptoException() self.__coeff = mod_inv(q, p) return self.__coeff
def _exp2(self): if self.__exp2 is None: d, q = self.__keydata[2], self.__keydata[4] if d is None or q is None: raise VCryptoException() self.__exp2 = d % (q - 1) return self.__exp2
def _exp1(self): if self.__exp1 is None: d, p = self.__keydata[2], self.__keydata[3] if d is None or p is None: raise VCryptoException() self.__exp1 = d % (p - 1) return self.__exp1
def encipher(self, data): """Encipher plaintext and return result. :param data: plaintext to encipher (8 bytes) :type data: bytes :returns: enciphered data (8 bytes) :rtype: bytes The data must align with 8-byte blocksize. .. note:: Enciphering is performed without any kind of chaining, and the same plaintext will always return the same enciphered block of data. In order to use securely as a cipher, it is normally required that the cipher is combined with chaining techniques. """ len_data = len(data) if len_data % 8: raise VCryptoException('Data not aligned with 8-byte blocksize') if len_data == 8: return self._encipher_block(data) else: result = [] start = 0 while start < len_data: end = start + 8 block = data[start:end] result.append(self._encipher_block(block)) start += 8 return b''.join(result)
def decipher(self, data): """Decipher encrypted data and return deciphered plaintext. :param data: enciphered data (8 bytes) :type data: bytes :returns: deciphered plaintext (8 bytes) :rtype: bytes The block of enciphered data must be a multiple of 8 bytes. """ len_data = len(data) if len_data % 8: raise VCryptoException('Data not aligned with 8-byte blocksize') if len_data == 8: return self._decipher_block(data) else: result = [] start = 0 while start < len_data: end = start + 8 block = data[start:end] result.append(self._decipher_block(block)) start += 8 return b''.join(result)
def __transform_cbc(self, data): len_data = len(data) if len_data % 8: raise VCryptoException('Input not aligned to blocksize') result = [] start = 0 while start < len_data: end = start + 8 block = data[start:end] if self.__encrypt: if _pyver == 2: indata = b''.join([ _s2b(_b_chr(_b_ord(a) ^ _b_ord(b))) for a, b in zip(block, self.__iv) ]) else: indata = bytes([a ^ b for a, b in zip(block, self.__iv)]) cipher = self.__cipher.encipher(indata) self.__iv = cipher result.append(cipher) else: deciphered = self.__cipher.decipher(block) if _pyver == 2: plaintext = b''.join([ _s2b(_b_chr(_b_ord(a) ^ _b_ord(b))) for a, b in zip(deciphered, self.__iv) ]) else: plaintext = bytes( [a ^ b for a, b in zip(deciphered, self.__iv)]) self.__iv = block result.append(plaintext) start += 8 return b''.join(result)
def _keydata(self, key): if isinstance(key, VKey): if key.cipher_name == self.name: return key.keydata else: raise VCryptoException('Key ciphername mismatch') raise TypeError('Key must be bytes or a Key object')
def message(self, plaintext): """Returns an encrypted message for a provided plaintext. :param plaintext: the plaintext to encode and encrypt :type plaintext: bytes :returns: encrypted message-protected plaintext :rtype: bytes :raises: :exc:`versile.crypto.VCryptoException` Raises an exception if provided plaintext is longer than :attr:`max_plaintext_len`\ , meaning the plaintext is larger than what is allowed inside a single message. Empty plaintext is also not allowed. """ plaintext_len = len(plaintext) if plaintext_len > self._max_plaintext_len: raise VCryptoException('Plaintext too long') elif not plaintext: raise VCryptoException('Empty plaintext not allowed') # Generate padding msg_len = 2 + plaintext_len + self._hash_len _bsize = self._plaintext_blocksize pad_len = msg_len % _bsize if pad_len: pad_len = _bsize - pad_len # Create message content encode_len = plaintext_len - 1 if _pyver == 2: plain_len = (_s2b(_b_chr((encode_len & 0xff00) >> 8)) + _s2b(_b_chr(encode_len & 0xff))) else: plain_len = bytes( (((encode_len & 0xff00) >> 8), (encode_len & 0xff))) padding = self._pad_provider(pad_len) _mac_msg = b''.join( (posint_to_bytes(self._msg_num), plain_len, plaintext, padding)) msg_hash = self._hash_cls.hmac(self._mac_secret, _mac_msg) msg = b''.join((plain_len, plaintext, padding, msg_hash)) # Create encrypted message enc_msg = self._encrypter(msg) self._msg_num += 1 return enc_msg
def _pycrypto_cipher(self, keydata, iv, mode): if mode == 'cbc': _mode = AES.MODE_CBC elif mode == 'ofb': _mode = AES.MODE_OFB else: raise VCryptoException('Mode not supported') return AES.new(keydata, _mode, iv)
def __max_num(self, key): try: max_num = self.__num_cipher.encrypter(key).max_number except: try: max_num = self.__num_cipher.decrypter(key).max_number except: raise VCryptoException('Could not determine max number') return max_num
def block_cipher(self, cipher_name): if cipher_name == 'blowfish': return _VLocalBlowfish() elif cipher_name == 'blowfish128': return _VLocalBlowfish128() elif cipher_name == 'rsa': num_cipher = self.num_cipher('rsa') return num_cipher.block_cipher() else: raise VCryptoException('Cipher not supported by this provider')
def emsa_pkcs1_v1_5_encode(cls, msg, enc_len, hash_cls): """Encodes an EMSA-PKCS1-v1_5-ENCODE message digest. :param msg: binary message to encode :type msg: bytes :param enc_len: length of encoded message :type enc_len: int :param hash_cls: hash class for message hashing :type hash_cls: :class:`versile.crypto.VHash` :returns: encoded message of length *enc_len* :rtype: bytes :raises: :exc:`versile.crypto.VCryptoException` See :term:`PKCS#1` for details. When a strong hash function is used, this method produces an encoded representation of *msg* which is suitable for digital signatures. An exception is raised if the encoding cannot be made (typically because the message does not fit inside a bytes object of length *enc_len*\ due to the length of *hash_cls* hash method digests). """ hasher = hash_cls(msg) der = DigestInfo().create() alg_id = hash_cls.oid() if not alg_id: raise VCryptoException('Hash algorithm not supported') seq = AlgorithmIdentifier().create() seq.append(alg_id, name='algorithm') seq.append(VASN1Null(), name='parameters') der.append(seq, name='digestAlgorithm') der.append(VASN1OctetString(hasher.digest()), name='digest') if not der.validate(): raise VCryptoException('Internal ASN.1 validation error') param_T = der.encode_der() pad_len = enc_len - len(param_T) - 3 if pad_len >= 0: param_PS = pad_len * b'\xff' else: raise VCryptoException('Encoding length too small') return b''.join((b'\x00', b'\x01', param_PS, b'\x00', param_T))
def result(self): """Returns plaintext of a decrypted and decoded message. :returns: decoded plaintext :rtype: bytes :raises: :exc:`versile.crypto.VCryptoException` Should only be called if :meth:`done` indicates message parsing was completed, otherwise an exception may be raised due to incomplete message. Raises an exception if the message failed to verify against the message hash, meaning the message cannot be trusted and may have been tampered with. """ if self._invalid: raise VCryptoException('Message failed to verify') elif self._result is None: raise VCryptoException('Message not yet fully decoded') return self._result
def import_ascii(self, keydata): name, num_data = self._decode_ascii(keydata) if not name.startswith(b'VERSILE RSA '): raise VCryptoException() name = name[12:] numbers = self._decode_numbers(num_data) if name == b'KEY PAIR': if len(numbers) != 5: raise VCryptoException() keydata = tuple(numbers) elif name == b'PUBLIC KEY': if len(numbers) != 2: raise VCryptoException() keydata = (numbers[0], numbers[1], None, None) elif name == b'PRIVATE KEY': if len(numbers) != 2: raise VCryptoException() keydata = (numbers[0], None, numbers[1], None, None) else: raise VCryptoException() return _VLocalRSAKey(keydata)
def merge_with(self, key): if self.name != key.name: raise VCryptoException('Key cipher types do not match') if self.keydata[0] != key.keydata[0]: raise VCryptoException('Key modulos do not match') if self.has_public and key.has_public: if self.keydata[1] != key.keydata[1]: raise VCryptoException('Key public component mismatch') if self.has_private and key.has_private: if self.keydata[2] != key.keydata[2]: raise VCryptoException('Key private component mismatch') if self.has_private: n = self.keydata[0] e = key.keydata[1] d = self.keydata[2] else: n = self.keydata[0] e = self.keydata[1] d = key.keydata[2] if None in (n, e, d): raise VCryptoException('Incomplete key data, cannot merge') # Try to recover p, q factors p, q = self.keydata[3:] if key.keydata[3] is not None: p = key.keydata[3] if key.keydata[4] is not None: q = key.keydata[4] if p is None or q is None: p = q = None elif p * q != n: raise VCryptoException('Got p,q, however p*q != n') return _VLocalRSAKey((n, e, d, p, q))
def decrypter(self, key, iv=None, mode='cbc'): if key is None: raise VCryptoException('Requires key') if iv is None: iv = self.blocksize(key) * b'\x00' num_decrypter = self.__num_cipher.decrypter(key) return _VNumBlockTransform(num_decrypter, self.c_blocksize(key), self.blocksize(key), iv, mode, encrypt=False, rand=self.__rand)
def hash_cls(self, hash_name): hash_oid = None if hash_name == 'sha1': _hash = hashlib.sha1 hash_oid = VObjectIdentifier(1, 3, 14, 3, 2, 26) elif hash_name == 'sha224': _hash = hashlib.sha224 elif hash_name == 'sha256': _hash = hashlib.sha256 hash_oid = VObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 1) elif hash_name == 'sha384': _hash = hashlib.sha384 hash_oid = VObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 2) elif hash_name == 'sha512': _hash = hashlib.sha512 hash_oid = VObjectIdentifier(2, 16, 840, 1, 101, 3, 4, 2, 3) elif hash_name == 'md5': _hash = hashlib.md5 else: raise VCryptoException('Hash method not implemented') class HashCls(VHash): @classmethod def name(cls): return hash_name @classmethod def oid(cls): return hash_oid @classmethod def digest_size(cls): return _hash().digest_size def __init__(self, data=None): self.__hash = _hash() super(HashCls, self).__init__(data=data) def update(self, data): if _pyver == 2: self.__hash.update(_b2s(data)) else: self.__hash.update(data) def digest(self): if _pyver == 2: return _s2b(self.__hash.digest()) else: return self.__hash.digest() return HashCls