|
|
@@ -0,0 +1,435 @@ |
|
|
#!/usr/bin/python |
|
|
""" |
|
|
Criptosistema ACYMOS2 por Agustin, Kriptopolis |
|
|
|
|
|
|
|
|
Notas: |
|
|
"~" representa la "enye" |
|
|
Los comentarios estan sin acentos y caracteres especiales |
|
|
|
|
|
|
|
|
by xiscu [en] email [punto] de |
|
|
""" |
|
|
|
|
|
import re |
|
|
from decimal import * |
|
|
from math import * |
|
|
|
|
|
############################################################################### |
|
|
# CONFIGURACION Y CONSTANTES |
|
|
|
|
|
## El alfabeto basico |
|
|
ALFABETO_BASE = list('ABCDEFGHIJKLMN~OPQRSTUVWXYZ_') |
|
|
ALFA_N = len(ALFABETO_BASE) |
|
|
|
|
|
|
|
|
## Traducciones |
|
|
espacio = re.compile(' ') # 1 |
|
|
enye = re.compile('\xc3\x91') # 2 |
|
|
retorno = re.compile('\n') # 3 |
|
|
|
|
|
|
|
|
## Numero de Cifras decimales |
|
|
ND = 10 |
|
|
DIEZ_DECIMALES = Decimal(10) ** -ND |
|
|
|
|
|
|
|
|
## Fichero con numeros primeros |
|
|
FICHERO_PRIMOS = '100000_primos.txt' |
|
|
PRIMOS=[] |
|
|
|
|
|
|
|
|
############################################################################### |
|
|
# FUNCIONES INTERNAS |
|
|
|
|
|
def lee_primos(): |
|
|
f=None |
|
|
try: |
|
|
f=open(FICHERO_PRIMOS) |
|
|
for linea in f: |
|
|
primos_texto=linea.split() |
|
|
for p in primos_texto: |
|
|
PRIMOS.append(int(p)) |
|
|
except: |
|
|
raise |
|
|
|
|
|
finally: |
|
|
if f: |
|
|
f.close() |
|
|
|
|
|
## Lee los primos |
|
|
lee_primos() |
|
|
|
|
|
## Constante PI_DECIMAL |
|
|
PI_DEC = Decimal('3.1415926536') |
|
|
|
|
|
|
|
|
############################################################################### |
|
|
# FUNCIONES EXPORTADAS |
|
|
|
|
|
def texto_a_ascii(texto): |
|
|
"""Traduce un texto a ascii |
|
|
""" |
|
|
tt = espacio.sub('_', texto) # 1 |
|
|
tt = enye.sub('~', tt) # 2 |
|
|
tt = retorno.sub('', tt) # 3 |
|
|
|
|
|
return tt |
|
|
|
|
|
|
|
|
def lan(alfabeto, letras): |
|
|
"""Traduce una o una lista de letras a numeros segun el orden del alfabeto |
|
|
""" |
|
|
index_mas1 = lambda letra: alfabeto.index(letra) + 1 |
|
|
if len(letras)==1: |
|
|
return index_mas1(letras) |
|
|
else: |
|
|
return map(index_mas1, letras) |
|
|
|
|
|
|
|
|
def primo_n(n): |
|
|
"""Retorna el primo numero n, empezando a contar por 0 |
|
|
""" |
|
|
return PRIMOS[n] |
|
|
|
|
|
|
|
|
def s_alfa(clave): |
|
|
"""Calcula la semilla para contruir el alfabeto derivado con la clave dada |
|
|
""" |
|
|
s=Decimal(0) |
|
|
pn=0 |
|
|
for c in clave: |
|
|
n=lan(ALFABETO_BASE, c) |
|
|
s+=n * Decimal(primo_n(pn)).log10() |
|
|
pn+=1 |
|
|
|
|
|
# Desplazamiento |
|
|
d=s.adjusted()+1 |
|
|
s=s/Decimal(10**d) |
|
|
|
|
|
return s.quantize(DIEZ_DECIMALES) |
|
|
|
|
|
|
|
|
def s_cifra(clave): |
|
|
"""Calcula la semilla para contruir el cifrado (Ai) con la clave dada |
|
|
""" |
|
|
s=Decimal(0) |
|
|
alfa_derivado=alfa_k(clave) |
|
|
pn=0 |
|
|
for c in clave: |
|
|
n=lan(alfa_derivado, c) |
|
|
s+=n * Decimal(primo_n(pn)).log10() |
|
|
pn+=1 |
|
|
|
|
|
# Desplazamiento |
|
|
d=s.adjusted()+1 |
|
|
s=s/Decimal(10**d) |
|
|
|
|
|
return s.quantize(DIEZ_DECIMALES) |
|
|
|
|
|
|
|
|
def pseudo(s): |
|
|
"""Calcula el siguiente numero pseudo aleatorio |
|
|
""" |
|
|
resto = divmod((PI_DEC + s)**2, Decimal(1))[1] |
|
|
|
|
|
return resto.quantize(DIEZ_DECIMALES) |
|
|
|
|
|
|
|
|
def fn_a(l, s): |
|
|
"""Funcion A para el calculo de la siguiente letra en el alfabeto |
|
|
derivado |
|
|
""" |
|
|
n = s * (l-1)+1 |
|
|
|
|
|
# Solo la parte entera |
|
|
return int(n.quantize(1)) |
|
|
|
|
|
|
|
|
def alfa_k(clave): |
|
|
"""Calcula el alfabeto derivado para la clave dada |
|
|
""" |
|
|
s = s_alfa(clave) |
|
|
alfa_act = list(ALFABETO_BASE) |
|
|
alfa_derivado = [] |
|
|
for n in range(ALFA_N): |
|
|
# Numero pseudoaleatorio |
|
|
s = pseudo(s) |
|
|
|
|
|
# L (Longitud restante) |
|
|
l = ALFA_N - n |
|
|
|
|
|
# Funcion A (el "-1" es para el siguiente indexado) |
|
|
p = fn_a(l, s) - 1 |
|
|
|
|
|
# Nueva letra |
|
|
alfa_derivado.append(alfa_act[p]) |
|
|
|
|
|
# Resto alfabeto |
|
|
alfa_act = alfa_act[0:p] + alfa_act[p+1::] |
|
|
|
|
|
return alfa_derivado |
|
|
|
|
|
|
|
|
|
|
|
def ldist(letra, pos): |
|
|
"""Toma una letra a distancia "x" del alfabeto base |
|
|
""" |
|
|
nueva_letra = (ALFABETO_BASE.index(letra) + pos) % NUM_LETRAS |
|
|
|
|
|
return ALFABETO_BASE[nueva_letra] |
|
|
|
|
|
|
|
|
def codifica(clave, texto, formato_s='t'): |
|
|
"""Codifica un texto con la clave |
|
|
|
|
|
Uso: codifica(clave, texto, formato) |
|
|
Donde los parametros son: |
|
|
- clave: clave usada para cifrar (en mayusculas) |
|
|
- texto: texto a cifrar (en mayusculas y ya traducido) |
|
|
- formato_s: formato de salida. 't' texto (por defecto) y 'n' numerico |
|
|
""" |
|
|
|
|
|
# Alfabeto derivado |
|
|
alfa_d = alfa_k(clave) |
|
|
|
|
|
# Tamano de la clave |
|
|
clave_n = len(clave) |
|
|
|
|
|
# Posiciones de las letras de la clave en el alfabeto derivado |
|
|
clave_ki = lan(alfa_d, clave) |
|
|
|
|
|
# Semilla aleatoria para la el calculo del cifrado |
|
|
s0 = s_cifra(clave) |
|
|
|
|
|
# Cifrado |
|
|
cifra = [] |
|
|
sa = s0 |
|
|
for n, ca in enumerate(texto): |
|
|
sa = pseudo(sa) |
|
|
a = fn_a(ALFA_N, sa) |
|
|
k = clave_ki[n % clave_n] |
|
|
p = lan(alfa_d, ca) |
|
|
c = (a + p + k) % ALFA_N |
|
|
if c == 0: |
|
|
c = ALFA_N |
|
|
|
|
|
cifra.append(c) |
|
|
|
|
|
# Formato de salida en texto ? |
|
|
if formato_s == 't': |
|
|
pos_menos1 = lambda pos: alfa_d.__getitem__(pos-1) |
|
|
return ''.join(map(pos_menos1, cifra)) |
|
|
|
|
|
return cifra |
|
|
|
|
|
|
|
|
def decodifica(clave, texto, formato_s='t'): |
|
|
""" Decodifica un texto con la clave |
|
|
|
|
|
Uso: decodifica(clave, texto, formato) |
|
|
Donde los parametros son: |
|
|
- clave: clave para descifrar (en mayusculas) |
|
|
- texto: texto a descifrar (en mayusculas) |
|
|
- formato_s: formato de salida. 't' texto (por defecto) y 'n' numerico |
|
|
""" |
|
|
|
|
|
# Alfabeto derivado |
|
|
alfa_d = alfa_k(clave) |
|
|
|
|
|
# Tamano de la clave |
|
|
clave_n = len(clave) |
|
|
|
|
|
# Posiciones de las letras de la clave en el alfabeto derivado |
|
|
clave_ki = lan(alfa_d, clave) |
|
|
|
|
|
# Semilla aleatoria para la el calculo del cifrado |
|
|
s0 = s_cifra(clave) |
|
|
|
|
|
# Cifrado |
|
|
claro = '' |
|
|
sa = s0 |
|
|
for n, ca in enumerate(texto): |
|
|
sa = pseudo(sa) |
|
|
a = fn_a(ALFA_N, sa) |
|
|
k = clave_ki[n % clave_n] |
|
|
c = lan(alfa_d, ca) |
|
|
p = (c - a - k) % ALFA_N |
|
|
p = p % ALFA_N |
|
|
if p == 0: |
|
|
p = ALFA_N |
|
|
|
|
|
claro += alfa_d[p-1] |
|
|
|
|
|
# Formato de salida numerico ? |
|
|
if formato_s == 'n': |
|
|
index_mas1 = lambda pos: ALFABETO_BASE.index(pos) + 1 |
|
|
claro = map(index_mas1, claro) |
|
|
|
|
|
return claro |
|
|
|
|
|
|
|
|
############################################################################### |
|
|
# Ejecucion directa del modulo: Ejecuta los tests |
|
|
# |
|
|
if __name__ == '__main__' : |
|
|
|
|
|
import unittest |
|
|
|
|
|
########################################################################### |
|
|
# Definicion de los tests |
|
|
class TestACYNOS2(unittest.TestCase): |
|
|
|
|
|
def test_letra_a_numero(self): |
|
|
self.assertEqual(lan('ABC', 'A'), 1) |
|
|
self.assertEqual(lan('ABC', 'C'), 3) |
|
|
self.assertRaises(ValueError, lan, 'ABC', 'D') |
|
|
|
|
|
|
|
|
def test_traduccion(self): |
|
|
self.assertEqual(texto_a_ascii('ABCDE'), 'ABCDE') |
|
|
self.assertEqual(texto_a_ascii(''), '') |
|
|
self.assertEqual(texto_a_ascii('\xc3\x91'), '~') |
|
|
self.assertEqual(texto_a_ascii(' '), '_') |
|
|
self.assertEqual(texto_a_ascii('\n'), '') |
|
|
|
|
|
|
|
|
def test_lee_primos(self): |
|
|
self.assertEqual(primo_n(0), 2) |
|
|
self.assertEqual(primo_n(1), 3) |
|
|
self.assertEqual(primo_n(100007), 1299827) |
|
|
|
|
|
|
|
|
def test_semilla_alfabeto(self): |
|
|
self.assertEqual(s_alfa(''), Decimal(0)) |
|
|
self.assertEqual(s_alfa('VALE'), Decimal("0.2001394141")) |
|
|
|
|
|
|
|
|
def test_pi_decimal(self): |
|
|
self.assertEqual(PI_DEC, Decimal("3.1415926536")) |
|
|
|
|
|
|
|
|
def test_pseudo(self): |
|
|
self.assertEqual( |
|
|
pseudo(Decimal("0.2001394141")), |
|
|
Decimal("0.1671732123")) |
|
|
|
|
|
self.assertEqual( |
|
|
pseudo(Decimal("0.1671732123")), |
|
|
Decimal("0.9479315553")) |
|
|
|
|
|
sa=Decimal("0.2001394141") |
|
|
for s in range(28): |
|
|
sa=pseudo(sa) |
|
|
|
|
|
self.assertEqual(sa, Decimal("0.3376494172")) |
|
|
|
|
|
|
|
|
def test_fn_a(self): |
|
|
s = Decimal("0.1671732123") |
|
|
self.assertEqual(fn_a(28, s), 6) |
|
|
|
|
|
s = Decimal("0.9479315553") |
|
|
self.assertEqual(fn_a(27, s), 26) |
|
|
|
|
|
s = Decimal("0.7242082552") |
|
|
self.assertEqual(fn_a(26, s), 19) |
|
|
|
|
|
s = Decimal("0.9444166665") |
|
|
self.assertEqual(fn_a(25, s), 24) |
|
|
|
|
|
|
|
|
def test_alfa_derivado(self): |
|
|
alfa_derivado = alfa_k('VALE') |
|
|
self.assertEqual(ALFA_N, len(alfa_derivado)) |
|
|
self.assertEqual( |
|
|
alfa_derivado, |
|
|
list('FZSYQRXNDAVGBWLUKHEPOMIJT~_C')) |
|
|
|
|
|
|
|
|
def test_semilla_cifrado(self): |
|
|
self.assertEqual(s_cifra(''), Decimal(0)) |
|
|
|
|
|
s0_cifrado=Decimal("0.3462395532") |
|
|
self.assertEqual(s_cifra('VALE'), s0_cifrado) |
|
|
|
|
|
s115 = Decimal("0.4232082654") |
|
|
si = s0_cifrado |
|
|
for i in range(115): |
|
|
si=pseudo(si) |
|
|
|
|
|
self.assertEqual(si, s115) |
|
|
|
|
|
|
|
|
def test_de_claro_a_pi(self): |
|
|
clave = 'VALE' |
|
|
claro = "LA_HEROICA_CIUDAD_DORMIA" |
|
|
pi = [15,10,27,18,19,06,21,23,28,10,\ |
|
|
27,28,23,16, 9,10, 9,27, 9,21,\ |
|
|
06,22,23,10] |
|
|
|
|
|
r = lan(alfa_k(clave), claro) |
|
|
for n, (c, p) in enumerate(zip(r, pi)): |
|
|
self.assertEqual(c, p, "C: %d, P: %d, N: %d"% (c, p, n)) |
|
|
|
|
|
|
|
|
def test_cifrado(self): |
|
|
# cifrado vacio |
|
|
self.assertEqual('', codifica('', '')) |
|
|
self.assertEqual([], codifica('', '', 'n')) |
|
|
|
|
|
clave = 'VALE' |
|
|
claro = 'LA_HEROICA_CIUDAD_DORMIA' |
|
|
cin = [ 3,18, 3,11, 6, 7,20, 1,22, 2,\ |
|
|
13,16,11,18,19,18,23,16, 8, 4,\ |
|
|
14,10,13,13] |
|
|
|
|
|
cit = 'SHSVRXPFMZBUVHEHIUNYWABB' |
|
|
|
|
|
r = codifica(clave, claro, 'n') |
|
|
|
|
|
self.assertEqual(len(r), len(claro)) |
|
|
|
|
|
for n, (c, p) in enumerate(zip(r, cin)): |
|
|
self.assertEqual(c, p, "C: %d, P: %d, N: %d"% (c, p, n)) |
|
|
|
|
|
r = codifica(clave, claro) |
|
|
for n, (c, p) in enumerate(zip(r, cit)): |
|
|
self.assertEqual(c, p, "C: %s, P: %s, N: %d"% (c, p, n)) |
|
|
|
|
|
|
|
|
def test_descifrado(self): |
|
|
# cifrado vacio |
|
|
self.assertEqual('', decodifica('', '')) |
|
|
self.assertEqual([], decodifica('', [], 'n')) |
|
|
|
|
|
clave = 'VALE' |
|
|
claro = 'LA_HEROICA_CIUDAD_DORMIA' |
|
|
|
|
|
lan_alfabase = lambda letra: lan(ALFABETO_BASE, letra) |
|
|
claro_num = map(lan_alfabase, claro) |
|
|
|
|
|
cit = 'SHSVRXPFMZBUVHEHIUNYWABB' |
|
|
|
|
|
# Formato de salida numerico |
|
|
r = decodifica(clave, cit, 'n') |
|
|
self.assertEqual(len(r), len(claro)) |
|
|
|
|
|
for n, (c_desc, c_claro) in enumerate(zip(r, claro_num)): |
|
|
self.assertEqual(c_desc, c_claro, |
|
|
"Caracter descifrado: %s, Caracter claro: %s, N: %d" % |
|
|
(c_desc, c_claro, n)) |
|
|
|
|
|
# Formato de salida texto |
|
|
r = decodifica(clave, cit) |
|
|
self.assertEqual(len(r), len(claro)) |
|
|
for n, (c_desc, c_claro) in enumerate(zip(r, claro)): |
|
|
self.assertEqual(c_desc, c_claro, |
|
|
"Caracter descifrado: %s, Caracter claro: %s, N: %d" % |
|
|
(c_desc, c_claro, n)) |
|
|
|
|
|
|
|
|
########################################################################### |
|
|
# Ejecucion de los tests |
|
|
unittest.main() |
|
|
|