Введение в простые инструменты шифрования
Способ установки:pip install easy-encryption-tool
в предыдущей статье«Серия медитаций на асимметричный ключ (2): разговоры о RSA и цифровых подписях»середина,Мы рассказали о понятии цифровой подписи,Сравнивалась связь между цифровыми подписями, MAC и значениями хеш-функции.
В этой статье мы говорим о сценариях использования цифровых подписей при аутентификации личности.
Многим веб-сайтам или приложениям перед публикацией в Интернете необходимо подать заявку на получение сертификата, чтобы доказать, что веб-сайт является законным.
Что касается цифрового сертификата, мы можем рассматривать его как своего рода удостоверение личности в онлайн-мире. Как правило, это авторитетный электронный документ, выданный авторитетным центром, который может обеспечить проверку личности в Интернете. Люди могут использовать его в Интернете. связи, чтобы подтвердить свою личность и идентифицировать другую сторону.
Например, когда мы посещаемhttps://www.baidu.comчас,Вы можете увидеть узор в форме 🔐 в левом верхнем углу адресной строки браузера.,Нажмите, чтобы просмотреть информацию о сертификате на этом веб-сайте:
При использовании браузера для доступа к веб-сайту,Браузер можно абстрактно понимать как сторонний центр сертификации.,https://www.baidu.comможно понимать как Учреждения, получившие Сертификат,И учреждение, выдавшее данный Сертификат,Мы можем назвать это центром сертификации.,В реальном мире его обычно называют CA.
Сертификат подобен удостоверению личности, доказывающему, что «открытый ключ соответствует веб-сайту».
В целом сертификат содержит следующее содержание:
В этой статье мы не будем подробно останавливаться на каждом поле сертификата.
В этой статье основное внимание уделяется ключевым вопросам сертификатов и удостоверений личности, поэтому мы просто абстрагируем сертификаты следующим образом:
Здесь мы предполагаем, что сертификат содержит следующую простую информацию:
И в этой статье мы игнорируем цепочку доверия сертификата и абстрагируем только три роли:
центр сертификации, его открытый ключ доступен всем пользователям или учреждениям.
Здесь мы по умолчанию указываем, что центр сертификации является независимым и авторитетным.
Организация или приложение, которому был выдан сертификат, который также предоставляет услуги всем пользователям Интернета, его открытый ключ также находится в свободном доступе для всех пользователей.
А услуги, предоставляемые организацией, выдавшей сертификат, также могут быть свободно доступны всем пользователям. Таким образом, любой пользователь, обращающийся к приложению, является проверяющим его сертификата.
В этой статье мы по-прежнему проводим эксперименты на основе классического алгоритма асимметричного ключа RSA.
Прежде чем приступить к моделированию реализации сертификата, давайте сначала рассмотрим характеристики подписей RSA.
Мы определяем следующую логику генерации пары ключей RSA:
def generate_rsa_keypair(key_size: int = 2048, exponent: int = 65537) -> Tuple[RSAPublicKey, RSAPrivateKey]:
"""
Справочная документация:
https://www.cnblogs.com/caidi/p/14794952.html
"""
private_key = rsa.generate_private_key(
public_exponent = exponent,
key_size = key_size,
backend = default_backend()
)
public_key = private_key.public_key()
# print("pubkey: e={}, n={}".format(public_key.public_numbers().e, public_key.public_numbers().n))
# print("prikey: d={}, n={}".format(private_key.private_numbers().d, private_key.public_key().public_numbers().n))
return public_key, private_key
Размер ключа по умолчанию составляет 2048 бит, что соответствует 256 байтам.
Длина ключа RSA может быть получена либо из открытого, либо из закрытого ключа и выражается в битовой длине.
def test_key_size(self):
"""
Одна и та же пара открытого и закрытого ключей имеет одинаковую длину ключа.
Либооткрытый ключ Длина зашифрованных данных зашифрованного текста или длина данных подписи после подписи секретным ключом соответствуют длине ключа, оба значения представляют собой длину ключа в байтах.
Размер длины ключа равен количеству битов, необходимых для значения n.
"""
pub_key, pri_key = rsa_base.generate_rsa_keypair()
print("pub_key key size:{}, pri_key key size:{}".format(pub_key.key_size, pri_key.key_size))
self.assertEqual(pub_key.key_size, pri_key.key_size)
self.assertEqual(pub_key.key_size, pub_key.public_numbers().n.bit_length())
self.assertEqual(pri_key.key_size, pub_key.public_numbers().n.bit_length())
После определения размера пары ключей,Независимо от того, зашифровано оно или подписано, длина группы зашифрованного текста определяется.。
Поскольку RSA также является режимом блочного шифрования, длина каждого блока соответствует длине ключа.
def test_rsa_cipher_size(self):
pub_key, pri_key = rsa_base.generate_rsa_keypair()
plain: bytes = b"hello,world"
cipher = pub_key.encrypt(plaintext = plain, padding = padding.OAEP(
mgf = padding.MGF1(algorithm = hashes.SHA256()),
algorithm = hashes.SHA256(),
label = None))
self.assertEqual(len(cipher), pub_key.key_size // 8)
signature = pri_key.sign(data = plain, padding = padding.PSS(
mgf = padding.MGF1(hashes.SHA256()),
salt_length = padding.PSS.MAX_LENGTH
), algorithm = hashes.SHA256())
self.assertEqual(len(signature), pub_key.key_size // 8)
Когда длина открытого текста не превышает длину пакета, длина зашифрованного текста и длина значения подписи остаются равными 256 байтам.
О максимальной длине открытого текста в шифровании RSA,Вы можете обратиться к подписанной статье:«Серия медитаций с асимметричным ключом (1): Обзор выборочных атак с использованием зашифрованного текста в режиме заполнения PKCSv1.5 по специальной теме RSA» рассуждение в.
Как мы упоминали ранее, мы просто абстрагируем содержимое сертификата и не восстанавливаем полностью исходные поля сертификата.
Наш сертификат теперь резюмируется как:
При реализации мы определяем следующую логику:
SUBJECT_KEY: str = 'subject'
AWARDED_PUB_KEY: str = 'awarded_pub_key'
ISSUER_NAME_KEY: str = 'issuer_name'
HASH_ALG_KEY: str = 'hash_alg'
HASH_NAME_KEY: str = 'hash_name'
HASH_BLOCK_SIZE_KEY: str = 'block_size'
HASH_DIGEST_SIZE_KEY: str = 'digest_size'
SIGNATURE_KEY: str = 'signature'
def mock_serialize_cert_content(subject: str,
awarded_pub_key: RSAPublicKey,
issuer_name: str,
hash_alg: hashes.HashAlgorithm) -> bytes:
"""
Сначала структурируйте содержимое Сертификата в объект json, а затем закодируйте этот объект json в поток байтов.
Args:
hash_alg: Алгоритм хеширования, используемый подписью
subject: Тема сертификата
awarded_pub_key: фото лица, награждаемого ключ
issuer_name: орган, выдавший документ
Returns: объект json, закодированный как возвращенный поток байтов
"""
# вернооткрытый ключ для кодировки base64
meta_data: Dict[str, str] = {
SUBJECT_KEY: subject,
AWARDED_PUB_KEY: pub_key_base64_encode(awarded_pub_key),
ISSUER_NAME_KEY: issuer_name,
HASH_ALG_KEY: json.dumps({
HASH_NAME_KEY: hash_alg.name,
HASH_BLOCK_SIZE_KEY: hash_alg.block_size,
HASH_DIGEST_SIZE_KEY: hash_alg.digest_size
})
}
# print("cert raw meta data:{}".format(meta_data))
meta_data_bytes = json.dumps(meta_data).encode(encoding = 'utf-8')
return meta_data_bytes
def mock_deserialize_cert_content(raw_data: bytes) -> Dict[str, str]:
raw_str = raw_data.decode(encoding = 'utf-8') # Преобразовать в строку
raw_dict: Dict[str, str] = json.loads(raw_str) # Десериализовать в объект json
return raw_dict
Отдельно следует пояснить, что касается кодирования открытого ключа эмитента, здесь мы определяем его как данные, в которых данные открытого ключа сериализуются в соответствии с форматом PEM, а затем кодируются в формате Base64. Его реализация может быть определена. как:
def pub_key_base64_encode(pub_key: RSAPublicKey) -> str:
p_bytes = pub_key.public_bytes(encoding = serialization.Encoding.PEM,
format = serialization.PublicFormat.SubjectPublicKeyInfo)
b64_bytes = base64.b64encode(p_bytes)
return b64_bytes.decode(encoding = 'utf-8')
def pub_key_base64_decode(pub_key_b64: str) -> RSAPublicKey:
pub_key_raw_bytes = base64.b64decode(pub_key_b64)
public_key = serialization.load_pem_public_key(
pub_key_raw_bytes,
backend = default_backend()
)
return public_key
Выдача сертификата — это процесс подписания на основе содержания сертификата.
Мы моделируем это как следующую реализацию:
def mock_create_cert(subject: str,
awarded_pub_key: RSAPublicKey,
issuer_pri_key: RSAPrivateKey,
issuer_name: str,
hash_alg: hashes.HashAlgorithm) -> bytes:
"""
Имитация выдачи Сертификата. Сертификат здесь представлен в байтовой форме, которую позже можно закодировать в различных форматах.
Args:
hash_alg: Алгоритм хеширования, используемый подписью
subject: Тема сертификата
awarded_pub_key: фото лица, награждаемого ключ
issuer_pri_key: Закрытый ключ эмитента
issuer_name: орган, выдавший документ
Returns: Информация о сертификате, содержание Сертификата + Фирменная композиция
"""
meta_data_bytes = mock_serialize_cert_content(subject, awarded_pub_key, issuer_name, hash_alg)
# print("meta data:{}".format(str(meta_data_bytes)))
signature = rsa_sign.sign_raw_message(issuer_pri_key, meta_data_bytes, hash_alg)
# print("signature:{}".format(signature))
return meta_data_bytes + signature
Здесь вы можете увидеть окончательно сгенерированное содержимое сертификата, которое на самом деле представляет собой простую сборку исходного содержимого сертификата + значение подписи:
class MockIssuer(object):
"""
Имитированный центр сертификации
Используется для выдачи сертификата нижестоящим учреждениям.
центр сертификация имеет собственную пару открытого и закрытого ключей.
"""
def __init__(self, this_issuer_name: str, this_issuer_hash_alg: hashes.HashAlgorithm = hashes.SHA256()):
self.__root_issuer_pub_key, self.__root_issuer_pri_key = rsa_base.generate_rsa_keypair()
self.__issuer_name = this_issuer_name
self.__issuer_hash = this_issuer_hash_alg
@property
def pub_key(self) -> RSAPublicKey:
return self.__root_issuer_pub_key
@property
def issuer_name(self) -> str:
return self.__issuer_name
@property
def issuer_hash(self) -> hashes.HashAlgorithm:
return self.__issuer_hash
def mock_issue_cert(self, subject: str, awarded_pub_key: RSAPublicKey) -> bytes:
"""
Выдача Сертификата нижестоящим организациям
Args:
subject: Тема Сертификата может быть связана с выдающим лицом, и эмитент может заполнить его самостоятельно.
awarded_pub_key: фото лица, награждаемого ключ
Returns: Информация о сертификате, форма потока байтов, незакодированная
"""
return mock_create_cert(subject, awarded_pub_key,
self.__root_issuer_pri_key,
self.__issuer_name,
self.__issuer_hash)
class MockAwarded(object):
"""
Имитировать субъекта, которому был выдан Сертификат
Эта организация получит финансирование от центра сертификациииз Сертификатинформация В то же время он подпишет данные для проверки сторонними проверяющими, тем самым доказывая, что он действительно владеет закрытым ключом.
"""
def __init__(self, awarded_name: str):
# # Выдается субъектом открытый ключ и закрытый ключ
self.__awarded_pub_key, self.__awarded_pri_key = rsa_base.generate_rsa_keypair()
self.__awarded_name = awarded_name # Название выдаваемой организации
self.__awarded_cert = bytes([0x00 for _ in range(256)]) # Зависит отцентр сертификациипроблемаиз Сертификат self.__default_hash_alg = hashes.SHA256()
@property
def name(self):
return self.__awarded_name
@property
def pub_key(self) -> RSAPublicKey:
return self.__awarded_pub_key
@property
def awarded_cert(self) -> bytes:
return self.__awarded_cert
@awarded_cert.setter
def awarded_cert(self, cert: bytes):
self.__awarded_cert = cert
def sign_data_for_verifier(self, data: bytes) -> Dict[str, Any]:
"""
Подпишите данные для стороннего проверяющего. Эти подписанные данные будут проверены сторонним проверяющим.
Args:
data: Сторонняя проверка предоставленных случайных данных
Returns: Возвращает проверенные данные в формате словарной структуры:
{
'signature': b'1234567890', # данные байтового потока
'hash_alg': hashes.SHA256() # хэши. Тип HashAlgorithm
}
"""
signature = rsa_sign.sign_raw_message(self.__awarded_pri_key, data, self.__default_hash_alg)
return {SIGNATURE_KEY: signature, HASH_ALG_KEY: self.__default_hash_alg}
После завершения выдачи сертификата ключом к применению сертификата является проверка личности сертификата и владельца сертификата.
В предыдущей логике мы выполнили следующие шаги:
Является ли сертификат законным или нет, является самым основным и ключевым звеном во всей проверке сертификата.
Все мы знаем, что при подписании используется закрытый ключ, а для проверки подписи используется открытый ключ.
Ранее мы ясно дали понять, что центр сертификацииизоткрытый Ключ является общедоступным и может быть получен кем угодно. Таким образом, любой верификатор может получить центр. сертификацииизоткрытый ключ,Вы можете проверить подпись самого Сертификата,Было проверено, выдан ли данный Сертификат законной организацией.
После проверки того, что сертификат выдан законно, у нас остается еще один момент для подтверждения, а именно, был ли этот сертификат выдан лицу, владеющему сертификатом.
Потому что сертификаты могут быть украдены. Хотя сам сертификат выдан на законных основаниях, он может оказаться у кого-то незаконно.
К счастью, наш сертификат содержит открытый ключ человека, выдавшего его, а также некоторую другую информацию.
Поэтому при проверке сертификата нам необходимо проанализировать содержимое сертификата и проверить некоторые поля по отдельности с информацией, предоставленной владельцем.
В этой статье логику разделения и разбора содержимого сертификата можно реализовать следующим образом:
def mock_parse_cert(cert: bytes, key_len: int = 256) -> Tuple[Dict[str, Any], bytes]:
"""
Разобрать Сертификат, проанализировать значение подписи Сертификата и содержимое Сертификата.
Args:
key_len: Длина закрытого ключа, используемого для подписи. Эта длина определяет длину значения подписи.
cert:Сертификатданные Returns: Вернуть содержимое и значение цифровой подписи Сертификата.
"""
if len(cert) < key_len:
raise ValueError("invalid cert data, length invalid")
split_index = len(cert) - key_len
signature = cert[split_index: len(cert)]
raw_data = cert[:split_index]
try:
cert_dict = mock_deserialize_cert_content(raw_data)
except BaseException as e:
print(e)
else:
return cert_dict, signature
Сертификаты могут быть украдены, как и открытые ключи.
Таким образом, после доказательства того, что сам сертификат является законным и что сертификат действительно выдан для определенного открытого ключа, нам все равно необходимо проверить, владеет ли проверяемое в данный момент лицо закрытым ключом, соответствующим открытому ключу.
class MockVerifier(object):
"""
Имитировать сторонние валидаторы,Когда он получает Сертификат определенного субъекта,проверю это Сертификатлииз确是Зависит оторган, выдавший документпроблемаиз
После проверки подлинности Сертификата он также попросит получателя Сертификата подписать данные, а затем сторонний проверяющий проверит подпись.
"""
def __init__(self,
awarded_pub_key: RSAPublicKey,
awarded_sign_func: Callable[[bytes], Dict[str, Any]],
issuer_name: str,
issuer_pub_key: RSAPublicKey,
issuer_hash_alg: hashes.HashAlgorithm):
# Собственный открытый сторонний валидатор ключ и закрытый ключ
self.__verifier_pub_key, self.__verifier_pri_key = rsa_base.generate_rsa_keypair()
# третья сторонапроверять ВОЗ在实例化час Уже зарегистрированорган, выдавший документиз:открытый ключ, имя, алгоритм хеширования
self.__issuer_pub_key = issuer_pub_key
self.__issuer_name = issuer_name
self.__issuer_hash_alg = issuer_hash_alg
# Сторонний верификатор зарегистрировал открытый верификатор при создании экземпляра. ключ со подписанными указателями на функции
self.__awarded_pub_key = awarded_pub_key
self.__awarded_sign_func = awarded_sign_func
def __verify_cert_signature(self, verified_cert_data: bytes, signature: bytes) -> bool:
"""
Убедитесь, что Сертификат соответствует подписи.
Если он соответствует, это означает только то, что сам Сертификат верен, но лицо, владеющее Сертификатом, не обязательно является правдивым.
Args:
verified_cert_data: Обычные текстовые данные, подлежащие проверке
signature: Значение подписи, которое необходимо проверить
Returns: Проверка подписи прошла успешно?
"""
return rsa_sign.verify_raw_message(pub_key = self.__issuer_pub_key,
message = verified_cert_data,
signature = signature,
hash_alg = self.__issuer_hash_alg)
def __verify_cert_content(self, cert_dict: Dict[str, Any]) -> bool:
"""
Убедитесь, что содержание Сертификата соответствует ожиданиям.
еслицентр сертификациии Сертификатоткрытый ключ одинаковы, это может означать только то, что Сертификат такой же, как этот открытый привязанный к ключу
Однако, придерживаясь этого открытого Ключ человека не обязательно имеет закрытый ключ, поэтому ему все равно необходимо подписать с помощью закрытого ключа, а затем проверяющий проверяет подпись.
Args:
cert_dict: Исходное содержание сертификата
Returns: Соответствует ли содержание Сертификата ожиданиям?
"""
if cert_dict[ISSUER_NAME_KEY] != self.__issuer_name:
"""
проверять Сертификатсерединаизорган, выдавший документимяипроверять ВОЗ持有изорган, выдавший Последовательно ли документирование?
"""
print("issuer name not match:{} vs {}".format(cert_dict[ISSUER_NAME_KEY], self.__issuer_name))
return False
if cert_dict[AWARDED_PUB_KEY] != pub_key_base64_encode(self.__awarded_pub_key):
"""
Проверьте эмитента в Сертификатеоткрытый ключ и открытый получены проверяющим ключ Это соответствует?
"""
print("awarded pub key not match")
return False
return True
def verify_cert(self, cert_data: bytes) -> bool:
"""
Убедитесь, что Сертификат выдан эмитентом.
Убедитесь, что Сертификат был выдан эмитентом лицу, предоставившему право.
"""
try:
cert_dict, signature = mock_parse_cert(cert_data, self.__issuer_pub_key.key_size // 8)
except ValueError as e:
# Если Сертификат не удается проанализировать, об ошибке будет сообщено напрямую.
print(e)
return False
else:
# Если Сертификат проанализирован успешно, подпись будет проверена.
# cert_dict[HASH_ALG_KEY] = json.loads(cert_dict[HASH_ALG_KEY])
verified_cert_data = json.dumps(cert_dict).encode(encoding = 'utf-8')
if not self.__verify_cert_signature(verified_cert_data = verified_cert_data,
signature = signature):
return False
if not self.__verify_cert_content(cert_dict = cert_dict):
return False
return True
def verify_awarded(self) -> bool:
"""
Returns: Верификатор генерирует случайное число, просит его подписать, а затем использует проверенный зарегистрированный открытый код. ключ Проверка """
random_data: bytes = bytes([random.randint(1, 255) for _ in range(64)])
sign_dict = self.__awarded_sign_func(random_data)
signature: bytes = sign_dict[SIGNATURE_KEY]
hash_alg: hashes.HashAlgorithm = sign_dict[HASH_ALG_KEY]
return rsa_sign.verify_raw_message(self.__awarded_pub_key, random_data, signature, hash_alg)
if __name__ == '__main__':
issuer = MockIssuer('test_issuer', hashes.SHA256())
awarded = MockAwarded('test_awarded')
awarded.awarded_cert = issuer.mock_issue_cert('cert for {}'.format(awarded.name), awarded.pub_key)
verifier = MockVerifier(awarded.pub_key,
awarded.sign_data_for_verifier,
issuer.issuer_name,
issuer.pub_key,
issuer.issuer_hash)
if verifier.verify_cert(awarded.awarded_cert) and verifier.verify_awarded():
print("certification verified success!!!")
Сертификат подобен удостоверению личности, подтверждающему «привязку открытого ключа к определенной личности».
Идентификация на основе асимметричного ключа проверки — это основа безопасности всех наших сетевых приложений.