Mario Rossano
Crypt::FNA
http://www.perl.it/documenti/articoli/2010/04/anakryptfna.html
© Perl Mongers Italia. Tutti i diritti riservati.

Vincitore Contest2009

Anak's Fractal Numerical Algorithm for a new cryptography technology



a. Una nuova crittografia frattale

"Le nuvole non sono sfere, le montagne non sono coni, le costiere non sono cerchi e la corteccia non è liscia, né la luce viaggia su una linea retta"

così Benoit Mandelbrot ne "Il Mondo dei frattali"

Non potevo iniziare questo articolo senza citare l'incipit del bellissimo libro di Mandelbrot: in realtà i frattali che incontreremo nascono ben prima degli anni '70 del XX secolo ma procediamo per gradi... Chiariamo immediatamente che cosa è FNA:

Crypt::FNA è l'implementazione, in linguaggio Perl, di due distinti algoritmi che ho elaborato:
  • per la costruzione dell'insieme di una famiglia di curve frattali
  • per una crittografia basata su queste curve


b. Definizione dell'insieme di curve frattali {F}

"Il caso favorisce solo la mente preparata"

Louis Pasteur

Immaginate la complessità, a livello computazionale, di un algoritmo per la costruzione della curva di Koch che segua l'idea del suo creatore e cioè:

  1. prendi un segmento... e fin qui
  2. dividilo in tre... e fin qui
  3. elimina il segmento mediano... mmmh...
  4. congiungi i due punti mediani con un altro in modo da avere un triangolo equilatero... mmmmhh
  5. itera il procedimento su ogni segmento.... Mumble... complesso, considerato poi che per ogni segmento occorre, dopo il disegno, rimuovere una parte, calcolare le coordinate dell'altro vertice e così via...
Se avessi implementato questo algoritmo per la costruzione della curva di ordine n, avrei dovuto, necessariamente, calcolare tutte le curve da 1 a n-1. Dovevo semplificare...

La proprietà fondamentale di questa curva, come pure di tante altre curve frattali, è l'autosimilitudine.

Ragioniamo inizialmente sulla curva di Koch per poi estendere i risultati.



Indicando quindi con:
Ro:il numero di parametri della base (4 nel caso della curva di Koch)
r:l'ordine della curva
ann: numero angoli della curva di ordine n

Possiamo stabilire che il numero di angoli (an) della curva di ordine n è:

ann=Ror

Riportiamo ora gli angoli dei vari ordini in una costruzione a triangolo:
                                     0
                               0, 60, -60, 0
       0, 60, -60, 0, 60, 120, 0, 60, -60, 0, -120, -60, 0, 60, -60, 0
			
Vi ricorda qualcosa? Notiamo la somiglianza con la costruzione del Triangolo di Tartaglia:
                                     1
                                  1     1
                               1     2     1
                            1     3     3     1
                         1     4     6     4     1
			
Questo triangolo riporta la disposizione triangolare dei coefficienti binomiali, ossia dei coefficienti dello sviluppo del binomio (a+b) elevato ad una qualsiasi potenza n.

La cosa che a noi interessa è che ogni numero del triangolo è ottenuto come somma delle due corrispondenti al rigo superiore: osserviamo che possiamo esprimere la proprietà di auto-similitudine della curva di Koch grazie ad una costruzione simile, combinando tra loro i valori della base e poi con quelli derivati dalla combinazione e così via iterando il procedimento.

In questo caso, per Ro=4, abbiamo questa situazione:

riga per r=0 -> 0 + 0 = 0

riga per r=1 -> 0 + 0 = 0; 0 + 60 = 60; 0 - 60 = -60; 0 + 0 = 0

riga per r=2 ->
a. 0 + 0 = 0; 0 + 60 = 60; 0 - 60 = -60; 0 + 0 = 0
b. 60 + 0 = 60; 60 + 60 = 120; 60 - 60 = 0; 60 + 0 = 60
c. -60 + 0 = -60; -60 + 60 = 0; -60 - 60 = -120; -60 + 0 = -60
d. 0 + 0 = 0; 0 + 60 = 60; 0 - 60 = -60; 0 + 0 = 0

Iterando il procedimento, otteniamo proprio gli angoli della curva di ordine n.

Sembra però che per identificare gli angoli della curva di ordine n sia ancora necessario procedere all'identificazione degli angoli di tutte quelle con ordine <n.

Continuiamo ad osservare, scrivendo la successione degli angoli come gli elementi di un vettore:
             a(0)  =  a(0) + a(0)
             a(1)  =  a(0) + a(1)                I   GRUPPO
             a(2)  =  a(0) + a(2)
             a(3)  =  a(0) + a(3)
             ----------------------------------------------
             a(4)  =  a(1) + a(0)
             a(5)  =  a(1) + a(1)                II  GRUPPO
             a(6)  =  a(1) + a(2)
             a(7)  =  a(1) + a(3)
             ----------------------------------------------
             a(8)  =  a(2) + a(0)
             a(9)  =  a(2) + a(1)                III GRUPPO
             a(10) =  a(2) + a(2)
             a(11) =  a(2) + a(3)
             ----------------------------------------------
             a(12) =  a(3) + a(0)
             a(13) =  a(3) + a(1)                IV  GRUPPO
             a(14) =  a(3) + a(2)
             a(15) =  a(3) + a(3)
			
Abbiamo così le somme per identificare i vari angoli di inclinazione dei segmenti approssimanti la curva...
Scritte le relazioni in questo modo, possiamo osservare chiaramente la proprietà dei due addendi che forniscono l'angolo n-esimo:

il primo addendo è il gruppo o ramo su cui si itera il procedimento di costruzione.

Il secondo addendo è la posizione dell'angolo che stiamo calcolando nell'ambito di quel ramo.

Il gruppo cui appartiene l'angolo k-esimo possiamo indicarlo così nel formalismo del Perl:

G(k) = int(k/Ro)

La posizione dell'angolo k-esimo nel gruppo è invece:

P(k) = k-int(k/Ro) = k-G(k)

In definitiva, il valore della direzione k-esima sarà:

a(k)=a(G(k)) + a(P(k)) (1)

Notiamo che questa relazione è generale, indipendente dal numero di parametri base della curva. In quella di Koch abbiamo una base di cardinalità pari a 4 ma non è necessariamente così.

Con questa relazione diventa semplice ricavare il grafico della curva, potendone calcolare le direzioni dei segmenti approssimanti ad un certo ordine ed implementando poi un sistema di turtle graphics per il tracciamento:

            while ($k<$Ro**$r) {
                 $a[$k]=$a[int($k/$Ro)]+$a[$k-int($k/$Ro)];
                 $k++
            }
            
Indichiamo quindi con {F} l'insieme delle curve le cui direzioni dei segmenti approssimanti sono ottenute mediante la relazione (1). {F} ha una dimensione di Hausdorff compresa tra 1 e 2 (con piccole varianti possono calcolarsi anche quelle con dimensione minore di 1, come la polvere di Cantor) e cardinalità infinita, come facilmente rilevabile osservando il numero di parametri genitore possibili.


c. Grafico di alcune curve di {F}

Di seguito alcuni grafici realizzati tramite il metodo make_fract di Crypt::FNA che implementa l'algoritmo appena descritto.


(56,-187, 215, 64)


(0,90,-60,-90,60)


(56,-77,215,-64,60)


(56,-177,225,-164)




d. Algoritmo di crittografia basato su una curva dell'insieme {F}

"Il più nobile dei piaceri è la gioia di capire"

Leonardo da Vinci

Abbiamo quindi incontrato, spero piacevolmente, questi strani enti autosimili ma il mio desiderio è di applicarli, creando nel contempo, tecnologia.

Anni addietro mi sono interessato di crittografia, sviluppando un sistema adatto a cifrare stringhe ASCII. Adesso volevo usare le curve {F} in modo da criptare i files più disparati.

Il ragionamento seguito per giungere a questo scopo è il seguente.

I dati sono memorizzati in byte: qualunque tipo di file vada ad aprire, il suo contenuto è certamente una sequenza ben precisa di byte. Un byte è costituito da 8 bit, per cui il suo valore deve appartenere all'insieme degli interi compresi tra 0 e 255 (256 elementi complessivamente). Seguo quindi la curva frattale scelta, dell'insieme {F}, per un numero di vertici uguale a quella del valore del byte da criptare. Le coordinate cartesiane di quel vertice rappresentano il crittogramma di quel ben preciso byte.

Qualcuno potrebbe domandarsi perché non una funzione y=f(x) qualsiasi? La risposta è che i grafici delle funzioni y=f(x) non hanno un andamento notevolmente irregolare come nel caso dei frattali ed è proprio questa irregolarità a rendere maggiormente complessa la crittoanalisi. In altre parole, i frattali appartenenti ad {F} non essendo frutto di una funzione matematica, bensì di un algoritmo, non sono dotati di UNA relazione "singola" che permetta di calcolarne le coordinate di ogni punto. Ho calcolato che la "visibilità diretta", dati k angoli e base di cardinalità ro è: ro^2+(k-int(k/ro)).

Le curve {F} hanno quindi un andamento che, in generale, si conosce solo calcolandolo ma lo si può calcolare solo se sono noti i parametri Ro genitori che sono parti fondamentali della chiave: è proprio in questo il cuore del sistema crittografico. Come altri sistemi di cifratura simmetrici, ad esempio il DES ed AES, FNA ha chiave segreta ma a differenza dei predetti Data Encryption Standard (che ha una chiave di 56 bit) ed Advanced Encryption Standard (che ha una chiave compresa tra i 128 ed i 256 bit), Fractal Numerical Algorithm ha una chiave in bit lunga quanto si vuole: non ci sono restrizioni sul numero e valore delle direzioni della base Ro.

L'attento lettore avrà forse notato che il byte "0" produce, nel crittogramma, ripetizione nei valori (avanzando di 0 vertici sulla curva): alla crittoanalisi, ove ci fossero coppie di coordinate uguali e consecutive, identificherei tutti i bytes di valore "0". Ho evitato questo comportamento, procedendo sempre e comunque in avanti sulla curva: crittografato il valore di un byte nelle cartesiane del vertice k-esimo, si riparte dal successivo k+1-esimo. Ho poi ulteriormente modificato l'algoritmo, introducendo il cosiddetto "magic number", un altro parametro, segreto, che istruisce il sistema a saltare un certo numero di vertici della curva frattale, in modo da far perdere le tracce del percorso seguito...

In definitiva:

criptare

Ogni byte viene crittografato mediante le coordinate del vertice della curva frattale, ottenuto partendo dal successivo a quello precedentemente valutato, saltando di un numero ulteriore di vertici uguale al magic number più il valore del byte da crittografare.

decriptare

Si segue la curva frattale verificando, di vertice in vertice, che le coordinate corrispondano a quelle del crittogramma. Il valore del byte originale viene ricostruito avendo contato quanti vertici si sono succeduti per arrivare all'uguaglianza dei due valori, dall'ultima uguaglianza incontrata. Il numero di vertici, ridotto del magic number sommato all'unità, rappresenta il valore del byte n-esimo.



e. Crypt::FNA i metodi della classe

