GlobalId Encoding

Ogólny zarys koncepcji znajduje się na blogu: "GlobalId encoding".

Fragmenty kodu z wyjaśnieniami

auth_code

Jednym z elementów PoC jest skrócenie wyjścia funkcji HMAC do możliwie krótkiego stringu, tak, by encoding identyfikatorów globalnych nie powodował bardzo dużego wzrostu objętości danych. Oczywiście wzrost i tak występuje, ale jest mniejszy, niż mógłby być. Wykorzystywany HMAC korzysta z algorytmu SHA1, który generuje wyjście postaci:

da39a3ee5e6b4b0d3255bfef95601890afd80709

W PoC ciąg ten skracany jest do 6 znaków, z czego 4 znaki pochodzą z wejściowego ciągu danych. Jest to realizowane przy pomocy funkcji auth_code (a właściwie __auth_code):

   1 def auth_code(data, count):
   2     pos = random.randint(0x00,0xff)
   3     hashdata = __get_sha256(data)
   4     return __auth_code(hashdata, count, pos)

Działanie funkcji auth_code jest proste - losuje ona wartość z zakresu od 0x00 do 0xff, i wywołuje właściwą funkcję __auth_code z parametrami, którymi jest:

Uwaga:

   1 def __auth_code(data, count, pos):
   2     i = 0
   3     auth = "%02x" % pos
   4     pos = pos % len(data)
   5     while i < count:
   6         auth += data[pos]
   7         while data[pos] == "0":
   8             pos = (pos + 1) % len(data)
   9         pos = (pos + int(data[pos], 16)) % len(data)
  10         i += 1
  11     return auth

Funkcja __auth_code dokonuje właściwego skracania sha256 do pożądanej długości. Generowanie skrótu odbywa się poprzez odczytanie wartości znaków w sha256 na określonych pozycjach. Pozycje określane są w sposób następujący:

Operacja ta jest powtarzana tyle razy, ile wskazuje parametr count, przy czym w pozostałej części PoC wykorzystana jest wartość 4. W przypadku, gdy na danej pozycji znajduje się 0, wartość ta jest wykorzystywana (dodawana) do wyjścia, ale o przesunięciu decyduje wartość następnego znaku, który nie jest 0. Ostatecznie otrzymywany jest ciąg postaci:

e3dc5b

Maksymalna możliwa ilość wyjść funkcji auth_code dla jednej wartości parametru data to 256, czyli tyle, ile jest możliwych wartości losowanego w niej parametru.

Weryfikacja auth_code odbywa się za pomocą funkcji auth_verify:

   1 def auth_verify(data, auth):
   2     pos = int(auth[:2],16)
   3     hashdata = __get_sha256(data)
   4     auth_tmp = __auth_code(hashdata, len(auth) - 2, pos)
   5     return auth_tmp == auth

Funkcja na wejściu otrzymuje dane (data) oraz wartość auth. Porównywana jest wartość auth_code przekazana w parametrze, z wartością wyliczoną przy pomocy funkcji __auth_code.

Prawdopodobieństwo zaistnienia zdarzenia "kolizji" wynosi 1/16^4. Ilość elementów, które generują taki sam kod auth_code zależy od mocy zbioru dopuszczalnych elementów. Dla przykładu w zakresie liczb od 1 do 1000000 będzie około 16 różnych wartości generujących ten sam auth_code.

ShortSecureGlobalId

Klasa ShortSecureGlobalId udostępnia dwie funkcje:

ShortSecureGlobalId.__init__

W konstruktorze ustawianych jest kilka unikalnych wartości dla każdej instancji.

   1     def __init__(self):
   2         self.__keySize = 512
   3         self.__authSize = 4
   4         self.__authKey = os.urandom(self.__keySize)
   5         self.__encKey = os.urandom(self.__keySize)
   6         self.__encCookie = struct.pack("B", random.randint(0,0xFF))
   7         self.__digestmod = hashlib.sha1

ShortSecureGlobalId.put

   1     def put(self, raw_id):
   2         """
   3         Zwraca zakodowany identyfikator globalny przekazany w raw_id
   4         """
   5         auth_part = auth_code(self.__getHMAC(self.__addEncCookie(raw_id)), \
                                            self.__authSize)
   6         enc_part = xor_crypt(self.__getEncKey(auth_part), \
                             to_hex(self.__addEncCookie(raw_id) ))
   7         enc_id = "%s%s" % (auth_part, enc_part)
   8         return enc_id

Rezultat funkcji put składa się z części:

Część auth_part jest rezultatem wywołania opisywanej już funkcji auth_code z parametrami:

Wykorzystanie HMAC powoduje, że dla tej samej wartości identyfikatora dla różnych sesji, rezultat auth_code będzie różny, bo różne są dane wejściowe.

