Криптография — наука о методах защиты информации путём её преобразования.
Основные задачи
- Конфиденциальность — защита от несанкционированного доступа
- Целостность — обеспечение неизменности данных
- Аутентификация — подтверждение подлинности отправителя
- Неотказуемость — невозможность отрицания авторства
История
- 1900 до н.э. — египетские иероглифы
- 100-44 до н.э. — Шифр Цезаря
- 1467 — Шифр Альберти
- 1976 — Публикация Диффи-Хеллмана
- 1977 — RSA
- 2001 — AES становится стандартом
Основные понятия
Терминология
- Открытый текст (Plaintext) — исходное сообщение
- Шифртекст (Ciphertext) — зашифрованное сообщение
- Ключ (Key) — секретная информация
- Шифрование (Encryption) — преобразование открытого текста
- Дешифрование (Decryption) — обратное преобразование
Принцип Керкгоффса
Безопасность должна основываться на секретности ключа, а не алгоритма
Виды шифрования
Криптография
├── Симметричная (один ключ)
│ ├── Потоковые шифры
│ └── Блочные шифры
├── Асимметричная (пара ключей)
│ ├── RSA
│ ├── ECC
│ └── ElGamal
└── Гибридная
└── TLS, PGP, SSH
Классическая криптография
1. Шифр Цезаря
Простейший шифр подстановки со сдвигом.
Принцип:
HELLO → (сдвиг 3) → KHOOR
Математика:
Шифрование: C = (P + k) mod 26
Дешифрование: P = (C - k) mod 26
Код на Python:
def caesar_encrypt(text, shift):
result = ""
for char in text:
if char.isalpha():
base = ord('A') if char.isupper() else ord('a')
shifted = (ord(char) - base + shift) % 26
result += chr(base + shifted)
else:
result += char
return result
def caesar_decrypt(text, shift):
return caesar_encrypt(text, -shift)
# Пример
plaintext = "HELLO WORLD"
encrypted = caesar_encrypt(plaintext, 3)
print(f"Зашифровано: {encrypted}") # KHOOR ZRUOG
print(f"Расшифровано: {caesar_decrypt(encrypted, 3)}")
Взлом (brute force):
def caesar_crack(ciphertext):
for shift in range(26):
print(f"Ключ {shift:2d}: {caesar_decrypt(ciphertext, shift)}")
caesar_crack("KHOOR ZRUOG")
2. Шифр Виженера
Многоалфавитный шифр.
Принцип:
Текст: HELLO WORLD
Ключ: KEYKE YKEYK
Результ: RIJVS UYVJN
Реализация:
def vigenere_encrypt(plaintext, key):
result = ""
key_index = 0
key = key.upper()
for char in plaintext.upper():
if char.isalpha():
shift = ord(key[key_index % len(key)]) - ord('A')
encrypted = chr((ord(char) - ord('A') + shift) % 26 + ord('A'))
result += encrypted
key_index += 1
else:
result += char
return result
def vigenere_decrypt(ciphertext, key):
result = ""
key_index = 0
key = key.upper()
for char in ciphertext.upper():
if char.isalpha():
shift = ord(key[key_index % len(key)]) - ord('A')
decrypted = chr((ord(char) - ord('A') - shift) % 26 + ord('A'))
result += decrypted
key_index += 1
else:
result += char
return result
# Пример
text = "HELLO WORLD"
key = "KEY"
encrypted = vigenere_encrypt(text, key)
print(f"Зашифровано: {encrypted}")
print(f"Расшифровано: {vigenere_decrypt(encrypted, key)}")
Симметричное шифрование
Принцип
Один ключ для шифрования и дешифрования.
AES (Advanced Encryption Standard)
Характеристики:
- Размер блока: 128 бит
- Длины ключей: 128, 192, 256 бит
- Текущий стандарт
Режимы работы:
- ECB — НЕ ИСПОЛЬЗОВАТЬ (небезопасен)
- CBC — Cipher Block Chaining
- CTR — Counter mode
- GCM — с аутентификацией (рекомендуется)
Пример AES-GCM:
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
import base64
def aes_encrypt(plaintext, key=None):
"""Шифрование AES-GCM"""
if key is None:
key = get_random_bytes(32) # AES-256
cipher = AES.new(key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode())
return {
'key': base64.b64encode(key).decode(),
'nonce': base64.b64encode(cipher.nonce).decode(),
'tag': base64.b64encode(tag).decode(),
'ciphertext': base64.b64encode(ciphertext).decode()
}
def aes_decrypt(enc_dict):
"""Дешифрование AES-GCM"""
key = base64.b64decode(enc_dict['key'])
nonce = base64.b64decode(enc_dict['nonce'])
tag = base64.b64decode(enc_dict['tag'])
ciphertext = base64.b64decode(enc_dict['ciphertext'])
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
return plaintext.decode()
# Пример
message = "Секретное сообщение"
encrypted = aes_encrypt(message)
decrypted = aes_decrypt(encrypted)
print(f"Результат: {decrypted}")
ChaCha20-Poly1305
Современная альтернатива AES.
from Crypto.Cipher import ChaCha20_Poly1305
def chacha20_encrypt(plaintext):
key = get_random_bytes(32)
cipher = ChaCha20_Poly1305.new(key=key)
ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode())
return {
'key': key,
'nonce': cipher.nonce,
'tag': tag,
'ciphertext': ciphertext
}
def chacha20_decrypt(enc_dict):
cipher = ChaCha20_Poly1305.new(
key=enc_dict['key'],
nonce=enc_dict['nonce']
)
plaintext = cipher.decrypt_and_verify(
enc_dict['ciphertext'],
enc_dict['tag']
)
return plaintext.decode()
Асимметричное шифрование
Принцип
Два ключа: публичный (для шифрования) и приватный (для дешифрования).
RSA
Математическая основа: Факторизация больших чисел.
Простой пример:
def simple_rsa():
# Маленькие числа для демонстрации
p, q = 61, 53
n = p * q # 3233
phi = (p-1) * (q-1) # 3120
e = 17 # Публичная экспонента
d = pow(e, -1, phi) # 2753 - приватная экспонента
print(f"Публичный ключ: (n={n}, e={e})")
print(f"Приватный ключ: (n={n}, d={d})")
# Шифрование
message = 123
ciphertext = pow(message, e, n)
print(f"Зашифровано: {ciphertext}")
# Дешифрование
decrypted = pow(ciphertext, d, n)
print(f"Расшифровано: {decrypted}")
simple_rsa()
Практическое использование:
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
# Генерация ключей
key = RSA.generate(2048)
private_key = key.export_key()
public_key = key.publickey().export_key()
# Шифрование
def rsa_encrypt(message, public_key_pem):
public_key = RSA.import_key(public_key_pem)
cipher = PKCS1_OAEP.new(public_key)
return cipher.encrypt(message)
# Дешифрование
def rsa_decrypt(ciphertext, private_key_pem):
private_key = RSA.import_key(private_key_pem)
cipher = PKCS1_OAEP.new(private_key)
return cipher.decrypt(ciphertext)
# Пример
message = b"Secret message"
encrypted = rsa_encrypt(message, public_key)
decrypted = rsa_decrypt(encrypted, private_key)
print(f"Расшифровано: {decrypted.decode()}")
Эллиптические кривые (ECC)
Более эффективная альтернатива RSA.
Сравнение безопасности:
| RSA | ECC | Соотношение |
|---|---|---|
| 1024 бит | 160 бит | 6.4:1 |
| 2048 бит | 224 бит | 9.1:1 |
| 3072 бит | 256 бит | 12:1 |
ECDH (обмен ключами):
from Crypto.PublicKey import ECC
# Алиса
alice_key = ECC.generate(curve='P-256')
alice_public = alice_key.public_key()
# Боб
bob_key = ECC.generate(curve='P-256')
bob_public = bob_key.public_key()
# Алиса вычисляет общий секрет
alice_shared = alice_key.d * bob_public.pointQ
# Боб вычисляет общий секрет
bob_shared = bob_key.d * alice_public.pointQ
# Секреты одинаковы!
print(alice_shared.x == bob_shared.x) # True
Хеш-функции
Определение
Функция, преобразующая данные произвольного размера в фиксированный хеш.
Свойства
- Детерминированность — один вход = один хеш
- Быстрое вычисление
- Эффект лавины — малое изменение → большое изменение хеша
- Необратимость
- Стойкость к коллизиям
Примеры
import hashlib
message = b"Hello World"
# MD5 (устарел, НЕ ИСПОЛЬЗОВАТЬ для безопасности)
md5 = hashlib.md5(message).hexdigest()
print(f"MD5: {md5}")
# SHA-256 (рекомендуется)
sha256 = hashlib.sha256(message).hexdigest()
print(f"SHA-256: {sha256}")
# SHA-512
sha512 = hashlib.sha512(message).hexdigest()
print(f"SHA-512: {sha512}")
# SHA-3
sha3 = hashlib.sha3_256(message).hexdigest()
print(f"SHA3-256: {sha3}")
# BLAKE2
blake2 = hashlib.blake2b(message).hexdigest()
print(f"BLAKE2b: {blake2}")
Проверка целостности файла
def file_hash(filename, algorithm='sha256'):
h = hashlib.new(algorithm)
with open(filename, 'rb') as f:
while chunk := f.read(8192):
h.update(chunk)
return h.hexdigest()
# Использование
# hash_value = file_hash('document.pdf')
# print(f"SHA-256: {hash_value}")
HMAC (аутентификация сообщений)
import hmac
key = b"secret_key"
message = b"Important message"
# Создание HMAC
h = hmac.new(key, message, hashlib.sha256)
mac = h.hexdigest()
# Проверка
h_verify = hmac.new(key, message, hashlib.sha256)
if hmac.compare_digest(h_verify.hexdigest(), mac):
print("✓ HMAC верен")
Password Hashing
НИКОГДА не используйте обычные хеш-функции для паролей!
Правильно — Argon2:
from argon2 import PasswordHasher
ph = PasswordHasher()
# Хеширование
password = "my_secure_password"
hashed = ph.hash(password)
print(f"Хеш: {hashed}")
# Проверка
try:
ph.verify(hashed, password)
print("✓ Пароль верен")
except:
print("✗ Пароль неверен")
Также хорошо — bcrypt:
import bcrypt
password = b"my_password"
# Хеширование
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))
# Проверка
if bcrypt.checkpw(password, hashed):
print("✓ Пароль верен")
Цифровые подписи
Принцип
Создание:
Message → [Hash] → [Sign with Private Key] → Signature
Проверка:
Message → [Hash] → Digest₁
Signature → [Verify with Public Key] → Digest₂
Digest₁ == Digest₂ ?
RSA подпись
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
# Генерация ключей
key = RSA.generate(2048)
public_key = key.publickey()
# Создание подписи
message = b"Contract: Pay $1000"
h = SHA256.new(message)
signature = pkcs1_15.new(key).sign(h)
# Проверка
h = SHA256.new(message)
try:
pkcs1_15.new(public_key).verify(h, signature)
print("✓ Подпись верна")
except:
print("✗ Подпись неверна")
Ed25519 (современная)
from Crypto.PublicKey import ECC
from Crypto.Signature import eddsa
# Генерация ключей
key = ECC.generate(curve='Ed25519')
public_key = key.public_key()
# Подпись
message = b"Message to sign"
signer = eddsa.new(key, 'rfc8032')
signature = signer.sign(message)
# Проверка
verifier = eddsa.new(public_key, 'rfc8032')
try:
verifier.verify(message, signature)
print("✓ Подпись верна")
except:
print("✗ Подпись неверна")
Сертификаты и PKI
X.509 сертификат
Стандартный формат цифрового сертификата.
Создание самоподписанного сертификата
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from datetime import datetime, timedelta
# Генерация ключа
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048
)
# Информация о субъекте
subject = issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, "US"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, "Example Inc"),
x509.NameAttribute(NameOID.COMMON_NAME, "example.com"),
])
# Создание сертификата
cert = x509.CertificateBuilder().subject_name(
subject
).issuer_name(
issuer
).public_key(
private_key.public_key()
).serial_number(
x509.random_serial_number()
).not_valid_before(
datetime.utcnow()
).not_valid_after(
datetime.utcnow() + timedelta(days=365)
).add_extension(
x509.SubjectAlternativeName([
x509.DNSName("example.com"),
x509.DNSName("www.example.com"),
]),
critical=False
).sign(private_key, hashes.SHA256())
# Сохранение
with open("cert.pem", "wb") as f:
f.write(cert.public_bytes(serialization.Encoding.PEM))
with open("key.pem", "wb") as f:
f.write(private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption()
))
Практическое применение
1. Шифрование файлов
from Crypto.Protocol.KDF import scrypt
import os
class FileEncryptor:
def __init__(self, password):
self.salt = os.urandom(32)
self.key = scrypt(password, self.salt, 32, N=2**14, r=8, p=1)
def encrypt_file(self, input_file, output_file):
# Чтение файла
with open(input_file, 'rb') as f:
plaintext = f.read()
# Шифрование
cipher = AES.new(self.key, AES.MODE_GCM)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)
# Сохранение
with open(output_file, 'wb') as f:
f.write(self.salt)
f.write(cipher.nonce)
f.write(tag)
f.write(ciphertext)
def decrypt_file(self, input_file, output_file, password):
with open(input_file, 'rb') as f:
salt = f.read(32)
nonce = f.read(16)
tag = f.read(16)
ciphertext = f.read()
key = scrypt(password, salt, 32, N=2**14, r=8, p=1)
cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
try:
plaintext = cipher.decrypt_and_verify(ciphertext, tag)
with open(output_file, 'wb') as f:
f.write(plaintext)
print("✓ Файл расшифрован")
except:
print("✗ Неверный пароль")
2. Генератор паролей
import secrets
import string
def generate_password(length=16, use_symbols=True):
charset = string.ascii_letters + string.digits
if use_symbols:
charset += string.punctuation
return ''.join(secrets.choice(charset) for _ in range(length))
def generate_passphrase(words=5):
wordlist = ['correct', 'horse', 'battery', 'staple', 'mountain']
return '-'.join(secrets.choice(wordlist) for _ in range(words))
# Примеры
print(f"Пароль: {generate_password(20)}")
print(f"Фраза: {generate_passphrase(5)}")
3. Двухфакторная аутентификация (TOTP)
import pyotp
# Генерация секрета
secret = pyotp.random_base32()
print(f"Секрет: {secret}")
# Создание TOTP
totp = pyotp.TOTP(secret)
# Получение текущего кода
current_code = totp.now()
print(f"Текущий код: {current_code}")
# Проверка кода
if totp.verify(current_code):
print("✓ Код верен")
Криптоанализ
Типы атак
1. Brute Force
Перебор всех возможных ключей.
# Время взлома
def time_to_crack(key_bits, ops_per_sec=1e12):
combinations = 2 ** key_bits
seconds = combinations / ops_per_sec
years = seconds / (365.25 * 24 * 3600)
print(f"Ключ {key_bits} бит: {years:.2e} лет")
time_to_crack(56) # DES - ~20 часов
time_to_crack(128) # AES-128 - миллиарды лет
time_to_crack(256) # AES-256 - практически невозможно
2. Частотный анализ
Для взлома подстановочных шифров.
from collections import Counter
def frequency_analysis(text):
clean = ''.join(c for c in text.upper() if c.isalpha())
freq = Counter(clean)
for letter, count in freq.most_common(10):
print(f"{letter}: {count}")
3. Timing Attack
import hmac
# ❌ УЯЗВИМО
def bad_compare(a, b):
if len(a) != len(b):
return False
for x, y in zip(a, b):
if x != y:
return False # Выход при первом несовпадении
return True
# ✅ БЕЗОПАСНО (constant-time)
def safe_compare(a, b):
return hmac.compare_digest(a, b)
Best Practices
✅ Делайте
-
Используйте проверенные библиотеки
- PyCryptodome, cryptography
- НЕ пишите свои алгоритмы
-
Современные алгоритмы
- AES-256, ChaCha20
- SHA-256, SHA-3, BLAKE2
- Ed25519, RSA-2048+
-
AEAD режимы
- AES-GCM вместо AES-CBC
- ChaCha20-Poly1305
-
Криптографическая случайность
import secrets key = secrets.token_bytes(32) # ✅ import random key = random.randbytes(32) # ❌ -
Argon2/bcrypt для паролей
from argon2 import PasswordHasher ph = PasswordHasher() hashed = ph.hash(password) # ✅ import hashlib hashed = hashlib.sha256(password).hexdigest() # ❌
❌ Не делайте
- НЕ используйте MD5, SHA-1, DES, 3DES
- НЕ используйте ECB режим
- НЕ храните ключи в коде
- НЕ используйте обычные хеши для паролей
- НЕ изобретайте свою криптографию
Полезные ресурсы
Библиотеки
- PyCryptodome — https://pycryptodome.readthedocs.io/
- cryptography — https://cryptography.io/
- PyNaCl — https://pynacl.readthedocs.io/
- argon2-cffi — для паролей
Обучение
- Cryptopals — https://cryptopals.com/
- Crypto101 — https://www.crypto101.io/
- Coursera Cryptography — онлайн курс
- Khan Academy — основы
Стандарты
- NIST — https://csrc.nist.gov/
- OWASP — криптографические рекомендации
- RFC — протоколы
Инструменты
- OpenSSL — Swiss Army knife
- GnuPG — PGP реализация
- Hashcat — password recovery
- John the Ripper — password cracker
Квантовая криптография
Угроза квантовых компьютеров
Алгоритм Шора может взломать:
- ❌ RSA
- ❌ ECC
- ❌ Диффи-Хеллман
Относительно безопасны:
- ✅ AES (удвоить длину ключа)
- ✅ SHA-2/SHA-3
Post-Quantum алгоритмы (NIST 2024)
- CRYSTALS-Kyber — обмен ключами
- CRYSTALS-Dilithium — подписи
- FALCON — подписи
- SPHINCS+ — подписи
QKD (Квантовое распределение ключей)
BB84 протокол — физически безопасный обмен ключами.
Принцип:
- Использует квантовые свойства фотонов
- Любой перехват обнаруживается
- Теоретически абсолютно безопасен
Заключение
Ключевые выводы
- ✅ Используйте проверенные алгоритмы и библиотеки
- ✅ Следуйте best practices
- ✅ Обновляйте знания
- ✅ Тестируйте безопасность
- ✅ Готовьтесь к квантовой эре
Помните
"Шифрование работает. Правильно реализованные криптосистемы — это одна из немногих вещей, на которые вы можете положиться."
— Эдвард Сноуден