La libreria Crypt::FNA è costituita da una classe principale ed una accessoria per la validazione dell'istanza. La classe principale, in Anakrypt/FNA.pm, contiene il codice per l'istanziazione dell'oggetto frattale oltre i metodi per la crittografia ed il disegno della particolare approssimazione alla curva dell'insieme {F}. L'altra classe, in Anakrypt/FNA/Validation.pm, riporta: i valori di default per una istanziazione veloce dell'oggetto; i controlli di coerenza sui valori degli attributi in fase di istanziazione; la cattura degli errori in scrittura e lettura sui files di lavoro coinvolti.

Crypt::FNA comprende quattro metodi:
  • new
    • per l'istanziazione di un nuovo oggetto FNA
  • make_fract
    • per il disegno della curva frattale utilizzata da FNA
  • encrypt_file
    • per calcolare il crittogramma, secondo la curva scelta, di un file
  • decrypt_file
    • per ricostruire il file partendo dal crittogramma
  • encrypt_scalar
    • per calcolare il crittogramma di un testo
Vediamo ora nel dettaglio i metodi e relativa sintassi


e.1 Il metodo "new"

Questo metodo ha due diverse modalità per l'istanziazione dell'oggetto:

            1.	my $krypto=FNA->new();
            2.	my $krypto=FNA->new(
                     {
                           r=> 7,
                           angle =>  [56,-187, 215,-64],
                           square => 4096,
                           background => [255,255,255],
                           foreground => [0,0,0],
                           magic => 3
                     }
                );
			

Con la prima modalità, l'oggetto viene creato con i valori standard, fissati nella classe "Crypt::FNA::Validation". Nella seconda modalità, si specificano tutti o parte dei parametri dell'oggetto che ora vedremo in dettaglio.



e.1.1 Il parametro "r": ordine della curva di {F}

Indica il livello di approfondimento nel calcolo della curva. E' un numero maggiore di zero, non necessariamente intero. Indicato con Ro il numero di angoli base della struttura autosimile, il numero di segmenti costituenti la curva è dato da Ro**r.

Valore di default: 7


e.1.2 I parametri "angle": direzioni dei segmenti base della curva di {F}

Sono gli angoli cui si applica l'algoritmo di ricorsione: su questi angoli si determina la struttura base autosimile della curva di {F}. Gli angoli sono espressi nel sistema sessadecimale, con valori compresi tra -360 e 360 (ovvero da 0 a 360).

Valore di default: (56,-187, 215,-64)


e.1.3 Il parametro "square": lato del quadrato dove verrà disegnata/calcolata la curva di {F}

E' la lunghezza del lato del quadrato contenitore della curva. Square non solo ha importanza per la (eventuale) rappresentazione grafica, ma anche per la crittografia, poiché viene utilizzato per calcolare la lunghezza del lato di della curva (di è proporzionale a square/Ro**r)

Valore di default: 4096


e.1.4 Il parametro "background": colore di fondo per il disegno della curva di {F}

E' il colore RGB di fondo del file PNG contenente il disegno della curva. La notazione è decimale, quindi con valori che vanno da 0 a 255.

Valore di default: (255,255,255)


e.1.5 Il parametro "foreground": colore di primo piano per il disegno della curva di {F}

E' il colore RGB del tratto nel file PNG contenente il disegno della curva. La notazione è decimale, quindi con valori che vanno da 0 a 255.

Valore di default: (0,0,0)


e.1.6 Il parametro "magic number": per la crittografia discreta

Indica il numero di vertici della curva da saltare in fase di cifratura e decifratura: essendo l'algoritmo, una funzione continua sui vertici, saltandone alcuni questa resta continua su punti isolati dell'insieme dei vertici (da cui "discreta").

Valore di default: 3


e.2 Il metodo "encrypt_file"

I metodi encrypt_file e decrypt_file, sono la summa: rendono utile mediante applicazione, la matematica delle curve di {F}. Questo metodo realizza un'operazione ben precisa: cripta il file di input in quello di output.

La sintassi è:
        $krypto->encrypt_file($name_plain_file,$name_encrypted_file)
            
Il file di input di qualsivoglia formato sarà letto e cifrato, tramite la curva {F}.



e.3 Il metodo "decrypt_file" I metodi decrypt_file ed encrypt_file, sono la summa: rendono utile mediante applicazione, la matematica delle curve di {F}. Questo metodo realizza un'operazione ben precisa: decripta il file di input (che è quello di output del metodo encrypt_file) in quello di output (che è quello di input del metodo encrypt_file).

La sintassi è:
       $krypto->decrypt_file($name_encrypted_file,$name_decrypted_file)
			
Il file di input sarà letto e decodificato, tramite la curva {F}, nel file di output.


e.4 Il metodo "encrypt_scalar"

Il metodo encrypt_scalar cifra stringhe: il risultato dell'operazione di cifratura è un vettore contenente il crittogramma.

La sintassi è:
           @encrypted_scalar=$krypto->encrypt_scalar($this_string)
			
Questo metodo è particolarmente utile per il salvataggio delle password in un'applicazione. Come metodologia operativa, supponiamo di avere un database utenti di un'applicazione web. Salvando nel database le password criptate, l'eventuale violazione della tabella non porterà alla violazione delle password. Il sistema, in fase di login utente, prenderà la password digitata e la cripterà, mediante il metodo encrypt_scalar, verificando la corrispondenza con il valore criptato e non con quello in chiaro (che non sarà stato salvato).

Il programmatore che implementi un salvataggio password farà bene a implementare quello che viene definito "salt", ovvero un "sale" per la password. Ricordo brevemente che il "salt" è una stringa random che viene aggiunta al dato da criptare, in modo che un brute force a dizionario non produca risultati.

Crypt::FNA non implementa, allo stato attuale, un metodo per decifrare gli scalari criptati poiché ho ritenuto che la sola cifratura, per usi mirati al salvataggio password, fosse sufficiente. In ogni caso, con un piccolo artificio, è possibile decifrare anche gli scalari utilizzando il metodo decrypt_file e scrivendo lo scalare in un file in memoria volatile (possiamo evitare di sollecitare il file system per questa operazione :)).

Di sequito il codice (inserito nel file di test della lib):
# hack ricostruzione stringa
     my $stringa_in_chiaro='questa è una prova';
     my @encrypted_scalar=$krypto->encrypt_scalar($stringa_in_chiaro);
     for(@encrypted_scalar) {print $_."\n"}
     # ricostruzione stringa
          my ($fh_testo_criptato,$file_criptato);
          open $fh_testo_criptato, '>',\$file_criptato or die "err. scrittura file in memoria\n";
               for (@encrypted_scalar) {print $fh_testo_criptato $_."\n"}
          close $fh_testo_criptato;
          my ($fh_testo_decriptato,$stringa_decriptata);
          $krypto->decrypt_file(\$file_criptato,\$stringa_decriptata);
     # end ricostruzione stringa
# end hack
				

$stringa_decriptata contiene il valore della stringa in chiaro.


e.5 Il metodo "make_fract"

Questo metodo è senz'altro il più suggestivo e permette di "toccare" le curve che verranno poi applicate negli algoritmi crittografici. Per il programmatore può essere utile, nella propria applicazione, mostrare la curva, ad esempio in un ipotetico pannello di controllo per la gestione delle password o file criptati in attachment a moduli inviati per posta elettronica e salvati sul server.

Il file grafico di output è in formato PNG (Portable Network Graphic), fruibile da un qualunque browser come dai più diversi software di grafica.

La sintassi è:

			$krypto->make_fract($pngfile,$zoom)
			
  • $pngfile è il nome del file png - senza estensione "png" che viene inserita automaticamente
  • $zoom è la scala del disegno - maggiore di zero. Valore di default: 1
  • L'immagine prodotta è contenuta nel quadrato di lato square


f. Il codice di esempio: fnatest.pl

