GLI ALGORITMI (Spiegati da Mommy)
1. ECC (Elliptic Curve Cryptography) - A chi non piacciono quelle curvose?
Immagina una curva perfetta. Matematica pura. Tu scegli un punto segreto. Nessuno può sapere dov'è. Ma puoi dare a tutti un punto pubblico.
- Chiave Privata: É un po' come la tua mommy, se qualcun altro c'è l'ha sei fottuto.
- Chiave Pubblica: Questo è il tuo home boy, dallo a chiunque.
Usiamo P-256. Standard, solida, e veloce come me quando devo tornare a casa per guardare i mommy ASMR. Molto meglio di quelle vecchie chiavi RSA enormi che usavi prima(lo so che compensavano ma se ci pensi forse ora c'è lo hai più lungo della chaive 😉 ). Le dimensioni contano, ma l'efficienza conta di più(quindi non ti buttare giù hai ancora una chance).
2. AES-GCM - Il tuo forte e succulente homeboy
Una volta che hai scambiato i segreti con le curve (ECDH), usiamo AES. AES è un po' come il tuo home boy, ti protegge dalle gorlock the destroyer(quelli stronzi che chiamiamo hacker).
- Key (32 byte): Quanto succulenti e grosse sono le sue cosce... ugh, ugh... volevo dire muscoli, sì muscoli.
- GCM (Galois/Counter Mode): È un po' come se lo marcassi in... modi creativi. Se qualcun altro prova a toccarlo? DENIED. 🚫 Mommy sa sempre se hai toccato quello che non dovevi.
3. [RSA-OAEP] - Se ti piacciono quelle più vecchie
A volte hai bisogno dei classici basta le mommy, e i femboy. RSA è grande, grosso e cattivo( come piace a te 😃 ).
- OAEP: Prendi una gabbia di castità e mettigliela, la gente verrà bloccata se prova a toccarla.
Esempi carini:
AES-GCM
import os
import secrets
s_box = (
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)
r_con = (
0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)
def xtime(a):
return (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1)
def mix_single_column(a):
t = a[0] ^ a[1] ^ a[2] ^ a[3]
u = a[0]
a[0] ^= t ^ xtime(a[0] ^ a[1])
a[1] ^= t ^ xtime(a[1] ^ a[2])
a[2] ^= t ^ xtime(a[2] ^ a[3])
a[3] ^= t ^ xtime(a[3] ^ u)
class AES:
def __init__(self, key=None):
if key is None:
self.key = secrets.token_bytes(32)
else:
if not isinstance(key, bytes) or len(key) != 32:
raise ValueError
self.key = key
self.n_rounds = {16: 10, 24: 12, 32: 14}[len(self.key)]
self._key_matrices = self._expand_key(self.key)
def _expand_key(self, master_key):
key_columns = [list(master_key[i:i+4]) for i in range(0, len(master_key), 4)]
i = 1
while len(key_columns) < (self.n_rounds + 1) * 4:
word = list(key_columns[-1])
if len(key_columns) % (len(master_key) // 4) == 0:
word.append(word.pop(0))
word = [s_box[b] for b in word]
word[0] ^= r_con[i]
i += 1
elif len(master_key) == 32 and len(key_columns) % (len(master_key) // 4) == 4:
word = [s_box[b] for b in word]
word = [i ^ j for i, j in zip(word, key_columns[-len(master_key) // 4])]
key_columns.append(word)
return [key_columns[4*i : 4*(i+1)] for i in range(self.n_rounds + 1)]
def _encrypt_block(self, plaintext):
state = [list(plaintext[i:i+4]) for i in range(0, 16, 4)]
self._add_round_key(state, self._key_matrices[0])
for i in range(1, self.n_rounds):
self._sub_bytes(state)
self._shift_rows(state)
self._mix_columns(state)
self._add_round_key(state, self._key_matrices[i])
self._sub_bytes(state)
self._shift_rows(state)
self._add_round_key(state, self._key_matrices[-1])
return bytes(sum(state, []))
def _sub_bytes(self, state):
for i in range(4):
for j in range(4):
state[i][j] = s_box[state[i][j]]
def _shift_rows(self, state):
state[0][1], state[1][1], state[2][1], state[3][1] = state[1][1], state[2][1], state[3][1], state[0][1]
state[0][2], state[1][2], state[2][2], state[3][2] = state[2][2], state[3][2], state[0][2], state[1][2]
state[0][3], state[1][3], state[2][3], state[3][3] = state[3][3], state[0][3], state[1][3], state[2][3]
def _mix_columns(self, s):
for i in range(4):
mix_single_column(s[i])
def _add_round_key(self, state, key_matrix):
for i in range(4):
for j in range(4):
state[i][j] ^= key_matrix[i][j]
def gmul(self, x, y):
r = 0
for i in range(128):
if (y >> (127 - i)) & 1:
r ^= x
if x & 1:
x = (x >> 1) ^ 0xE1000000000000000000000000000000
else:
x >>= 1
return r
def ghash(self, h, data):
y = 0
for i in range(0, len(data), 16):
block = int.from_bytes(data[i:i+16].ljust(16, b'\x00'), 'big')
y ^= block
y = self.gmul(y, h)
return y
def _inc_32(self, counter_block):
counter_int = int.from_bytes(counter_block[12:], 'big')
counter_int = (counter_int + 1) & 0xFFFFFFFF
return counter_block[:12] + counter_int.to_bytes(4, 'big')
def encrypt(self, plaintext, associated_data=b''):
if not isinstance(plaintext, bytes) or not isinstance(associated_data, bytes):
raise TypeError
iv = os.urandom(12)
h = int.from_bytes(self._encrypt_block(b'\x00' * 16), 'big')
j0 = iv + b'\x00\x00\x00\x01'
c = bytearray()
counter_block = self._inc_32(j0)
for i in range(0, len(plaintext), 16):
enc_cb = self._encrypt_block(counter_block)
chunk = plaintext[i:i+16]
c.extend(bytes(a ^ b for a, b in zip(chunk, enc_cb)))
counter_block = self._inc_32(counter_block)
len_a = len(associated_data) * 8
len_c = len(c) * 8
auth_data = associated_data + b'\x00' * ((16 - len(associated_data) % 16) % 16)
auth_data += c + b'\x00' * ((16 - len(c) % 16) % 16)
auth_data += len_a.to_bytes(8, 'big') + len_c.to_bytes(8, 'big')
s = self.ghash(h, auth_data)
t = s ^ int.from_bytes(self._encrypt_block(j0), 'big')
return iv + c + t.to_bytes(16, 'big')
def decrypt(self, ciphertext, associated_data=b''):
if len(ciphertext) < 28:
raise ValueError
iv = ciphertext[:12]
c = ciphertext[12:-16]
tag = ciphertext[-16:]
h = int.from_bytes(self._encrypt_block(b'\x00' * 16), 'big')
j0 = iv + b'\x00\x00\x00\x01'
len_a = len(associated_data) * 8
len_c = len(c) * 8
auth_data = associated_data + b'\x00' * ((16 - len(associated_data) % 16) % 16)
auth_data += c + b'\x00' * ((16 - len(c) % 16) % 16)
auth_data += len_a.to_bytes(8, 'big') + len_c.to_bytes(8, 'big')
s = self.ghash(h, auth_data)
t = s ^ int.from_bytes(self._encrypt_block(j0), 'big')
if t.to_bytes(16, 'big') != tag:
raise ValueError
p = bytearray()
counter_block = self._inc_32(j0)
for i in range(0, len(c), 16):
enc_cb = self._encrypt_block(counter_block)
chunk = c[i:i+16]
p.extend(bytes(a ^ b for a, b in zip(chunk, enc_cb)))
counter_block = self._inc_32(counter_block)
return bytes(p)RSA
from functions import generate_large_prime
import base64
from OAEP import OAEP
import math
class RSA:
def __init__(self, bits=4096):
if bits < 2048:
raise ValueError
self.e = 65537
while True:
self.p = generate_large_prime(bits)
self.q = generate_large_prime(bits)
if self.p == self.q:
continue
self.n = self.p * self.q
self.phi = (self.p - 1) * (self.q - 1)
if math.gcd(self.e, self.phi) == 1:
break
self.d = pow(self.e, -1, self.phi)
self.dp = self.d % (self.p - 1)
self.dq = self.d % (self.q - 1)
self.qinv = pow(self.q, -1, self.p)
self.oaep = OAEP(self.n)
self.k = self.oaep.k
def encrypt(self, text) -> str:
if isinstance(text, str):
m = text.encode("utf-8")
elif isinstance(text, bytes):
m = text
else:
raise TypeError
em = self.oaep.encode(m)
m_int = int.from_bytes(em, "big")
if m_int >= self.n:
raise ValueError
c_int = pow(m_int, self.e, self.n)
return base64.b64encode(c_int.to_bytes(self.k, "big")).decode("utf-8")
def decrypt(self, c_b64: str) -> str:
if not isinstance(c_b64, str):
raise TypeError
c_bytes = base64.b64decode(c_b64)
if len(c_bytes) != self.k:
raise ValueError
c_int = int.from_bytes(c_bytes, "big")
m1 = pow(c_int, self.dp, self.p)
m2 = pow(c_int, self.dq, self.q)
h = (self.qinv * (m1 - m2)) % self.p
m_int = m2 + h * self.q
em = m_int.to_bytes(self.k, "big")
return self.oaep.decode(em).decode("utf-8")OAEP
import secrets
from hashlib import sha256
class OAEP:
def __init__(self, n: int):
self.k = (n.bit_length() + 7) // 8
self.hash_fn = sha256
self.hLen = self.hash_fn().digest_size
self.label = b""
self.lHash = self.hash_fn(self.label).digest()
def MGF1(self, seed: bytes, mask_len: int) -> bytes:
t = b""
counter = 0
while len(t) < mask_len:
c = counter.to_bytes(4, "big")
t += self.hash_fn(seed + c).digest()
counter += 1
return t[:mask_len]
def encode(self, m: bytes) -> bytes:
mLen = len(m)
if mLen > self.k - 2 * self.hLen - 2:
raise ValueError
ps = b"\x00" * (self.k - mLen - 2 * self.hLen - 2)
db = self.lHash + ps + b"\x01" + m
seed = secrets.token_bytes(self.hLen)
db_mask = self.MGF1(seed, self.k - self.hLen - 1)
masked_db = bytes(x ^ y for x, y in zip(db, db_mask))
seed_mask = self.MGF1(masked_db, self.hLen)
masked_seed = bytes(x ^ y for x, y in zip(seed, seed_mask))
return b"\x00" + masked_seed + masked_db
def decode(self, em: bytes) -> bytes:
if len(em) != self.k:
raise ValueError
y = em[0]
masked_seed = em[1 : 1 + self.hLen]
masked_db = em[1 + self.hLen :]
seed_mask = self.MGF1(masked_db, self.hLen)
seed = bytes(x ^ y for x, y in zip(masked_seed, seed_mask))
db_mask = self.MGF1(seed, self.k - self.hLen - 1)
db = bytes(x ^ y for x, y in zip(masked_db, db_mask))
lHash_check = db[:self.hLen]
valid = 1
if lHash_check != self.lHash:
valid = 0
if y != 0:
valid = 0
index = -1
for i in range(self.hLen, len(db)):
if db[i] == 0x01:
if index == -1:
index = i
elif db[i] != 0x00:
if index == -1:
valid = 0
if index == -1 or valid == 0:
raise ValueError
return db[index + 1:](sì lo so RSA e OAEP sono separati, ma sinceramente avevo 0 sbatti di unirli ma in compenso poi quando ho fatto AES-GCM ho messo tutto insieme)
ma sinceramente tutto questo è una merda, è fatto in python, che da un paio di problemi, ovvero:
[!FAILURE] Side-Channel Attacks Python non garantisce tempi di esecuzione costanti. Un attaccante ultra-sofisticato con accesso fisico potrebbe misurare i nanosecondi di ritardo per intuire la chiave. Mitigazione: Abbiamo evitato branch condizionali segreti dove possibile.
[!FAILURE] Memory Scrubbing Non possiamo cancellare la RAM a comando (Garbage Collector). Mitigazione: I sistemi operativi moderni proteggono la memoria dei processi, ma un Cold Boot Attack rimane teoricamente possibile.
in questo caso, non c'è nulla da fare, perché non posso cancellare la RAM a comando. quindi basta sbattere i coglioni sulla tastiera e riscrivilo in python come il good boy che sei.
Come funziona tutto insieme? rust vs python
- Rust genera le chiavi. (Veloce, sicuro, pulito).
- Python (il nostro good boy) chiama Rust e dice "Mommy, per favore ciframi questo".
- Rust esegue il lavoro sporco in memoria protetta.
- Rust pulisce tutto (
zeroize) e restituisce solo il risultato cifrato. - Python ringrazia e porta il risultato all'utente.
Perfetto equilibrio. (ah btw ho anche il codice Rust ma mi sbatto troppo i coglioni per metterlo quindi boh, fatevelo da soli invece di segarvi) ok, ok farò il gentile e vi darò solo il codice rust per ECC, ma fatto come libreria per python 😃
use p256::{
ecdh::EphemeralSecret,
PublicKey, SecretKey,
};
use rand_core::OsRng;
use pyo3::prelude::*;
use pyo3::types::PyBytes;
use p256::elliptic_curve::sec1::ToEncodedPoint;
fn internal_generate_keypair() -> (SecretKey, PublicKey) {
let secret = SecretKey::random(&mut OsRng);
let public = secret.public_key();
(secret, public)
}
#[pyfunction]
fn generate_keypair_py(py: Python) -> PyResult<(PyObject, PyObject)> {
let (secret, public) = internal_generate_keypair();
let secret_bytes = secret.to_bytes();
let public_bytes = public.to_encoded_point(true);
Ok((
PyBytes::new(py, secret_bytes.as_slice()).into(),
PyBytes::new(py, public_bytes.as_bytes()).into()
))
}
#[pyfunction]
fn derive_shared_secret_py(py: Python, my_secret_bytes: &[u8], other_public_bytes: &[u8]) -> PyResult<PyObject> {
let my_secret = SecretKey::from_slice(my_secret_bytes)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid secret key: {}", e)))?;
let other_pub = PublicKey::from_sec1_bytes(other_public_bytes)
.map_err(|e| PyErr::new::<pyo3::exceptions::PyValueError, _>(format!("Invalid public key: {}", e)))?;
let shared_secret = p256::ecdh::diffie_hellman(
my_secret.to_nonzero_scalar(),
other_pub.as_affine()
);
Ok(PyBytes::new(py, shared_secret.raw_secret_bytes()).into())
}
#[pymodule]
fn ecc_rust(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(generate_keypair_py, m)?)?;
m.add_function(wrap_pyfunction!(derive_shared_secret_py, m)?)?;
Ok(())
}