Część enc_part jest rezultatem operacji XOR, gdzie klucz szyfrowania jest otrzymywany przez wywołanie funkcji __getEncKey i oparty jest na operacji HMAC na auth_part i kluczu __encKey. Dane szyfrowane to identyfikator oraz jeden bajt kontrolny o wartości unikalnej dla każdej sesji (__encCookie).

UWAGA:

Przykładowe rezultaty funkcji put dla identyfikatora o wartości 1:

 918c37cffa
 e194f3407b
 4933970db5
 f1c356a51e
 2ef3568287
 6c4d358e14
 72c175df52
 13fd1d5206
 924d949447
 c3c9743c88

ShortSecureGlobalId.get

Funkcja get jest w zasadzie odwrotnością funkcji put:

   1     def get(self, enc_id):
   2         """
   3         Odkodowuje identyfikator globalny i zwraca raw_id    
   4         """
   5         auth_part = enc_id[:2 + self.__authSize]
   6         enc_part = enc_id[2 + self.__authSize:]
   7         dec_data = from_hex(xor_crypt(self.__getEncKey(auth_part), enc_part))
   8         raw_id, encCookie = self.__splitIdAndEncCookie(dec_data)
   9         # Sprawdź, czy wartość auth_auth part dla odszyfrowanych danych zgadza
  10         # się z częścią przekazaną jawnie. Jeśli nie, zgłoś wyjątek
  11         if not auth_verify(self.__getHMAC(self.__addEncCookie(raw_id)), auth_part):
  12             raise DataIntegrityException()
  13         # Sprawdż, czy encCookie z odszyfrowanych danych zgadza się z 
  14         # self.__encCookie. Jeśli nie, zgłoś wyjątek
  15         if not encCookie == self.__encCookie:
  16             raise DataIntegrityException()
  17         return raw_id

Na początu otrzymany zakodowany identyfikator jest dzielony na dwie części:

Na podstawie auth_part generowany jest klucz szyfrowania i enc_part jest odszyfrowane do postaci dec_part. Część dec_part jest dalej dzielona na dwie części:

Dodatkowo wykonywane są dwa testy:

Jeśli którykolwiek z tych testów zakończy się niepowodzeniem, zgłaszany jest wyjątek.

Wyzwanie

Cały PoC nie ma za zadanie stworzenia metody kodowania identyfikatorów globalnych w taki sposób, by ich użycie było całkowicie bezpieczne. Testy "integralności" otrzymanych danych w szczególności nie mogą zastąpić dobrze zaimplementowanej kontroli dostępu. PoC pokazuje dodatkową linię ochrony, która może być przydatna, jeśli:

Utrudnienie polega na tym, że atakujący musi znaleźć odpowiednią wartość zakodowanego identyfikatora, która po rozkodowaniu po stronie serwera spełni warunki:

WYZWANIE: Znaleźć metodę generowania prawidłowych wartości zakodowanych identyfikatorów, o ile jest to możliwe.


