Oggi lo smartphone fa di tutto: Web, cloud, social networking, multimedia, mail, banking, localizzazione GPS, ticketing, eccetera, eccetera, eccetera…ah, dimenticavo, all’occorrenza può anche fungere da telefono! : )
Dentro questi aggeggi si è accumulata una mole enorme di funzionalità ed informazioni personali che va opportunamente protetta e resa disponibile solo al proprietario del dispositivo. La sicurezza degli smartphone è un argomento molto vasto e complesso, per cui oggi vi vorrei parlare solo di un piccolo aspetto (ma basilare!) di questo tema. Tenetevi forte, anzi fortissimo, rimarrete a bocca aperta…Parliamo del famosissimo ***Rullo di tamburi***:
blocco schermo con codice.
…no?…beh sì, in effetti forse non è la caratteristica più cool che ci sia, me ne rendo conto, ma si tratta di quella funzione, un po’ vintage, che protegge l’accesso allo smartphone quando non è in uso.
Ma quanto è sicura?
Scoprilo nel nuovo shottino e, se non ti basta ancora, leggi qui.
Qual è la nerdata questa volta?
Solitamente, quando uno smartphone non è in uso, lo schermo viene bloccato. Così facendo si evitano azioni involontarie sul touchscreen, inoltre, se vogliamo impedire che chiunque abbia un accesso diretto al nostro dispositivo, possiamo anche settare un codice di sblocco da inserire prima di ogni utilizzo.
Guardiamo questo codice più da vicino: come funziona? Quale tipologia di blocco è preferibile?
Oggi non voglio parlarvi della sicurezza rispetto alle metodologie software che potrebbero permettere di craccare il dispositivo a chi ce lo avesse, di fatto, rubato. Quello che voglio raccontarvi è la sicurezza contro quegli attacchi poco “romantici”, quelli ruspanti, brutali ma evergreen, che permettono di risalire al codice ad insaputa del proprietario del dispositivo e di usarlo senza traccia. Insomma sto parlando del metodo grezzo per eccellenza: il semplice tirare ad indovinare più o meno casuale. Ma andiamo con ordine.
Per semplicità baso quest’analisi sugli smartphone con sistema operativo Android, un po’ perché oggi Android rappresenta quasi l’80% del mercato dei sistemi operativi per smartphone (potete verificare il dato preciso cercando sul Web delle keyword come “smartphone operating system market share”) ed un po’ perché offre diverse modalità di blocco schermo, eccole qui:
- PIN: si tratta di un codice numerico lungo tra le 4 e le 16 cifre;
- password: è la classica password, che può incorporare lettere, numeri e caratteri speciali, lunga tra i 4 ed i 6 caratteri;
- pattern: è un codice grafico da disegnare su una matrice di punti 3×3, vero e proprio fiore all’occhiello dei Googlefonino;
In realtà c’è anche la possibilità di usare la modalità riconoscimento facciale/vocale che permette di riconoscere viso e voce dell’utilizzatore tramite le periferiche del dispositivo. Questo metodo è molto comodo ma Google stessa segnala che è ancora in fase sperimentale e non è così sicuro. A volte non funziona benissimo e potrebbe bastare una vostra foto o una persona che vi somigli per sbloccare il dispositivo. Va beh, col vostro permesso, lo tralascerei.
“Misuriamo” i metodi di blocco schermo con codice di Android
Partiamo da questo presupposto:
più combinazioni consente un codice, più sarà difficile da indovinare tirando a caso, più sarà sicuro!
Ora proviamo ad applicare questo concetto a PIN, PASSWORD e PATTERN: quante combinazioni permettono queste modalità di protezione?
PIN. Il buon vecchio PIN permette di scrivere dei codici numerici dove ogni cifra può assumere un valore da 0 a 9. Abbiamo 10 valori possibili per ogni carattere del PIN. Avremo quindi possibili PIN lunghi 4, possibili PIN lunghi 5, e così via. Quanti possibili PIN posso scrivere? Si tratta della somma delle potenze di 10 con esponente compreso tra 4 e 16 (che è proprio la lunghezza possibile dei codici):
che è pari a:
,
ovvero 11 milioni di miliardi di combinazioni!
PASSWORD. Se vi sembravano tante le combinazioni del PIN, allora dovete vedere le password. Le password sono simili ad un PIN ma ogni carattere può essere:
- una delle 26 lettere minuscole dell’alfabeto inglese;
- una delle 26 lettere maiuscole dell’alfabeto inglese;
- una delle 10 cifre di cui sopra;
- una carattere speciale.
Proviamo ad ignorare la possibilità di utilizzare dei caratteri speciali e ripetiamo lo stesso ragionamento di prima. Se per ogni carattere ci sono 62 (= 26+26+10) possibilità, avremo un totale di:
che è pari a:
,
ovvero 48 miliardi di miliardi di miliardi di combinazioni (e non ho considerato i caratteri speciali, sennò sarebbero ancora di più)!
PATTERN (Segno Grafico). Ecco, è qui che viene il bello. Il segno grafico è il metodo più affascinante dei tre, e si presenta così:
Fantastico, come funziona? In breve:
- abbiamo a disposizione una matrice da 9 punti;
- dobbiamo comporre il codice unendo tra loro i punti della matrice;
- possiamo iniziare a disegnarlo arbitrariamente da qualsiasi punto;
- il segno deve essere lungo tra i 4 ed i 9 punti;
- dobbiamo seguire delle regole di connettività dinamiche che non sto a specificare (ma chi non le conoscesse, le può trovare nell’appendice A in fondo a questo post)
Detto questo, come facciamo a contare quanti sono i possibili codici grafici?
Bel dilemma, e visto che le regole di connettività non mi han permesso di trovare una formuletta elegante che rispondesse al quesito, ho la scusa pronta per risolvere il quesito parlandovi della teoria dei grafi. Cos’è un grafo? Si tratta di uno strumento matematico di introduzione relativamente recente, di solito è una cosa del genere:
Si tratta di una sorta di mappa logica, composta da un insieme di nodi (o vertici) e da un insieme di collegamenti (detti anche archi o spigoli) che possono essere orientati, cioè avere un verso di percorrenza o meno. Un grafo viene spesso indicato con la lettera G e potete trovarlo spesso scritto in questa forma:
,
dove V è appunto l’insieme dei vertici mentre E è quello dei collegamenti. Ad esempio quello che vi ho mostrato poco fa, era un grafo formato da 6 nodi e 7 archi.
Il bello del grafo è la sua versatilità che lo rende in grado di modellare astrattamente problemi di ogni tipo. Il pattern Andorid si presta ad essere uno di questi: basta pensare che V sia l’insieme dei nove punti della matrice Android e che E rappresenti l’insieme dei collegamenti leciti e che vari a mano a mano che il pattern si compone (ricordate le regole di connettività). Per darvi l’idea del grafo Android in questione, ve lo riporto qui in forma grafica:
Ci manca ancora un ultimo pezzo: come simulare l’inserimento di un pattern? Il codice grafico è esattamente un percorso (detto anche cammino) su G, ovvero una sequenza ordinata di punti collegabili tramite gli archi del grafo. Per risolvere il nostro problema dobbiamo cercare tutti i cammini semplici, ovvero tutti quei percorsi che includono i nodi una ed una sola volta. Nel nostro caso Google ci impone che questi cammini siano lunghi tra i 4 ed i 9 nodi. Il gioco è fatto. Questo grafo è un’astrazione corretta del nostro problema. Una volta scritto un algoritmo ad hoc che conti i cammini semplici che ci interessano (se voleste guardare da vicino il l’algoritmo, lo trovate nell’appendice B), scopriamo questo risultato:
per un totale di:
possibili pattern!
Ok, è quindi come la mettiamo con la sicurezza?
Abbiamo visto che le tre modalità offrono diversi livelli di “sicurezza”. Un potenziale curioso che volesse sbloccare il nostro smartphone, dovrebbe tirare a caso tra:
- nel caso aveste usato la Password;
- nel caso aveste usato il PIN;
- nel caso aveste usato il Pattern;
Cavolo ma allora la Password è sicurissima, ed il PIN pure. Ma il Pattern? Sicuramente è un metodo comodo e accattivante, very nerd, ma contempla molte meno combinazioni. E quindi? Tranquilli: Google ha progettato Andorid in maniera tale che il telefono si blocchi dopo venti tentativi sbagliati (si potrà poi sbloccare con l’account di Google). La probabilità che qualcuno indovini il codice grafico entro 20 tentativi sparati a caso è circa una su 20.000 (se siete curiosi di sapere come si calcola, la risposta è nell’appendice C). Il classico ago in un pagliaio.
Conclusioni
Considerando che il numero di “sblocchi” giornalieri è abbastanza alto, occorre trovare un buon mix tra sicurezza e comodità; probabilmente impostare una password complessissima da 16 caratteri non è una buona idea. Il pattern, che è il metodo che offre meno combinazioni ma è, a mio avviso, il più comodo dei tre, è sicuro se usato cum grano salis. Esibire palesemente il codice di sblocco all’immissione potrebbe non essere il massimo della riservatezza, giusto per usare un ovvio eufemismo. A questo proposito Andorid permette di inserire il pattern di sblocco senza che questo venga disegnato sullo schermo; se proprio vogliamo essere scrupolosi, possiamo rinunciare all’ebrezza di vedere il cammino semplice formarsi sul display.
Concludo citando che sembra sia possibile risalire al pattern analizzando i residui che le ditate lasciano sul touchscreen. A mio avviso le condizioni che rendono efficace un attacco del genere sono po’ particolari e sono facilmente evitabili. Ma lascio a voi la valutazione, provate a dare un’occhiata qui: Smudge Attacks on Smartphone Touch Screens, Aviv, Adam J. et. al., WOOT’10 Proceedings of the 4th USENIX conference on Offensive technologies, 1-7 (2010).
Insomma, cercando di essere degli utenti un po’ smart e facendo un po’ di attenzione, direi che anche solo con un pattern si possono dormire sonni tranquilli. Provate a chiederlo all’FBI…
A presto con nuovi shottini e videoAperitivi!
-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
APPENDICE A: Regole di Connettività
La prima connessione del pattern segue queste regole:
- possiamo connettere un nodo d’angolo con tutti i nodi tranne che con gli altri tre nodi d’angolo, es. 1 non è connettibile con {3, 7, 9}
- possiamo connettere un nodo laterale con tutti i nodi tranne che col nodo laterale nascosto dietro al nodo centrale, es. 4 non è connettibile con 6 o 2 non è connettibile con 8
- possiamo connettere il nodo centrale con tutti gli altri nodi.
Dalla seconda connessione in poi, possiamo scavalcare i nodi già usati nel pattern (evvai!):
- dopo che abbiamo usato il nodo centrale lo possiamo attraversare, quindi:
- le eccezioni della regola 2) non valgono più, es. 4 diventa connettibile con 6;
- possiamo connettere tra loro nodi d’angolo opposti, es 1 diventa connettibile con 9;
- dopo che abbiamo usato un nodo laterale possiamo scavalcare anche lui, e allora:
- possiamo connettere tra loro nodi d’angolo che stanno ai suoi estremi, es. 1 diventa connettibile con 3, dopo che abbiamo già incluso 2.
APPENDICE B: Codice di calcolo per i cammini
Eccolo qui! L’algoritmo in questione è un adattamento di un algoritmo standard chiamato depth-first search (DFS), che di solito si occupa di fare ricerche sui grafi. Nel nostro problema non avevamo nulla di particolare da cercare, allora l’ho modificato, un po’ alla buona, in maniera tale che girasse “a vuoto”, contando di volta in volta la lunghezza dei percorsi che intrinsecamente stava visitando.
Sfruttando le simmetrie, è possibile ottenere il risultato finale sommando tutti i pattern formabili:
- partendo dal nodo 1 e moltiplicati per 4 (= percorsi formabili a partire dai nodi d’angolo);
- partendo dal nodo 2 e moltiplicati per 4 (= percorsi formabili a partire dai nodi laterali);
- partendo dal nodo 5.
In questa tabella ci sono i risultati dettagliati:
Ecco qui il codice (è scritto in Python):
''' The following code: - aims at counting the number of Android-lockscreen Patterns; - was intended to the educational project Science4Fun.org; - simulates a Graph G = (V,E) where: - V is the set of Android dots {1,2,3,4,5,6,7,8,9}; - E is the set of edges and it could vary according to previous moves: - see addExtra(node) and removeExtra(node). ''' ''' Graph definition: - V: the keys of this dictionary represent the nodes of Android Matrix - E: each element of a list represents the link (key->element) ''' neighborhood = {} neighborhood[1] = [2,5,4,8,6] neighborhood[2] = [1,3,4,5,6,7,9] neighborhood[3] = [2,5,6,4,8] neighborhood[4] = [1,2,3,5,7,8,9] neighborhood[5] = [1,2,3,4,6,7,8,9] neighborhood[6] = [1,2,3,5,7,8,9] neighborhood[7] = [4,5,8,6,2] neighborhood[8] = [1,3,4,5,6,7,9] neighborhood[9] = [8,5,6,4,2] # Add extra-moves when necessary def addExtra(node): if node == 2: neighborhood[1].append(3) neighborhood[3].append(1) elif node == 4: neighborhood[1].append(7) neighborhood[7].append(1) elif node == 6: neighborhood[3].append(9) neighborhood[9].append(3) elif node == 8: neighborhood[7].append(9) neighborhood[9].append(7) elif node == 5: neighborhood[1].append(9) neighborhood[9].append(1) neighborhood[2].append(8) neighborhood[8].append(2) neighborhood[3].append(7) neighborhood[7].append(3) neighborhood[4].append(6) neighborhood[6].append(4) # Remove extra-moves when necessary def removeExtra(node): if node == 2: neighborhood[1].remove(3) neighborhood[3].remove(1) elif node == 4: neighborhood[1].remove(7) neighborhood[7].remove(1) elif node == 6: neighborhood[3].remove(9) neighborhood[9].remove(3) elif node == 8: neighborhood[7].remove(9) neighborhood[9].remove(7) elif node == 5: neighborhood[1].remove(9) neighborhood[9].remove(1) neighborhood[2].remove(8) neighborhood[8].remove(2) neighborhood[3].remove(7) neighborhood[7].remove(3) neighborhood[4].remove(6) neighborhood[6].remove(4) # Get difference between two lists def diff(a, b): b = set(b) return [aa for aa in a if aa not in b] # simple paths counter def getPaths(source): # counter and solution tracker count = {} for k in range(1,10): count[k] = 0 visited = [] stack = [source] while stack: n = stack[-1] if n not in visited: visited.append(n) addExtra(n) count[len(visited)] +=1 children = neighborhood[n] notVisitedchildren = diff(children, visited) # check if empty if not notVisitedchildren: stack.pop() popped = visited.pop() removeExtra(popped) else: for i in notVisitedchildren: stack.append(i) else: stack.pop() popped = visited.pop() removeExtra(popped) return count ''' call getPaths to calculate all single paths starting from a source node, passed as argument. getPaths returns a dict such that: - keys: indicate the length of paths - values: indicate the number of patterns for each key i.e. getPaths(5) returns this dict {1: 1, 2: 8, 3: 48, 4: 256, 5: 1152, 6: 4248, 7: 12024, 8: 23280, 9: 23280} ''' c = getPaths(5) print c
Il codice è scritto in fretta e furia, per cui segnalazioni e consigli sono più che mai graditi; potete usare i commenti di questo post oppure mandare anche solo una mail da qui.
APPENDICE C: un ago in un pagliaio
Come fare a calcolare la probabilità che qualcuno indovini il pattern entro 20 tentativi? La probabilità di “indovinare entro venti tentativi” è pari a uno meno la probabilità di “non indovinare entro 20 tentativi”. Possiamo dire così perché indovinare o non indovinare entro il blocco del telefono sono due eventi complementari, cioè: il verificarsi dell’uno esclude il verificarsi dell’altro, ma uno dei due eventi si verificherà di sicuro (o il codice viene indovinato o lo smartphone si blocca). Abbiamo detto:
Per semplicità, calcoliamo la probabilità di non indovinare prima del blocco e poi sottraiamola da 1! Come si fa?
I possibili pattern sono 389.112 ma solo uno di questi è quello corretto, quindi 389.111 sono sbagliati. La probabilità di non indovinare al primo tentativo è 389.111/389.112.
La probabilità di non indovinare ai tentativi successivi è invece un po’ più difficile. Si tratta di una probabilità condizionata, ad esempio: se si arriva al secondo tentativo, significa che si è già sbagliato il primo e che sono rimasti “solo” 389.111 pattern da provare (dei quali 389.110 sbagliati). Per calcolare questa probabilità non sarà sufficiente fare il rapporto (389.110/389.111), ma dovremo ritarare il secondo tentativo sull’esito del primo. Il nuovo rapporto 389.110/389.111 è una sorta di “percentuale” che non va calcolata sul valore di partenza, ma su un valore un po’ più piccolo, che rappresenta solo quei “casi” in cui erano stati già inseriti dei pattern errati al tentativo numero 1. Insomma, è un po’ come calcolare una “percentuale” della “percentuale”. La probabilità di non indovinare al secondo tentativo è data quindi dal prodotto della probabilità di non aver indovinato al primo per la probabilità di non indovinare al secondo:
Applicando ricorsivamente questo calcolo, possiamo arrivare a calcolare la probabilità di non indovinare prima del blocco (20 tentativi), che sarà:
Ora abbiamo tutto quello che ci serve. La probabilità di indovinare prima del blocco è pari a 1 – 0,999949. Se prendiamo questo numero e ne calcoliamo il reciproco otteniamo:
Che è proprio quella possibilità su circa 20.000 di cui si parlava nell’articolo.
A presto! : )
Scusate l’ignoranza, ma il pin del cellulare permette di inserire dei numeri da 0 a 9 con delle ripetizioni fino a k volte(lunghezza del pin), di conseguenza la formula non dovrebbe essere :(n+k-1)!/k!(n-1)! ?
Pingback: Carnevale della Matematica #68, 14 dicembre 2013: il Tempo - Maddmaths!
Cӏ SEMPRE LA LEGGE DI MURPH AD AIUTARE.