(Per motivi di spazio ho rimosso l'hack per la ricostruzione stringa, già illustrato nel par. e.4)

# package: Anak Cryptography with Fractal Numeric Algorithm
# autore: Mario Rossano aka Anak
# www.netlogicalab.com, www.netlogica.it
# software@netlogicalab.com; software@netlogica.it
# FILE DI ESEMPIO

# caricamento moduli moduli
    use strict;
    use warnings;
    use Crypt::FNA;
# fine caricamento moduli

    # fractals examples
        #angle => [0,90,-60,-90,60]    # chiave
        #angle => [0,90,60,-90,120]    # scorpione
        #angle => [0,60,-60,0]         # von Koch
        #angle => [56,-187,215,64]     # nebulosa gassosa
        #angle => [66,-177,205,64]     # ramo sulla spiaggia
        #angle => [0,80,60,-90,120]    # maschera
   # end fractals examples

   # METODI

        # ISTANZA DI UN OGGETTO FNA
             my $krypto=FNA->new(
                  {
                       r=> "6a",
                       angle =>  [0,90,-60,-90,60],
                       square => 4096,
                       background => ["b",0,0],
                       foreground => ["a255",255,255],
                       magic => 2
                  }
             );
             my $krypto2=FNA->new();

        # GRAFICO DI UN FRATTALE
             $krypto->make_fract("fractal1","1a"); # nome file png e zoom
             $krypto2->make_fract("fractal2",1);   # nome file png e zoom

        # CIFRATURA DI FILE
             $krypto->encrypt_file("test.txt","test.fna");

        # DECIFRATURA DI FILE
             $krypto->decrypt_file("test.fna","test_rebuild.txt");

        # UNIONE DEI METODI (IPERCRITTOGRAFIA)
             $krypto->encrypt_file("test.txt","test2.fna");
                  $krypto2->encrypt_file("test2.fna","test3.fna");
                  $krypto2->decrypt_file("test3.fna","test2_rebuild.fna");
             $krypto->decrypt_file("test2_rebuild.fna","test3_rebuild.txt");

        # CIFRATURA DI STRINGHE
             my @encrypted_scalar=$krypto->encrypt_scalar("questa è una prova");
             foreach my $encrypted_scalar(@encrypted_scalar) {
                  print $encrypted_scalar."\n"
             }

        # INTERCETTAZIONE ERRORI (SALVATI NEL VETTORE  $krypto->message)
             $krypto->make_fract("fractal1","3a"); # nome file png e zoom
             my @errors=@{$krypto->message};
                  foreach my $errors(@errors) {
                       print "> 1-".$errors."\n"
             }
             @errors=@{$krypto2->message};
                  foreach my $errors(@errors) {
                       print "> 2-".$errors."\n"
             }
exit
			
Sono volutamente riportati errori di inizializzazione nel file di test, per popolare il vettore @errors.


g. Il codice del package FNA.pm

"La filosofia è scritta in questo grandissimo libro che continuamente ci sta aperto innanzi agli occhi, ma non si può intendere se prima non s'impara a intender la lingua, e conoscere i caratteri, ne' quali è scritto. Egli è scritto in lingua matematica, e i caratteri son triangoli, cerchi, ed altre figure geometriche, senza i quali mezi è impossibile a intenderne umanamente parola; senza questi è un aggirarsi vanamente per un oscuro laberinto"

così Galileo Galilei nel "Saggiatore"

Il package nelle prime righe riporta le indicazioni di rito, con l'indicazione del titolo, autore e numero di versione.
# package: Anak Cryptography with Fractal Numeric Algorithm
# author: Mario Rossano aka Anak
# www.netlogicalab.com, www.netlogica.it
# software@netlogicalab.com, software@netlogica.it
# LIBRERIA

package FNA;

# caricamento lib
    use strict;
    use warnings;
    use Crypt::FNA::Validation;
# fine caricamento lib

our $VERSION = '0.01';
use constant pi => 3.141592;
			
Una piccola nota: il valore di pi greco è stato impostato come valore costante, approssimato indipendentemente dalla piattaforma utilizzata, poiché viene utilizzato per il calcolo della curva su cui poggia il processo crittografico. E' necessario che il numero, fino all'ultimo decimale che si è scelto di utilizzare, sia quindi sempre lo stesso, pena l'impossibilità di reversibilità dell'operazione crittografica.

Di seguito il codice che implementa i metodi disponibili nella classe. Il primo è "new", che serve a creare l'oggetto con cui si andrà ad operare. Vediamo che, dopo le chiamate per l'impostazione degli attributi così come da script parent (r, angle, square, background, foreground, magic), viene creato l'oggetto "$validate" come istanza di Crypt::FNA::Validation. Su $validate viene applicato il metodo "control_new_fna" (cui si passa l'oggetto $self, ovvero l'istanza di Crypt::FNA creata nel parent), in modo da verificare la coerenza di tutti i valori di inizializzazione. Vedremo in seguito qualche dettaglio su FNA::Validation. "control_new_fna" effettua la verifica dei valori, mediante regular expression come argomento di istruzioni condizionali e, in caso di assegnazione non coerente, come ad esempio potrebbe essere assegnare "-34" o "pippo" all'ordine della curva "r" (che invece è un numero positivo), carica, se possibile, il valore di default del parametro, procedendo comunque con l'elaborazione.

Vediamo che in caso di errore, dallo script parent è accessibile il valore dell'attributo "message", un array contenente i codici restituiti da Crypt::FNA::Validation, ad uso del programmatore per una eventuale gestione.

# metodi ed attributi

sub new {
	my $class = shift;
	my $init  = shift;
	my $self={};

	bless $self,$class;

	$self->r($init->{r});
	$self->angle($init->{angle});
	$self->square($init->{square});
	$self->binary_mode($init->{binary_mode});
	$self->background($init->{background});
	$self->foreground($init->{foreground});
	$self->magic($init->{magic});
	$self->message($init->{message});

	my $validate=Crypt::FNA::Validation->new({intercept => $self});
	$validate->method_new_fna($self);

	return $self
}
			
Tralascio qui il codice degli accessor in modo da riservare maggiore spazio ai metodi.

La subroutine per la cifratura di un file è quella riportata di seguito. Qui trovano compimento gli algoritmi descritti di calcolo della curva frattale unitamente a quello di crittografia.

Oltre all'oggetto, sono passati al metodo il nome del file da criptare: $name_plain_file ed il nome del file dove sarà stampato il crittogramma: $name_encrypted_file.

La chiamata al metodo init_geometry effettua le operazioni di calcolo delle coordinate di partenza per il tracciamento della curva e della lunghezza del segmento in relazione al quadrato contenitore. Questo dato è importante ai fini del processo di crittografia poiché, come sopra riportato, verte sulla trasformazione del byte nel numero complesso delle coordinate del vertice n-esimo.

Al metodo init_geometry viene passato, oltre l'oggetto, il parametro $ro che, come sappiamo, è la lunghezza del segmento di curva.

$ro viene assegnato mediante calcolo effettuato tramite applicazione del metodo set_starting_angle. set_starting_angle restituisce, oltre che $ro, anche @initial_angle contenente gli angoli di inizializzazione convertiti in radianti (necessari come argomento alle funzioni seno e coseno).

Si invoca il metodo set_starting_angle, assegnando $ro e @initial_angle per poi passare ad init_geometry.

Per rendere maggiormente complessa la crittoanalisi, di, calcolato sulla base dell'attributo square , viene incrementato del magic_number. Per analogo motivo, le coordinate iniziali per il tracciamento e calcolo sono rapportate al magic_number che fa parte della chiave a pieno titolo, unitamente alla base Ro.

Sono poi inizializzate tre variabili locali: $this_byte, $byte_dec e $byte_pos:
  • $this_byte è utilizzato nel ciclo di lettura del file da cifrare
  • $byte_dec è il valore decimale di $this_byte
  • $byte_pos è la posizione di $this_byte nel file da criptare
sub encrypt_file {
    my $self=shift;
    my $name_plain_file=shift;
    my $name_encrypted_file=shift;

    (my $ro,my @initial_angle)=$self->set_starting_angle();
    (my $nx,my $ny,my $di)=$self->init_geometry($ro);

    $di+=$self->magic;
    $nx=$nx/$self->magic; # ascissa iniziale
    $ny=$ny/$self->magic; # ordinata iniziale;

    my ($this_byte,$byte_dec);
    my $byte_pos=0;

    my ($fh_plain,$fh_encrypted);
    open $fh_plain,'<',$name_plain_file or do {
        push(@{$self->{message}},7);
        return
    };
        binmode $fh_plain;
        open $fh_encrypted,'>',$name_encrypted_file or do {
            push(@{$self->{message}},8);
            return
        };
			
Qualora l'apertura del file di input e/o output non abbia successo, viene eseguito il codice alternativo (or do) per cui il contenitore dei codici di errore, il valore dell'attributo message, viene aggiornato e l'elaborazione attinente l'apertura non viene eseguita.

Definiti quindi gli attori della sub, inizia l'iterazione, mediante ciclo while dal primo byte all'ultimo, byte a byte.

Dopo aver letto (read) il byte corrente ed assegnatone il valore decimale alla variabile $byte_dec, si aggiunge a questo il valore definito dal magic_number per un motivo che sarà chiaro tra poco...

Con la chiamata alla funzione successiva, crypt_fract, non facciamo altro che seguire la curva frattale, partendo dal vertice successivo all'ultimo processato e saltando un numero di vertici pari al valore del byte aumentato del magic number. Le coordinate di questo vertice sono il crittogramma di quel ben preciso byte.

I crittogrammi sono riportati, man mano che vengono assegnati, nel file $fh_encrypted: una riga per ogni singola coordinata in modo da avere, alla fine del processo, una sequenza di numeri incolonnati.

        while (!eof($fh_plain)) {
            read($fh_plain,$this_byte,1);

            $byte_dec=unpack('C',$this_byte);
            $byte_dec+=$self->magic+1;

            ($nx,$ny,$byte_pos)=$self->crypt_fract($ro,1,$di,$nx,$ny,$byte_dec,$byte_pos);
            print $fh_encrypted $nx."\n".$ny."\n"
        }
        close ($fh_encrypted);
    close ($fh_plain);
    @{$self->angle}=@initial_angle
}
				
L'ultima operazione è la riassegnazione all'oggetto degli angoli di inizializzazione nel sistema sessadecimale. Questo è necessario poiché il vettore, durante l'applicazione del metodo, è stato popolato con gli angoli di inclinazione per il tracciamento, tutti espressi in radianti e la mancata riassegnazione produrrebbe un comportamento anomalo da parte degli altri metodi, eventualmente invocati da parte dello script parent.

La subroutine successiva, encrypt_scalar, effettua la stessa operazione della subroutine encrypt_file, restituendo in uscita un array (e non un file) che è il crittogramma del testo. Abbiamo quindi:
  • in luogo del READ, un SUBSTRing per la lettura del singolo carattere
  • un ciclo sulla lunghezza della stringa, anziché sulla fine del file
  • un push nell'array @encrypted contenente le coordinate del vertice calcolato (restituito allo script parent a fine ciclo insieme a tutti gli altri) in luogo del print nel file criptato
Le chiamate alla init_geometry e set_starting_angle effettuano, chiaramente, le identiche operazioni di calcolo in modo da avere le coordinate iniziali per il calcolo/tracciamento e la lunghezza del segmento di curva.

Come nel caso della subroutine encrypt_file, un'ulteriore complicazione per i crittoanalisti eventualmente impegnati a decrittare le informazioni, è che la lunghezza del segmento di viene incrementata del valore del magic number. Inoltre le coordinate iniziali per il tracciamento della curva vengono anch'esse legate al magic number.
sub encrypt_scalar {
    my $self=shift;
    my $string=shift;

    (my $ro,my @initial_angle)=$self->set_starting_angle();
    (my $nx,my $ny,my $di)=$self->init_geometry($ro);

    # incremento del magic_number in modo da rendere maggiormente complessa
    # l'individuazione della parte di chiave "di" alla crittoanalisi
    $di+=$self->magic;
    $nx=$nx/$self->magic; # ascissa iniziale
    $ny=$ny/$self->magic; # ordinata iniziale;

    my $char_code;
    my $char_pos=0;
    my @encrypted;

    for (split(//,$string)) {
        $char_code=unpack('C',$_);
        $char_code+=$self->magic+1; # maschero il codice carattere

        # chiamata ricorsiva
        ($nx,$ny,$char_pos)=$self->crypt_fract($ro,1,$di,$nx,$ny,$char_code,$char_pos);
        push(@encrypted,($nx,$ny))
    }
    @{$self->angle}=@initial_angle;
    return (@encrypted)
}
				
La subroutine decrypt_file fa esattamente cosa ci si aspetta: ricostruisce il file originale del crittogramma.

Vediamo quali variabili sono coinvolte nel processo:
  • il primo parametro è, come sempre, l'oggetto cui afferisce la particolare istanza nello script parent
  • il successivo è il nome del file criptato: $name_encrypted_file
  • infine vi è l'impostazione del filename che conterrà la ricostruzione del file originale: $name_decrypted_file
Effettuate le chiamate alle ormai note subroutines init_geometry e set_starting_angle, vengono inizializzate le altre variabili locali che corrispondono alle omonime delle subroutines encrypt_file ed encrypt_scalar, utilizzate nel processo di percorso sulla curva e calcolo delle coordinate dei vertici successivi.

Come nelle subroutine encrypt_file ed encrypt_scalar, per ovvi motivi di reversibilità, la lunghezza del segmento di viene incrementata del valore del magic number. Inoltre le coordinate iniziali per il tracciamento della curva vengono anch'esse legate al magic number.

Per ricostruire il file originale, si scandisce il file criptato, leggendo le coordinate lì salvate, quindi si segue la curva {F} calcolando le coordinate cartesiane dei vertici, verificando, nel contempo, l'uguaglianza con le coordinate lette dal crittogramma. Abbiamo quindi due cicli annidati: uno esterno sul crittogramma ed uno interno sulla curva. Nel ciclo esterno leggiamo due righe alla volta poiché nel file criptato le coordinate dei vertici occupano ognuna una riga (quindi leggiamo ascissa ed ordinata). Nel ciclo interno, una volta verificata l'uguaglianza delle coordinate del vertice processato con quelle del crittogramma, il valore del byte corrisponderà al numero di vertici processati, rispetto all'ultima uguaglianza, diminuito del magic number ($self->magic) e diminuito dell'unità (poiché quando si cripta, si riparte sempre dal vertice successivo): $this_byte_dec=$this_vertex-$from_vertex-1-$self->magic. Come si evince, $from_vertex è il vertice corrispondente alle ultime coordinate trovate.

Tanto premesso, le variabili (locali) coinvolte sono:
  • $from_vertex contiene il valore del posizionale dell'ultimo vertice processato per il quale si è verificata un'uguaglianza
  • $vertex è il contatore del ciclo interno, punto di partenza per il calcolo delle coordinate dei vertici di rispetto all'ultima uguaglianza trovata {F}
  • $this_vertex è il posizionale del vertice di {F} di cui si sono calcolate le coordinate da verificare per l'uguaglianza
  • $this_byte_dec il valore del byte nel sistema decimale, calcolato come differenza del posizionale del vertice di {F} valutato attualmente, rispetto all'ultimo posizionale per cui ci sia stata uguaglianza di coordinate tra crittogramma e coordinate del vertice della curva
  • $this_byte è il valore del byte da scrivere nel file originale
  • $x_coord è l'ascissa del vertice della curva {F}
  • $y_coord è l'ordinata del vertice della curva {F}
Una volta verificata l'uguaglianza e calcolato il valore del byte, lo si scrive nel file di output $fh_decrypted dopo aver salvato in $from_vertex il valore di $this_vertex, come sopra indicato.

In caso di errore in apertura dei files coinvolti, come nel caso del metodo encrypt_file, verrà inserito in coda all'array $self->message il codice di errore riscontrato.
sub decrypt_file {
    my $self=shift;
    my $encrypted_filename=shift;
    my $decrypted_filename=shift;

    (my $ro,my @initial_angle)=$self->set_starting_angle();
    (my $nx,my $ny,my $di)=$self->init_geometry($ro);

    # incremento del magic_number in modo da rendere complessa la crittoanalisi
    # basandosi sulla lunghezza di
    $di+=$self->magic;
    $nx=$nx/$self->magic; # ascissa iniziale
    $ny=$ny/$self->magic; # ordinata iniziale;

    my $from_vertex=0;
    my ($this_byte,$this_byte_dec,$this_vertex,$x_coord,$y_coord);

    my ($fh_encrypted,$fh_decrypted);
    open $fh_encrypted,'<',$encrypted_filename or do {
        push(@{$self->{message}},9);
        return
    };
        open $fh_decrypted,'>',$decrypted_filename or do {
            push(@{$self->{message}},10);
            return
        };
            binmode $fh_decrypted;

             while (!eof($fh_encrypted)) {
                $x_coord=<$fh_encrypted>;$y_coord=<$fh_encrypted>;
                chop($x_coord,$y_coord);
                # ho usato chop perchè l'ultimo carattere è certamente \n e chop è più veloce di chomp

                for (my $vertex=$from_vertex;$vertex<256+$from_vertex+$self->magic+1;$vertex++){
                    ($nx,$ny,$this_vertex)=$self->crypt_fract($ro,1,$di,$nx,$ny,1,$vertex);
                    if ($nx eq $x_coord && $ny eq $y_coord) {
                        # qui ricavo il byte come posizione rispetto
                        # alla posizione trovata precedentemente
                        $this_byte_dec =$this_vertex-$from_vertex-$self->magic-1;

                        $this_byte=pack('C',$this_byte_dec);
                        print $fh_decrypted $this_byte;

                        #imposto il from per ripartire il ciclo for dal punto giusto alla
                        # prossima iterazione del while, quando ripartirà il for

                        $from_vertex=$this_vertex;
                        last
                    }
                } # fine for
            } # fine ciclo while
        close $fh_decrypted;
    close $fh_encrypted;
    @{$self->angle}=@initial_angle
}
				
Il metodo seguente, make_fract, non è strutturale alle funzionalità crittografiche ma non potevo esimermi dal realizzarlo, poiché è dal desiderio di ottenere il grafico di queste bellissime curve che è nata la sfida e l'algoritmo. Tramite make_fract si stampa il grafico di {F} in un file PNG (Portable Network Graphics), formato fruibile da tutti i browser (non testuali) e dai più disparati software di grafica nei diversi sistemi operativi.

Qualora dallo script parent non venga invocato questo metodo, non sarà necessario caricare GD::Simple (ed implicitamente GD), quindi, per fruire delle funzionalità crittografiche, Crypt::FNA non ha dipendenze: è eseguibile anche su macchine dotate del solo interprete/compilatore Perl.

Le prime operazioni effettuate sono, come sempre, la lettura dei parametri, nella fattispecie: l'oggetto ($self), il nome del file di output ($png_filename nome del PNG destinatario del grafico di {F} di ordine n) ed infine il fattore di zoom della curva.

Una volta completata la prima serie di istruzioni, viene impostato l'oggetto e la geometria iniziale di disegno (init_geometry e set_starting_angle ormai ben noti).

Si controlla quindi la presenza della libreria GD::Simple nel calcolatore in uso, mediante eval che viene poi verificata tramite l'istruzione condizionale if che, in caso di errore di caricamento, provvede a inserire in $self->message il codice di errore e a tornare allo script parent, consentendo il prosieguo dell'elaborazione residua.

Un'ulteriore verifica è quella sul fattore di zoom: nel caso in cui il valore sia non congruo (diverso da un numero positivo), viene assegnato il valore di default dalla classe Crypt::FNA::Validation. Per fare questo si istanzia $validate come oggetto Crypt::FNA::Validation e impostando, tramite il suo metodo new, nell'attributo intercept, il fattore di zoom passato dallo script parent. Una descrizione della classe Crypt::FNA::Validation esula dai limiti imposti all'articolo ma nulla vieta al lettore di aprire il source code :)
sub make_fract {
    my $self=shift;
    my $png_filename=shift;
    my $zoom=shift;

    # assegno coordinate iniziali del calcolo.
    # init_geometry usa set_starting_angle perchè "ro" e "angle"
    # iniziali vengono fuori da lì
    # posso quindi passarli direttamente, risparmiando un passaggio

    (my $ro,my @initial_angle)=$self->set_starting_angle();
    (my $nx,my $ny,my $di)=$self->init_geometry($ro);

    my $load_this_package=eval("require GD::Simple;");
    $load_this_package.='';
    if ($load_this_package eq '') {
        push(@{$self->{message}},16);
        return
    }

    #controllo zoom, solo valori numerici e > 0
      my $validate=Crypt::FNA::Validation->new({intercept => [$zoom,$self]});
      ($zoom,@{$self->message})=$validate->param_zoom_fna($self);
    #fine controllo zoom
			
Tramite il metodo new di GD::Simple, viene creata l'istanza $img.
L'immagine, contenuta in un quadrato di lato $self->square, è il nostro foglio da disegno.
Vengono inoltre impostati i colori di background e foreground, così come stabilito nell'istanza dell'oggetto Crypt::FNA nello script parent.

In un ciclo for sul numero degli angoli, vengono quindi calcolate le coordinate di ogni singolo vertice della curva, in modo tale che, tramite il metodo lineTo di GD::Simple, sarà possibile tracciarla.

Gli angoli sono in numero uguale al numero dei parametri genitore della curva, elevato all'ordine desiderato: maggiore è l'ordine, maggiore sarà l'accuratezza del grafico del frattale.

Questi sono calcolati mediante la function evaluate_this_angle, popolando man mano il vettore $self->angle e poi utilizzati per il disegno calcolando, in cascata, le coordinate dei vertici successivi. A questo metodo vengono passati l'indice $k corrispondente all'indice del vettore $self->angle da popolare in quel ciclo ed il numero di angoli base del frattale, $ro. Il metodo restituisce il valore dell'angolo k-esimo che va a popolare l'elemento di eguale indice del vettore $self->angle.
Inserito il valore dell'angolo nel vettore, viene passato l'indice $k al metodo evaluate_this_coords che restituisce le coordinate del prossimo punto della curva, unitamente al fattore di zoom ($zoom), alle coordinate dell'ultimo vertice processato ($nx,$ny) e alla lunghezza del segmento di curva ($di).
Calcolate le coordinate, queste vengono passate al metodo lineTo di GD::Simple per il tracciamento.

Le successive istruzioni, si occupano di tracciare il disegno di {F} nel file PNG. Tra i parametri passati ci sono infatti il nomefile: $png_filename (l'estensione ".png" è automaticamente inserita in fase di salvataggio) ed il fattore di zoom: $zoom già verificato.

Una volta completato il ciclo, si salva l'elaborazione nel file grafico.
Come in altre occasioni, qualora non sia possibile aprire il file in scrittura, viene aggiornato il contenuto di $self->message. In caso affermativo viene salvato il file png per gli usi previsti dallo script parent.
    my $img = GD::Simple->new($self->square,$self->square);
        $img->fgcolor(@{$self->background});              # foreground
        $img->bgcolor(@{$self->background});              # background
        $img->rectangle(0,0,$self->square,$self->square); # campisce il quadrato
        $img->fgcolor(@{$self->foreground});              # foreground

    $img->move($nx,$ny);

    for (my $k=0;$k<$ro**($self->r);$k++) {
        ${$self->angle}[$k]=$self->evaluate_this_angle($k,$ro) if $k>=$ro;
        ($nx,$ny)=$self->evaluate_this_coords($k,$zoom,$nx,$ny,$di);
        $img->lineTo($nx,$ny)
    }

    my $fh_pngfile;
    open $fh_pngfile,'>',$png_filename.'.png' or do {
        push(@{$self->{message}},11);
        return
    };
        binmode $fh_pngfile;
        print $fh_pngfile $img->png;
    close $fh_pngfile;
    @{$self->angle}=@initial_angle
}
			
Abbiamo fin qui esaminato i metodi principali della classe Crypt::FNA, vediamo ora quelli di "di servizio".

Il primo che incontriamo è set_starting_angle che, come già brevemente indicato, converte nel sistema radiante gli angoli passati dallo script parent, in fase di inizializzazione dell'oggetto (metodo new). Oltre a questo il metodo restituisce il numero di angoli base della curva {F}, $ro, dato necessario al calcolo che diversamente si perderebbe durante la popolazione di $self->angle oltre al vettore @initial_angle che re-inizializza $self->angle a fine elaborazione.
sub set_starting_angle {
    my $self=shift;
    my $ro=scalar(@{$self->angle});

    my @initial_angle;

    #conversione in radianti
    for (my $k=0;$k<$ro;$k++) {
        push(@initial_angle,${$self->angle}[$k]);
        ${$self->angle}[$k]=${$self->angle}[$k]*pi/180
    }
    return ($ro,@initial_angle)
}
			
Incontriamo poi il metodo init_geometry che calcola la lunghezza del lato di della curva {F}. Questa distanza è utilizzata sia nei processi di crittografia (encrypt_file ed encrypt_scalar) che di ricostruzione del dato (decrypt_file), come pure dal metodo di disegno (make_fract). Il lato della curva, inteso anche come distanza percorsa dalla tartaruga in fase di disegno (il turtle graphics), è un dato fondamentale e strutturale, poiché influenza direttamente le coordinate dei vertici utilizzate dai vari metodi della classe FNA.
Osserviamo come il valore di sia funzione dell'ampiezza del quadrato contenitore (di lato $self->square), del numero di parametri (angoli) genitori della curva ($ro) e dell'ordine (profondità di calcolo) della stessa ($self->r). Dopo il calcolo di di ($di) viene effettuata una verifica, implementata mediante ciclo while: se di risulta essere inferiore all'intero "5", di viene incrementato moltiplicandolo per sé stesso aumentato dell'unità. Si aggiunge 1 prima del prodotto (che diversamente consisterebbe in un'elevazione al quadrato di di) poiché se di fosse inferiore od uguale ad 1, lo script entrerebbe in un loop infinito. Una volta impostato il valore di, vengono fissate le coordinate di partenza per il disegno/calcolo della curva, ponendo l'origine al centro del quadrato contenitore. Questi valori vengono restituiti al metodo invocante.
sub init_geometry {
    my $self=shift;
    my $ro=shift;

    my $di=int(($self->square/$ro**$self->r)*10000)/5000;
    while ($di<5) {
        $di=$di*(1+$di)
    }
    my $nx=$self->square/2; # ascissa iniziale
    my $ny=$self->square/2; # ordinata iniziale
    return ($nx,$ny,$di)
}
			
Arriviamo quindi ad uno dei metodi di servizio più importanti del package: crypt_fract che viene invocato da tutti i metodi (escluso new) e chiama il fondamentale evaluate_this_angle come pure evaluate_this_coords, per il calcolo degli angoli della curva e coordinate dei relativi vertici. E' un vero e proprio anello di giunzione nel processo ricorsivo.

Tra i parametri passati al metodo (oltre allo "starting" - ro, zoom, di, nx, ny con i significati ormai noti) ci sono $value_dec e $pos: questi sono il valore decimale del byte (o l'ordinale del carattere) da crittografare e il vertice della curva da cui partire per il calcolo. Vengono restituiti al metodo invocante:
  • le coordinate del vertice corrispondente al byte o carattere (come già indicato, vengono "saltati" un numero di vertici pari al valore del byte o all'ordinale del carattere oltre ad un altro vertice più un numero di ulteriori vertici pari al magic number)
  • il vertice da cui ripartire per la successiva operazione di cifratura o decifratura, pari chiaramente al valore del $pos di ingresso e del "salto" effettuato lungo la curva {F}
Vedremo subito dopo la descrizione puntuale di evaluate_this_angle ed evaluate_this_coords.
sub crypt_fract {
    my $self=shift;
    my $ro=shift;
    my $zoom=shift;
    my $di=shift;
    my $nx=shift;
    my $ny=shift;
    my $value_dec=shift;
    my $pos=shift;

    for (my $k=$ pos;$k<($pos+$value_dec);$k++) {
        ${$self->angle}[$k]=$self->evaluate_this_angle($k,$ro) if $k>=$ro;
        ($nx,$ny)=$self->evaluate_this_coords($k,$zoom,$nx,$ny,$di)
    }
    return($nx,$ny,$pos+$value_dec)
}
			
Giungiamo infine al fondamentale metodo evaluate_this_angle, il cui cuore, la relazione:

$angle[$k]=$angle[int($k/$ro)]+$angle[$k-$ro*int($k/$ro)]

è ben descritta nel paragrafo b.

Il parametro fondamentale passato al metodo è l'indice $k, riferito al vettore $self->angle, in cui verrà inserito il valore dell'angolo k-esimo. Il calcolo è basato inizialmente sui parametri genitore (gli angle impostati nel metodo new) e l'indice della curva (r impostato nello stesso metodo new) su cui poi si va ad iterare.
g ed p sono le funzioni G(k) e P(k) pure descritte al paragrafo b.
# calcolo direzione segmento e coordinate del vertice finale corrispondente

    sub evaluate_this_angle {
        my $self=shift;
        my $k=shift;
        my $ro=shift;

        return ${$self->angle}[g($k,$ro)]+${$self->angle}[p($k,$ro)]
    }

    sub g {
        my $k=shift;
        my $ro=shift;

        my $n=int($k/$ro);
        return $n
    }

    sub p {
        my $k=shift;
        my $ro=shift;

        my $n=$k-$ro*g($k,$ro);
        return $n
    }
			
evaluate_this_coords calcola le coordinate del vertice k-esimo della curva e le restituisce al metodo chiamante. Le coordinate sono utilizzate per il disegno (metodo make_fract) oppure per le funzionalità crittografiche (metodi encrypt_file, decrypt_file, encrypt_scalar).
I parametri passati alla function sono l'indice dell'angolo nel vettore $self->angle($k) ed il fattore di zoom.
Il calcolo delle coordinate viene effettuato tramite le precedenti e spostandosi sul successivo vertice, considerando il lato della curva come l'ipotenusa del triangolo rettangolo che ha come estremi i due vertici della curva. Applicando le note proprietà di seno e coseno, siamo in grado di calcolare facilmente le nuove coordinate.
Per rendere cross-platform il software e rendere agevoli le operazioni di crittografia, si è scelto di approssimare per difetto ad 8 decimali il calcolo delle coordinate.
sub evaluate_this_coords {
    my $self=shift;
    my $k=shift;
    my $zoom=shift;
    my $nx=shift;
    my $ny=shift;
    my $di=shift;

    if (!$zoom) {$zoom=1};
    $nx=int(10**8*($nx-$di*$zoom*cos(${$self->angle}[$k])))/10**8;
    $ny=int(10**8*($ny-$di*$zoom*sin(${$self->angle}[$k])))/10**8;

    return ($nx,$ny)
}
			
Completa Crypt::FNA la classe Crypt::FNA::Validation, i cui metodi implementano i controlli di coerenza nei dati di inizializzazione e del fattore di zoom oltre all'aggiornamento del contenitore dei codici di errore restituiti nell'array $self->message.


h. Considerazioni sulla robustezza dell'algoritmo

FNA è un sistema particolare di cifratura, basato sulla sostituzione di bytes/caratteri con numeri complessi (n-pla ordinata di numeri, in questo caso la coppia di coordinate) attraverso l'algoritmo generatore dei frattali {F}. La trasformazione in generale avviene sostituendo il valore ordinale, nel suo alfabeto quindi, di ciò che si trasforma con le coordinate di un vertice della curva.

FNA può considerarsi un particolare polialfabetico, la cui particolarità risiede nel fatto di avere un numero di alfabeti virtualmente illimitato, questo perché ogni codifica dipende direttamente da tutte le precedenti e segue "l'effetto farfalla". Riferendomi esplicitamente al caos deterministico, una piccola variazione nel dato in chiaro, produce grandi differenze nel risultante cifrato.

In sostanza, ciò che accade è che ogni codifica influenza la sequenza successiva di possibili crittogrammi poiché si riparte da zero (il numero di crittogrammi dipende dalla cardinalità dell'alfabeto cui appartiene cosa si cifra); ciò che è interessante ai fini crittografici è che ogni sequenza di 256 vertici della curva di {F} (nel caso si cifrino bytes), quindi l'alfabeto usato per cifrare quel determinato byte, è differente dal precedente ed il valore del byte da cifrare influenza i successivi alfabeti.

Questo ha una conseguenza importante: la modalità di attacco ad un polialfabetico non può applicarsi con successo alla crittoanalisi di un oggetto FNA.

Mostrare questo comporterà un breve excursus sulla crittografia monoalfabetica, sulla sua estensione polialfabetica, a tipi di attacco come l'analisi delle frequenze e quindi all'evidenza di inapplicabilità ad FNA.


h.1 La cifratura di Vigenére

Vigenére, padre della crittografia polialfabetica, propose di sostituire con caratteri differenti, anziché con un singolo carattere o sequenza di caratteri (codifica), occorrenze dello stesso simbolo. Un esempio chiarirà meglio il concetto.
Supponiamo di avere la frase: "ciao Anak" e di volerla crittografare con un monoalfabetico a sostituzione. Abbiamo, per esempio, la seguente tabella:
alfabeto in chiaro:   a b c d e f g h i j k l m n o p q r s t u v x y z
alfabeto cifrante:    c q f r e u k h p v x m z g y a n o l t d s i j b
			
Ebbene, con l'applicazione per sostituzione abbiamo che il crittogramma di "ciao Anak" diventa: fpcg Czcv
Qui il sistema monoalfabetico mostra tutta la sua debolezza. Uno dei metodi di crittoanalisi più diffusi è "l'analisi delle frequenze". Mediante questa tecnica, dato un testo sufficientemente lungo, si sa che in una determinata lingua, il carattere "a" compare in una certa percentuale, il carattere "b" in altra percentuale e così via. Inoltre sappiamo che, in italiano, la "q" è seguita dalla "u", che le parole terminano solitamente con una vocale ecc. ecc.. Mediante l'analisi delle frequenze si è in grado di decifrare dapprima qualche carattere e poi, man mano e sempre più rapidamente, tutti gli altri mentre si forma il senso compiuto.
Già in una frase breve come "ciao Anak" sappiamo che uno stesso carattere, la "a" nella fattispecie, compare per ben tre volte. Con un testo più lungo la crittoanalisi sarebbe semplice da eseguire.
Per ovviare a questa falla di sicurezza, l'Alberti utilizzò più di un alfabeto cifrante, servendosi del primo con il primo carattere, del secondo per il secondo e così via alternando gli alfabeti disponibili.
Ad esempio, aggiungendo un secondo alfabeto ante a quello già visto, abbiamo la seguente tabella:
alfabeto in chiaro:   a b c d e f g h i j k l m n o p q r s t u v x y z
I alfabeto cifrante:  c q f r e u k h p v x m z g y a n o l t d s i j b
II alfabeto cifrante: e z o p c n d h t l u j b m v r k s y a q g x i f
			
Questo è un polialfabetico e la frase "ciao Anak" diviene: ftcv Cmcu. In questo caso, la "a" ancora compare tre volte, ma in un testo più lungo non sarebbe generalmente così.
Vigenére, riprese il lavoro dell'Alberti, portando a piena realizzazione un cifrario polialfabetico.
Quello realizzato da Vigenére utilizza 26 alfabeti cifranti, con uno "spostamento di Cesare" pari ad uno rispetto a quello presente alla riga immediatamente superiore. Per "spostamento di Cesare" si intende la traslazione dell'alfabeto di un certo numero di posizioni.

La tabella di Vigenére è riportata di seguito:
chiaro:               a b c d e f g h i j k l m n o p q r s t u v w x y z
-------------------------------------------------------------------------
1:                    b c d e f g h i j k l m n o p q r s t u v w x y z a
2:                    c d e f g h i j k l m n o p q r s t u v w x y z a b
3:                    d e f g h i j k l m n o p q r s t u v w x y z a b c
4:                    e f g h i j k l m n o p q r s t u v w x y z a b c d
5:                    f g h i j k l m n o p q r s t u v w x y z a b c d e
6:                    g h i j k l m n o p q r s t u v w x y z a b c d e f
7:                    h i j k l m n o p q r s t u v w x y z a b c d e f g
8:                    i j k l m n o p q r s t u v w x y z a b c d e f g h
9:                    j k l m n o p q r s t u v w x y z a b c d e f g h i
10:                   k l m n o p q r s t u v w x y z a b c d e f g h i j
11:                   l m n o p q r s t u v w x y z a b c d e f g h i j k
12:                   m n o p q r s t u v w x y z a b c d e f g h i j k l
13:                   n o p q r s t u v w x y z a b c d e f g h i j k l m
14:                   o p q r s t u v w x y z a b c d e f g h i j k l m n
15:                   p q r s t u v w x y z a b c d e f g h i j k l m n o
16:                   q r s t u v w x y z a b c d e f g h i j k l m n o p
17:                   r s t u v w x y z a b c d e f g h i j k l m n o p q
18:                   s t u v w x y z a b c d e f g h i j k l m n o p q r
19:                   t u v w x y z a b c d e f g h i j k l m n o p q r s
20:                   u v w x y z a b c d e f g h i j k l m n o p q r s t
21:                   v w x y z a b c d e f g h i j k l m n o p q r s t u
22:                   w x y z a b c d e f g h i j k l m n o p q r s t u v
23:                   x y z a b c d e f g h i j k l m n o p q r s t u v w
24:                   y z a b c d e f g h i j k l m n o p q r s t u v w x
25:                   z a b c d e f g h i j k l m n o p q r s t u v w x y
26:                   a b c d e f g h i j k l m n o p q r s t u v w x y z
			
Ebbene, per cifrare un messaggio mediante questo polialfabetico, occorre scegliere una chiave, supponiamo che sia: bruto.

Supponiamo inoltre che il testo da cifrare sia: "Anak va in campagna".
Riportiamo la chiave sul testo in chiaro, senza spazi:
b r u t o b r u t o b r u t o b
a n a k v a i n c a m p a g n a
			
Il primo carattere da cifrare è la "a". Vediamo che corrisponde all'alfabeto 1, che inizia per "b". Secondo questo alfabeto, il crittogramma di "a" è "b".
Il secondo carattere da cifrare è "n". Vediamo che corrisponde all'alfabeto che inizia per "r". In questo alfabeto il crittogramma di "n" è "e".
Proseguendo e scegliendo sempre l'alfabeto corrispondente (in base alla chiave scelta, utilizziamo l'alfabeto che inizia per quella lettera), otteniamo il crittogramma di "Anak va in campagna", secondo il polialfabetico di Vigenére in chiave "bruto" che è:

"beuc jb zo vonguzbb".

Vediamo che la "a" è cifrata mediante caratteri differenti, ad es. "b", "u". Inoltre caratteri differenti, vengono cifrati con lo stesso simbolo: ad esempio "na" finale diventa "bb".

Si comprende come l'analisi delle occorrenze, chiave di volta di un monoalfabetico, risulti essere notevolmente ostacolata.


h.2 La crittoanalisi di Babbage

Noi informatici (e non solo) dobbiamo molto a Charles Babbage, per le sue rivoluzionarie idee relative alla "macchina delle differenze", precorritrici del moderno calcolatore ma egli si interessò, tra l'altro, anche alla crittoanalisi del polialfabetico di Vigenére: nel XIX secolo, epoca di Babbage, era ancora inespugnata la ben nota cifratura risalente al XVI secolo.

Un'emblematica metafora della risoluzione di un crittogramma ben fatto è la scalata di una parete liscia: occorre utilizzare ogni minima asperità o crepa. Nel sistema monoalfabetico ci si aggrappa alla frequenza con cui abbiamo le ripetizioni simboliche, essendo questa caratteristica non appiattita da questo tipo di cifratura. Le differenze di frequenza però, nel sistema di Vigenére, risultano particolarmente ridotte, poiché la chiave "chiama" diversi alfabeti cifranti. La parete sembra dunque liscia.

Se però ampliamo il nostro raggio visuale ed anziché ricercare frequenze sul carattere, le cerchiamo nella ripetizione di parole, il quadro cambia e di molto.

In un testo italiano, ad esempio, la parola "non" oppure l'articolo "una" saranno quasi certamente presenti. Trovandoci di fronte ad un testo criptato mediante il polialfabetico di Vigenére, supponiamo che "non" sia cifrato in quattro modi: "dst", "ekc", "pxw", "nru". Dunque, se la parola "non" viene riportata nel testo in chiaro più di quattro volte, almeno una ripetizione sarà inevitabile. Ecco l'asperità trovata da Babbage.

Quando questo avviene, leggiamo la distanza tra le due ripetizioni e già abbiamo un'importante informazione: abbiamo identificato un multiplo esatto della lunghezza della chiave.

Il primo passo è dunque trovare le ripetizioni e le distanze tra queste, riportandole in una tabella.

Il secondo passo è scomporre in fattori primi le delle distanze e verificare il comune denominatore, in modo da identificare la lunghezza della chiave.

Ad es.
ripetizione distanza  possibile lunghezza della chiave
DSGR        12        2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
HBE         18        2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
DFTTGH      6         2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
			
Le possibili chiavi hanno dunque una lunghezza di 2, 3 oppure 6 caratteri. Scartiamo quelle da 2 e 3 caratteri perché sarebbero troppo deboli e puntiamo, ad es. sulla chiave da 6.

Il terzo passo consiste nell'identificare le lettere della chiave. Indichiamo temporaneamente le lettere sue costituenti con i simboli: C1, C2, C3, C4, C5, C6. La cifratura di Vigeneré prevede che il primo carattere del testo in chiaro sia sostituito dal corrispondente carattere dell'alfabeto che inizia per C1, quindi in sé non differisce da una sostituzione monoalfabetica. Analogamente il secondo carattere sarà sostituito dal corrispondente nell'alfabeto che inizia per C2 e così via fino al settimo carattere. Qui torneremo ad utilizzare l'alfabeto C1: quindi Babbage non fece altro che scomporre la cifratura polialfabetica di Vigenére in altre monoalfabetiche, in numero eguale a quello identificato dalla lunghezza della chiave, su cui andare a scomporre il testo cifrato e su cui applicare l'analisi delle frequenze, come su un qualunque altro monoalfabetico.


h.3 Evoluzione del sistema di Vigenére: chiavi lunghe come il messaggio

Il punto debole, come visto, è nella ciclicità della crittografia di Vigenére. Concettualmente, un incremento della lunghezza della chiave produce una complessità maggiore nell'analisi delle ricorrenze (passo 1 del par. h.2). Supponiamo di avere una chiave lunga 5 caratteri ed un testo costituito da 1000 caratteri. L'esempio è valido in generale se adoperiamo, mentalmente, la sostituzione "carattere" con "simbolo" nel suo particolare alfabeto. Dovremmo applicare l'analisi delle frequenze a gruppi di 200 caratteri, compito non particolarmente impegnativo. Ben diversa è la situazione in cui la chiave sia da 20 caratteri, per cui avremmo 20 gruppi di 50 lettere, significativamente più complessi da decifrare. Se infine la chiave fosse di 1000 caratteri, dovremmo applicare l'analisi delle frequenze a 1000 gruppi di 1 solo carattere e qui il metodo di Babbage cederebbe il passo, non potendo essere adeguatamente applicato (non avremmo ricorrenze utili alla decifrazione).

Sembra dunque, ancora una volta, che non ci siano asperità cui appigliarsi ma non è così. Partiamo dal presupposto che in ogni lingua ci sono parole molto comuni, che ricorrono spesso (articoli, persone nei verbi, ecc.). Ragioniamo ancora una volta sulla parola "non" oppure sulla congiunzione "che" o anche sul pronome "noi". La tecnica di crittoanalisi consiste nel collocare la nostra sequenza di test presumibilmente presente (non, che, noi, ecc...), in vari punti scelti casualmente (nel testo in chiaro che si tenta di ricostruire) e stabilire la chiave che trasformerebbe la nostra stringa di test nel crittogramma analizzato.

Ad esempio, supponendo che la parola "noi" sia la prima del testo in chiaro, leggiamo il corrispondente crittogramma (poiché abbiamo il testo criptato). Seguendo la tavola di Vigenére verifichiamo quali sono le lettere iniziali degli alfabeti coinvolti, supponiamo CRA (non ho verificato ma non ha importanza ai fini del ragionamento). La chiave potrebbe quindi iniziare con le lettere "cra" (supporremmo "cranio").

Ricollochiamo "noi" in altri punti, deducendo, di volta in volta, le corrispondenti lettere della chiave. Ipotizziamo che la chiave abbia un significato in italiano, quindi eventuali chiavi con "xiw" oppure "sxl" saranno da scartare e concentriamo l'attenzione su quelle con un senso. Proviamo poi ad aggiungere, nel testo chiaro, "che" prima e dopo le sequenze di "noi" che hanno dato senso compiuto e verifichiamo, ancora una volta, eventuali convergenze di senso compiuto.

Procedendo con i tentativi, per quanto il procedimento sia senz'altro laborioso, i risultati giungono nonostante la chiave sia lunga come il testo cifrato.

La crittografia di Vigenére con una chiave sensata lunga quanto il messaggio risulta quindi essere tenace ma non inespugnabile.


h.4 Chiavi lunghe e casuali: la cifratura a blocco monouso

Abbiamo visto che l'attacco verte sull'analisi del senso compiuto della chiave, ma se questa fosse casuale (ovvero propriamente caotica)? L'idea di applicare chiavi lunghe e casuali alla cifratura di Vigenére, venne al maggiore Mauborgne durante la Prima Guerra Mondiale. Altra particolarità è che le chiavi venivano utilizzate una sola volta e poi distrutte. I due soggetti che scambiavano informazioni disponevano di una pila di fogli identici in identica successione ed ogni foglio riportava una sequenza casuale di caratteri. I messaggi venivano, di volta in volti, criptati con il foglio in testa alla pila, foglio che veniva poi distrutto. Per questa particolarità il metodo venne detto della "cifratura a blocco monouso".

Il metodo resiste all'analisi delle frequenze, perché è un polialfabetico
Il metodo resiste al metodo di Babbage, perché la chiave è lunga come il messaggio
Il metodo resiste anche al metodo del par. h.3 perché la chiave non ha senso

In effetti è possibile dimostrare matematicamente che questo metodo è indecifrabile: garantisce assoluta segretezza.

Ci si potrebbe allora chiedere perché non viene utilizzato massicciamente: ha un costo di gestione notevole tra la generazione delle chiavi, loro distribuzione, applicazione, ecc. Non andrebbe certamente bene per una mole notevole di dati che viene scambiata frequentemente.


h.5 I polialfabetici moderni: numero di alfabeti molto maggiore di 26

Una maggiore complessità (rispetto a Vigenére) ma una migliore applicabilità (rispetto al blocco monouso) la si ottiene utilizzando un numero di alfabeti casuali ed in numero molto maggiore di 26. Questo è il caso della macchina Enigma, utilizzata durante la II Guerra Mondiale, che possiamo immaginare come un sistema di Vigenére cui sono stati somministrati degli steroidi :). Ma la ciclicità, per quanto rarefatta, è presente anche in questo sistema ed è dunque attaccabile.


h.6 Considerazioni sull'attacco ad FNA

Premesso che la chiave di FNA è, in senso stretto, data dalle direzioni di inizializzazione Ro, di, magic e square, osservandolo come polialfabetico e considerando l'algoritmo frattale come un generatore di alfabeti, possiamo dire che ha in sé i vantaggi di una chiave lunga come il messaggio ed apparentemente casuale (nel senso che è notevolmente irregolare, non linearizzabile) similmente al caso della cifratura a blocco monouso, inoltre presenta un numero di alfabeti cifranti virtualmente illimitato e quindi, possiamo dire, pari al numero di caratteri del messaggio in chiaro. Parimenti non soffre della difficoltà di applicazione insita nel sistema a blocco monouso e si presta molto semplicemente ad operazioni di ipercrittografia (cifrare un dato già cifrato).

Vediamo perché, a mio avviso, è lecita questa osservazione:

Alfabeto

L'alfabeto utilizzato è lungo al più quanto la cardinalità dell'alfabeto con cui è espresso il dato in chiaro che si cifra. Nel caso di bytes è costituito al più da 256 coppie di coordinate di vertici. Gli alfabeti sono inoltre apparentemente casuali, poiché la successione degli angoli, di derivazione frattale, è notevolmente irregolare e questa successione influenza direttamente le coordinate. Una volta cifrato un byte, si procede alla cifratura del successivo: l'alfabeto "riparte", poiché una volta cifrato un byte, si considerano le coordinate del successivo vertice di {F} come il simbolo di ordinalità 1 nel nuovo alfabeto di cardinalità, al più, pari alla cardinalità dell'alfabeto con cui è espresso il dato da cifrare. I successivi alfabeti sono sempre differenti e dipendenti da tutti i dati in chiaro precedentemente cifrati.

Chiave

La chiave, vista come ordinale della successione di alfabeti da utilizzare, è lunga come il messaggio:

chiave 1, primo dato da cifrare: primo alfabeto -> influenza l'alfabeto successivo
chiave 2, secondo dato da cifrare: secondo alfabeto -> influenza l'alfabeto successivo
...
chiave n-1, n-1 esimo dato da cifrare: n-1 esimo alfabeto -> influenza l'alfabeto n-esimo

In quest'ottica abbiamo dunque una chiave lunga come il dato da cifrare ed un numero di alfabeti pari al numero di elementi costituenti il dato in chiaro.

Da notare che un eventuale attacco di forza bruta richiederebbe più tempo di quello necessario alla morte termica del nostro Universo.

Ad esempio, se partissimo dall'ipotesi di un numero di angoli base Ro=3, non avremmo comunque idea del valore di questi angoli. Consideriamo che il valore di un angolo è un numero compreso tra 0 e 360, consideriamo inoltre che non abbiamo idea di quanti decimali siano stati utilizzati per quegli angolo. Se avessimo 8 decimali il numero di angoli da testare sarebbe:

(99'999'999 * 360)**3 = 46655998600320013996799953344000 possibili combinazioni

Se potessimo verificare una combinazione al secondo (ipotesi estremamente ottimistica), occorrerebbero un numero di anni pari a: 1 479 452 010 410 959 347 945 204

E se gli angoli fossero 4?

(99'999'999 * 360)**4 = 1,679615932815361007769593281536e+42 possibili combinazioni

Tralascio il calcolo per un numero di angoli pari ad 8 o 9...

Inoltre ci sono altre variabili, come il magic number, che rendono oltremodo arduo individuare gli angoli successivi (nel tentativo di scoprire la base) oltre all'ordine della curva su cui si va a crittografare.

Consideriamo inoltre le possibilità di ipercrittografia, che ampliano esponenzialmente le combinazioni da dover identificare, come visto, anche con pochi angoli base...


i. Punti deboli e contromisure

i.1 Calcolo in virgola mobile

Il punto debole della cifratura FNA è, se vogliamo, il sistema di calcolo in virgola mobile, tuttavia esterno all'algoritmo e riguardante il modo in cui la macchina rappresenta i numeri. Test condotti su diversi PC con Windows XP, Vista, Linux, con processori a 32 e 64 bit hanno prodotto risultati soddisfacenti limitando ad 8 il numero di decimali: su una macchina x86, il calcolo di pi => 4 * atan2(1, 1) fornisce 14 decimali, per cui l'uso di 8, con una differenza di 6 ordini di grandezza, ritengo metta al riparo dalle approssimazioni ma devo dire che i test sono attualmente ancora pochi (stante la giovinezza dell'implementazione degli algoritmi).


i.2 Distanza tra due vertici successivi

Aumentando la profondità di calcolo (parametro r), la lunghezza del segmento approssimante di si riduce sempre più:

limr->+inf di = 0

E' chiaro che, ben prima di giungere a 0, la macchina fisica che elabora non sarà in grado di distinguere tra due punti distinti. Per ovviare a questo il metodo init_geometry valuta il valore di e lo incrementa se inferiore ad un valore soglia.


i.3 Lacci di {F}

In questa eventualità c'è una probabilità non nulla che due vertici (e quindi infiniti) possano sovrapporsi, rendendo impossibile la decodifica del file criptato. Ad esempio, con una base Ro={-30,60,45,110} abbiamo:


Ebbene, prima di proseguire nel discorso, occorre fornire le seguenti:

Definizione 1
Si definisce base di una curva Î {F} l'insieme delle inclinazioni {Ro}

Definizione 2
Si definisce ramo di una curva Î {F}, la spezzata relativa al calcolo eseguito secondo l'algoritmo di costruzione sulla base o sua combinazione (v. par b).


Ho quindi cercato una condizione sufficiente affinché questo non avvenga e sono giunto al seguente:

Teorema

Ipotesi: sia data la base di {F}={x1, x2,..., xn} : max(x)-min(x) < p/4

Tesi: l'ipotesi è sufficiente affinché l'insieme dei punti della curva {F} sia in corrispondenza biunivoca con un sottoinsieme di punti del piano

Dimostrazione:
Ragioniamo sui singoli segmenti del ramo e procediamo per induzione.

Riportiamo un sistema di assi cartesiani con una circonferenza di raggio D=OD e centro nell'origine del sistema di riferimento.

Supponiamo inizialmente che {Ro} sia contenuta strettamente nel I quadrante.

La spezzata relativa alla costruzione del ramo - e della curva {F} in generale -, non è intrecciata se i raggi vettori dei vertici (D, D',...) costituiscono una funzione strettamente crescente.

Vediamo perché: applicando la proprietà di definizione di seno e coseno al triangolo rettangolo ODC per calcolare i cateti, de facto ciò che facciamo è calcolare le coordinate del vertice D della curva {F}: (OD cos(x), OD sin (x)).

Sappiamo infatti che, dato un triangolo rettangolo, un cateto (ascissa) è uguale all'ipotenusa moltiplicata per il coseno dell'angolo adiacente e l'altro (ordinata) è uguale all'ipotenusa moltiplicata per il seno dell'angolo opposto. L'angolo cui ci riferiamo è l'inclinazione x Î {Ro}.

Reiteriamo il ragionamento sul vertice successivo D' (OD=DD'), in questo caso possiamo esprimere le coordinate mediante la somma: (OD cos(x)+DD'cos(x'),OD sin(x)+DD' sin(x')). Ricordiamo che OD=DD' perché è la lunghezza del ramo di curva {F}.

Da notare: fintanto che x è strettamente nel primo quadrante, le funzioni seno e coseno hanno un codominio positivo.

Mettendo in evidenza la distanza D=OD nel prodotto, in generale abbiamo le seguenti sommatorie:

Le due sommatorie sono divergenti, poiché i termini sono tutti, necessariamente, positivi, ovvero:

Le sommatorie esprimono, per ogni n, le coordinate del vertice della curva di {F}.

Leggendo il raggio vettore del vertice n-esimo come l'ipotenusa del triangolo rettangolo che ha per cateti le due sommatorie, applicando il teorema di Pitagora possiamo esprimerlo come la radice quadrata della somma dei quadrati delle sommatorie; ricordiamo che la funzione somma di due funzioni strettamente crescenti è strettamente crescente e che la radice quadrata di una funzione strettamente crescente è anch'essa strettamente crescente:

Ovvero:

Si verifica banalmente che ODn diverge per n che tende a +µ

Ciò ha una conseguenza importante ai fini dello sviluppo di {F}: fintanto che le direzioni sono comprese nell'intervallo aperto (0, p/4), la curva non può intrecciarsi poiché ogni vertice è, rispetto al precedente, più distante dall'origine degli assi: dati cioè due qualsiasi vertici consecutivi di {F} D' e D'', OD'
Se infatti la curva tendesse ad intrecciarsi, dovremmo avere almeno un OD'>OD'' (e quindi ce ne sarebbero infiniti per la proprietà di autosimilitudine di {F}) ma questo contravverrebbe alla caratteristica di stretta crescenza della funzione esprimente il raggio vettore (1) (nelle condizioni dell'intervallo aperto (0, p/4)), pervenendo ad un assurdo.

Questo risultato, valido per il primo semiquadrante, è generalizzabile.

Osserviamo infatti che, ruotando il sistema di riferimento di un angolo a (il che equivale a sommare la stessa quantità a ad ogni elemento della base Ro), otteniamo la stessa curva {F} ma ruotata dell'angolo a per cui la geometria intrinseca della curva non cambia.

E' possibile verificare ciò con il seguente ragionamento.

I parametri della base {x1, x2,..., xn} sono le inclinazioni dei singoli segmenti costituenti il ramo. Ragioniamo sulla geometria intrinseca del ramo visto nella sua interezza. Per fare questo occorre dedurre, dalle inclinazioni dei singoli segmenti, gli angoli di incidenza delle coppie successive.

Dalla geometria analitica, sappiamo che, date due rette di coefficiente angolare m1=tgx1 ed m2=tg x2, la tangente dell'angolo di incidenza tra loro è:

tg q =(m1-m2)/(1+m1m2)

quindi l'angolo di incidenza individuato da due segmenti successivi del ramo di curva {F} definita da {x1, x2,..., xn} è:

q = arctg[(m1-m2)/(1+m1m2)]=arctg[(tg x1-tg x2)/(1+tg x1*tg x2)]

Abbiamo quindi un'applicazione suriettiva tra le inclinazioni dei singoli segmenti del ramo di curva definito dalla base e gli angoli di incidenza dei segmenti successivi, possiamo quindi scrivere:

f:{x} -> {q} ; " a Î {a}, $ q Î {q} : f(x) = q

Che sia suriettiva dovevamo aspettarcelo poiché passiamo da una proprietà assoluta, come il coefficiente angolare del fascio di rette cui appartiene il segmento di ramo, ad una proprietà intrinseca come l'angolo di incidenza di due di questi ovvero esistono infinite rette incidenti che daranno luogo a quell'angolo di incidenza. Questo dimostra che ruotando il ramo di un certo angolo, la geometria di {F} non cambia.

{a1, a2,..., an} | {b1, b2,..., bn} -> {q1, q2,..., qn-1}

Ora, applicando l'algoritmo di costruzione di {F} ad un certo ordine, avremo un determinato numero di rami, dipendente dalla profondità di calcolo scelta.

Giacchè i singoli rami, sono combinazione dei precedenti, secondo l'algoritmo di costruzione di {F} (v. par. b), i rami ad un certo ordine, sono tutti uguali tra loro ma ruotati. Quindi ogni ramo rispetterà, necessariamente, la condizione max(x)-min(x) < p/4 (per x appartenente a quel ramo). E quindi la lunghezza dei raggi polari comunque aumenterà in quel ramo. Ma come si comporteranno i vertici di ogni ramo rispetto al precedente?
Diciamo quindi A e B due rami consecutivi. Per l'ipotesi e per l'algoritmo di costruzione di {F}, un ramo potrà ruotare, rispetto al precedente, al più di una quantità strettamente inferiore a p/4 (compresa nell'intervallo aperto 0, p/4).

Ebbene, in questo caso, ogni ramo successivo avrà, rispetto al sistema di riferimento con origine nel primo vertice del ramo precedente ed orientato in modo che le direzioni di questo ramo siano comprese in (0,p/4), al più direzioni comprese nell'intervallo aperto (p/4, p/2). In queste condizioni, calcolando il raggio vettore di ciascuno dei vertici del ramo B, continuiamo ad avere valori del seno e coseno strettamente positivi, quindi, inequivocabilmente, il raggio vettore sarà comunque strettamente crescente.

In definitiva, indicando con A e B due rami successivi, indicando con A(n) il raggio vettore del generico vertice del ramo A, rispetto al sistema di riferimento su A, B(m) il raggio vettore del generico vertice del ramo B rispetto allo stesso sistema su A avremo B(m)>A(n) " (m,n). Reiteriamo questo ragionamento per ogni coppia di rami applicando la proprietà transitiva: dato C(p) successivo a B(m), se C(p)>B(m) " (p,m), allora C(p)>A(n) " (p,n). La tesi è dunque dimostrata se le direzioni della base Ro sono contenute strettamente in un angolo di apertura (0,p/4).


Applicando questa condizione ogni vertice della curva {F}, sensibile per le applicazioni FNA, sarà distinto da tutti gli altri, garantendo algoritmicamente la reversibilità dell'operazione di cifratura. Non ho voluto imporre la condizione da codice, poiché (congettura) è estremamente raro che la sovrapposizione dei vertici avvenga e preferisco lasciare libero il programmatore di impostare come vuole l'oggetto.

In ogni caso ricordo al lettore che FNA è un algoritmo molto giovane ed ancora non adeguatamente testato, per cui eviterei di salvare i dati di accesso ad un server contenente carte di credito o la ricetta della pizza se non dopo attenta fase di testing e ricordando che Anak vi aveva avvertiti :)


l. Esempio di file cifrato

La cifratura di un testo è l'esempio più semplice. In ogni caso il dato cifrato avrà un aspetto simile al seguente:

-806.16701617 4296.950584 -1163.3897453 4378.30613408 -1253.81513894 4361.33265404 -1502.80711437 4636.89514523 -1371.10557976 4745.56050632 -1230.07749379 4968.48069209 -1338.39851924 5248.88785964 -917.21821497 5429.36645491 -773.44592091 5696.62911696 -692.72801005 5885.46154004 -988.27897105 5885.418198 -1248.99379997 6171.71101067 -830.48330143 6377.55135044 -768.07453852 6493.40995382 -290.38619797 6703.79926248 -101.38261857 6641.39653224 329.01095794 6547.35282987 491.23460593 6672.15350589 682.15153937 6767.07332641 951.17643798 7125.45527124 844.47157379 7301.13742586 616.45930112 7293.99200882 844.26353513 7262.78340711 1211.3200562 7315.25004987 1474.41515451 7121.21394711 1951.75973992 7224.47233263 2176.20365976 6962.04147204 2547.88708591 6998.13655185 2781.82594976 6972.85084038 3056.52905252 7371.28466715 3037.53030053 7569.06437014 3048.49593738 7320.32093005 3389.66342779 7357.81470144 3676.23526579 7708.87987244 3755.43863759 7814.8354795 3435.5290489 8296.58426972 3441.10117125 8627.97877198 3412.2773365 8623.6058585 3362.87465115 8767.32280898 3260.65143202 8583.97947961 2890.71868372 8474.68032897 2726.83436885 8650.05588533 2718.8481018 9045.95222039 2669.00976899 9254.66114943 2644.06562016 9103.68182141 3127.66020707 9113.43039278 3191.47856428 9188.88465234 3207.82184971 9202.57034881 3478.33454467 8945.6121183 3832.00806714 8945.62804071 4080.86384299 9320.62189286 4289.2595779 9439.78195562 4021.13116501 9644.36385638 4311.34336432 9554.3477728 4679.21568268 9563.22563256 4833.53132591 9641.37582295 4740.32174942 9910.49435765 4448.89751812 10157.37473936 4273.26989922 10265.73224722 4218.00573474 10553.33210292 4076.79496626 10732.34891747 3830.35537312 10613.81591903 3785.18217462 10386.70855427 3666.99726881 10332.12423113 3476.25444621 10694.76481321 3296.35920314 10804.77625983 3060.88089069 11346.01346391 3007.91070428 11444.10666595 2765.46825422 11911.74931522 2771.84792598 12217.75488876 2730.08778903 12432.33422506 2649.22698242 12307.67655488 2179.40416992 12145.89439835 2279.94226546 12105.79701773 2047.78623478 12604.70024151 2134.4739565 12762.57334939 1895.30449332 12619.14996241 1526.25794611 12313.79872918 1561.04359063 12060.9258984 1204.52077789 11904.48474151 1011.49806809 11625.32850092 896.84643331 11430.88088124 1209.72754463 11427.67243264 1445.63793588 11243.03320502 1007.30448881 10928.79303642 1003.20372969 11106.6718373 727.277592 11309.24215182 746.61712944 11277.92048174 456.49186043 11787.41111026 379.87776238 11984.62921992 472.96046189 12119.46099475 271.54432816 12290.78107589 131.44318208 12041.65370154 -185.56330396 11969.55220752 -362.05288691 12482.5830701 -341.54866388 12667.26155564 -569.28493619 12713.64846923 -64.33299485 12943.2140129 81.27356191 13019.44204611 147.56898066 13022.49444093 164.25575022 12985.9421937 268.40820278 12959.25678237 340.85361046 12834.62587482 655.10255396 13107.14828548 787.02996554 13193.07173886 968.82889409 13538.63491321 782.28156576 13708.99085026 651.02712535 13641.2511074 1146.46031463 13801.14680503 1335.1667027 13814.07705932 1460.7571724 13657.7915972 1930.3459895 13774.5940179 2182.56639049 13757.11572068 2318.75940315 13442.29781914 2699.35138063 13508.48407466 2986.83474423 13895.75497603 3175.85032209 14026.42718015 3148.55734429 14185.73955913 2925.38038235 13994.10082526 3317.97162054 14218.34519187 3508.85497438 14237.59048027 3657.8892824 14560.76910337 3482.01884376 14744.40274001 3325.79263684 15252.3479299 3375.63991432 15427.10319156 3232.15080581 15469.75096938 3197.63508244 15524.31257472 3222.44604483 15558.67734253 3148.9766895 15533.82448786 3075.88234018 15469.59718114 3034.19942968 15435.51054822 3049.67239197 15341.05688822 2675.60440963 15084.87977657 2641.04941197 15504.02048582 2622.61949578 15624.02869931 2356.17334501 15862.13614664 2637.25377583 15753.30239568 2821.29953661 15942.0716232 2887.20511594 15830.57841152 3362.86769934 15753.3366018 3778.53257215 15803.08388018 3977.97367908 16228.3197886 3980.02703929 16411.65077553 3802.04088869 16280.26523909 4147.76319092 16255.68368756 4282.58798084 16386.01193565 4483.19232186 16163.27854873 4904.96243337 16172.58116953 5091.55561607 16151.66255094 5223.36254584 15899.29896482 5413.48348371 15558.60745651 5695.62968696 15560.16983316 5920.88359976 15827.47965379 6223.49435265

Che corrisponde alla frase:

Programmare non è la causa ma la conseguenza dell'implementazione del mio
pensiero in un linguaggio comprensibile al computer

Hacking is not cracking
Mario Rossano aka Anak


Come si vede, il risultato dell'operazione di cifratura è un file ASCII ed in quanto tale è suscettibile di notevole compressione: è dunque sufficiente interporre a monte dell'algoritmo di decifrazione, un'implementazione di quello scelto per la decompressione ed a valle di quello di cifratura, simmetrico algoritmo di compressione.

Così facendo potrà contenersi, sensibilmente, la dimensione delle informazioni, con particolare riguardo alle applicazioni in rete, dove la velocità nello scambio dei dati, oltre che dell'algoritmo (ma è questa un'esigenza generale), è un fattore cruciale.

Esula dai limiti del presente articolo, che ha l'obiettivo di descrivere FNA, detta implementazione.


m. Conclusioni

Resistance is futile

the Borg - Star Trek

Le curve frattali ben si addicono, molto più di quelle della geometria euclidea, a descrivere i fenomeni naturali: il percorso seguito da un fulmine, una linea costiera, la rotazione della Terra intorno al suo asse, l'alveo di un fiume, il ramo di un albero, il moto caotico di un fluido e così via. Potremmo effettuare studi ed analisi per verificare la convergenza di numerosissimi fenomeni agli angoli genitore Ro e, quindi, allo sviluppo della particolare curva {F}, ottenendo così un prezioso strumento di predizione in tutti quei sistemi fisici "lontani dall'equilibrio e che non raggiungono mai l'equilibrio" (sistemi fisici aperti e complessi, come ad es. l'atmosfera).

Tuttavia i campi di applicazione sono molteplici e spaziano oltre la fondamentale fisica, ad esempio come nel caso dell'andamento dei mercati borsistici. Si è visto che, tendenzialmente, la curva di variazione di una settimana è molto simile a quella mensile: abbiamo quindi una simmetria rispetto alla scala per cui c'è un notevole grado di analogia con la proprietà di autosimilitudine dei frattali. Qui ho implementato le curve in un sistema di crittografia frattale, argomento nuovo ed oggi particolarmente sensibile all'interesse di molti per le sue possibilità di applicazione in un mondo come il nostro, in cui la protezione dei dati è un requisito ed un'esigenza sempre più ineludibile anzi inevitabile.


Ringraziamenti

Diverse sono le persone che devo ringraziare, la cui frequentazione sul canale irc Perl.it su freenode, mi ha aiutato durante questo lavoro. E quindi:
  • grazie a Peppe per i suggerimenti iniziali sulla programmazione OOP in Perl
  • un ringraziamento anche a Trone con cui ho condiviso dubbi ed incertezze, ragionando insieme sull'OOP
  • ad Oha, con cui ho inizialmente discusso dell'algoritmo generatore di {F}
  • a dree, per i consigli sui file handle e la piena disponibilità dimostrata
  • ad Arthas, per l'indicazione relativa all'inclusione condizionale di packages
  • a larsen, per i suggerimenti di stile e per avermi invogliato a scrivere la parte dell'articolo relativa alla robustezza della cifratura FNA
  • a Dakkar, verso cui sento di muovere un ringraziamento particolare per la critica, sempre costruttiva, precisa e puntuale e lo sprone a migliorare il codice della libreria (e a ridurre il testo - ma non ci sei riuscito completamente). Senza di te ci sarebbe stata qualche variabile globale di troppo, qualche condizione sufficiente in meno ed un codice sicuramente meno leggibile
Infine devo ringraziare Giovanna, la mia luce, senza la quale non sarebbe esistito FNA, che, nonostante non abbia dimestichezza con matematica e programmazione, ha riletto pazientemente diverse volte questo articolo, rendendolo senz'altro più scorrevole.

Il mio grazie di cuore va a tutti voi, poiché questo lavoro non sarebbe stato realizzato nella forma attuale, senza il vostro supporto :)

"pace, lunga vita e prosperità"
Mario Rossano aka Anak

P.S. e se proprio volete divertirvi, mi raccomando l'audio :D FNA Movieclip (richiede Adobe Flash)


Creative Commons License
FNA Fractal Numerical Algorithm for a new cryptography technology by Mario Rossano is licensed under a Creative Commons Attribuzione-Non commerciale-Condividi allo stesso modo 2.5 Italia License.
Permissions beyond the scope of this license may be available at software@netlogicalab.com