Pełny kod PoC

   1 # -*- coding: utf8 -*-
   2 
   3 import os
   4 import hashlib
   5 import hmac
   6 import gzip
   7 import binascii
   8 import random
   9 import struct
  10 
  11 def __get_sha256(data):
  12     sha256 = hashlib.new("sha256")
  13     sha256.update(data)
  14     hashdata = sha256.hexdigest()
  15     del sha256
  16     return hashdata
  17 
  18 
  19 def auth_code(data, count):
  20     pos = random.randint(0x00,0xff)
  21     hashdata = __get_sha256(data)
  22     return __auth_code(hashdata, count, pos)
  23 
  24 
  25 def __auth_code(data, count, pos):
  26     i = 0
  27     auth = "%02x" % pos
  28     pos = pos % len(data)
  29     while i < count:
  30         auth += data[pos]
  31         while data[pos] == "0":
  32             pos = (pos + 1) % len(data)
  33         pos = (pos + int(data[pos], 16)) % len(data)
  34         i += 1
  35     return auth
  36 
  37 
  38 def auth_verify(data, auth):
  39     pos = int(auth[:2],16)
  40     hashdata = __get_sha256(data)
  41     auth_tmp = __auth_code(hashdata, len(auth) - 2, pos)
  42     return auth_tmp == auth
  43 
  44 
  45 def xor_crypt(key, data):
  46     """
  47     XOR na dwóch blokach HEX klucz musi być krótszy, lub równy długości data
  48     """
  49     assert len(data) <= len(key)
  50     out = ""
  51     for pos in xrange(0, len(data),2):
  52         out += "%02x" % ( int(key[pos:pos+2], 16) ^ int(data[pos:pos+2], 16))
  53     return out
  54 
  55 
  56 to_hex = binascii.hexlify
  57 from_hex = binascii.unhexlify
  58 
  59 class ShortSecureGlobalId:
  60     """ 
  61     PoC bezpiecznych identyfikatorów globalnych.
  62     
  63     Dla każdej instancji klasy generowane są dwa unikalne klucze:
  64     __authKey - użyty do generowania HMAC dla identyfikatora,
  65     __encKey - użyty do generowania klucza szyfrowania,
  66     
  67     Encodowany identyfikator składa się z:
  68     - 1: losowego bajtu z zakresu 0x00 do 0xFF,
  69     - 2: wartości zależnej od 1 oraz HMAC dla identyfikatora o długości __authSize,
  70     - 3: zakodowanej wartości identyfikatora
  71     Całość reprezentowana jest jako HEX. Przykład:
  72     e4a7dc53aa7113:
  73     e4 - 1
  74     a7dc - 2
  75     53aa7113 - 3
  76     
  77     W skład 3 wchodzi:
  78     - zakodowany identyfikator
  79     - zakodowane __encCookie
  80     
  81     Kodowanie odbywa się przy pomocy operacji XOR z wykorzystaniem klucza __encKey.
  82     """
  83 
  84     def __init__(self):
  85         self.__keySize = 512
  86         self.__authSize = 4
  87         self.__authKey = os.urandom(self.__keySize)
  88         self.__encKey = os.urandom(self.__keySize)
  89         self.__encCookie = struct.pack("B", random.randint(0,0xFF))
  90         self.__digestmod = hashlib.sha1
  91 
  92 
  93     def __getHMAC(self, data):
  94         """
  95         Zwraca hmac (hex) z podanych danych
  96         
  97         HMAC jest wykonywany na przekazanych danych z wykorzystaniem:
  98         - klucza self.__authKey ustalanego w __init__
  99         - funkcji skrótu wskazywanej przez self.__digestmod
 100         """
 101         mac = hmac.new(self.__authKey, digestmod = self.__digestmod)
 102         mac.update(data)
 103         return mac.hexdigest()
 104 
 105 
 106     def __getEncKey(self, seed):
 107         """
 108         Zwraca klucz szyfrowania (sha512) dla podanego seeda
 109         
 110         Klucz jest tworzony jako HMAC korzystający z sha512 i self.__encKey
 111         """
 112         key = hmac.new(self.__encKey, digestmod = hashlib.sha512)
 113         key.update(seed)
 114         return key.hexdigest()
 115 
 116 
 117     def __addEncCookie(self, raw_id):
 118         """
 119         Dodaje __encCookie do raw_id
 120         """
 121         return "%s%s" % (raw_id, self.__encCookie)
 122 
 123 
 124     def __splitIdAndEncCookie(self, dec_data):
 125         """
 126         Rozdziela encCookie i raw_id
 127         """
 128         return (dec_data[:-len(self.__encCookie)], \
                dec_data[-len(self.__encCookie):])
 129 
 130 
 131     def put(self, raw_id):
 132         """
 133         Zwraca zakodowany identyfikator globalny przekazany w raw_id
 134         """
 135         auth_part = auth_code(self.__getHMAC(self.__addEncCookie(raw_id)), \
                                            self.__authSize)
 136         enc_part = xor_crypt(self.__getEncKey(auth_part), \
                             to_hex(self.__addEncCookie(raw_id) ))
 137         enc_id = "%s%s" % (auth_part, enc_part)
 138         return enc_id
 139 
 140 
 141     def get(self, enc_id):
 142         """
 143         Odkodowuje identyfikator globalny i zwraca raw_id    
 144         """
 145         auth_part = enc_id[:2 + self.__authSize]
 146         enc_part = enc_id[2 + self.__authSize:]
 147         dec_data = from_hex(xor_crypt(self.__getEncKey(auth_part), enc_part))
 148         raw_id, encCookie = self.__splitIdAndEncCookie(dec_data)
 149         # Sprawdź, czy wartość auth_auth part dla odszyfrowanych danych zgadza
 150         # się z częścią przekazaną jawnie. Jeśli nie, zgłoś wyjątek
 151         if not auth_verify(self.__getHMAC(self.__addEncCookie(raw_id)), auth_part):
 152             raise DataIntegrityException()
 153         # Sprawdż, czy encCookie z odszyfrowanych danych zgadza się z 
 154         # self.__encCookie. Jeśli nie, zgłoś wyjątek
 155         if not encCookie == self.__encCookie:
 156             raise DataIntegrityException()
 157         return raw_id
 158 
 159 
 160 class DataIntegrityException(Exception):
 161 
 162 #pylint: disable-msg=W0231    
 163     def __init__(self):
 164         pass


CategoryTekst

GlobalIdEncoding (ostatnio edytowane 2009-02-01 20:04:10 przez PawelGolen)