標識符在許多領域主要用於標記用途。可以根據環境條件等因素隨機的生成一個ID,也可以使用哈希算法或者消息摘要算法對對象生成一個唯一的固定長度的標記符。前者主要用於區分身份的標記,后者可以用於比較文件數據的一致性和重復數據的檢測。
三種標識符
UUID
uuid即通用唯一標識符(Universally Unique Identifier),是一種軟件構建的標准,目的是讓分布式系統中的元素都能有唯一辨識信息。
其由4個連字號(-)連接32個字節的字符串構成,總共36個字節長。如:UUID('c5c11029-8c99-400e-a7a5-d741f6aaf3e3')。
GUID
guid是UUID的一種實現,目前最廣泛應用的UUID,是MS的全局唯一標識符(GUID)。
VS中通過Tools->Create GUID生成:
// {8403C5C0-7F0C-4933-BE85-0E2A90E888AB} DEFINE_GUID(<<name>>, 0x8403c5c0, 0x7f0c, 0x4933, 0xbe, 0x85, 0xe, 0x2a, 0x90, 0xe8, 0x88, 0xab);
ObjectID
objectid則是用於MongoDB的主鍵,12-byte的bson類型字符串:
An ObjectId is a 12-byte unique identifier consisting of:
- a 4-byte value representing the seconds since the Unix epoch,
- a 3-byte machine identifier,
- a 2-byte process id, and
- a 3-byte counter, starting with a random value.
>>> from bson import objectid as objectid >>> objectid.ObjectId() ObjectId('5c6921b65846b70514ebf839')
上面是MongoDB中產生的若干條記錄
這些隨機生成算法產生的ID重復的概率可以小到忽略不計,通常可以認為是唯一的。
消息摘要算法
上面是我們主動生成一個全局唯一標識符,用於標記信息數據對象等。被標記對象與標記符之間沒有必然關系,兩者之間通常通過key-value的形式關聯,通過key來索引得到value。
那么能不能用一個算法對給定的信息生成一個唯一的標識符?且滿足下面的條件:
func(data_1) = identifier_1 func(data_2) = identifier_2 if data_1 == data_2 then identifier_1 == identifier_2 if data_1 != data_2 then identifier_1 != identifier_2
其實這就是消息摘要算法:
- 結果一致性:相同的輸入應該產生相同的結果;
- 結果唯一性:不同的輸入的結果應該不同;
- 不可逆性:已知算法的生成過程和產生結果,但不能推斷出輸入值。
消息摘要算法的應用是很廣泛的,其本質是對數據進行摘要計算以得到一個固定長度的hash值,目前比較常用的有MD5和SHA1.
MD5
信息-摘要算法Message-Digest Algorithm 5簡稱MD5,是廣泛使用的一種散列函數。
能將任意字符串加密成固定長度為128bit的MD5值,且該過程不可逆,安全性較高。能對文件進行散列計算得到一個"數字指紋”,用於校驗文件的一致性。
SHA1
安全哈希算法Secure Hash Algorithm簡稱SHA1 。SHA1基於MD5,它對長度小於264的輸入,產生長度為160bit的hash值。
由於加密后的數據長度更長,SHA1的運算速度要比MD5慢,但是更安全。
散列計算和上面介紹的隨機算法一樣均可能產生重復值,因為將任意的輸入轉換到一個固定格式和長度的元素構成的集合中,必然會出現多對一的映射關系。
但是實際應用中重復的概率非常小,可以忽略不計。
因此實際使用時如果兩個輸入a和b產生相同的hash值,我們便認為a和b是相同的。這也是數字簽名和文件一致性校驗等應用的一個基礎。
動手實踐
由於python有豐富的庫,而且操作較為方便,下面通過python來簡單的了解實踐一下。
UUID的生成算法
python提供了RFC 4122標准中uuid 1, 3, 4, 和5版本的生成算法,分別對用uuid模塊中的uuid1、uuid3、uuid4和uuid5四個函數。
- uuid1:該函數使用host ID和序列數以及當前的時間來生成UUID,由於包含有網絡地址,因此該算法可能會暴露隱私;
- uuid3:使用一個命名空間的UUID的MD5值和一個字符串來生成一個UUID;
- uuid4:直接生成一個隨機的UUID;
- uuid5:使用一個命名空間的UUID的sha-1值和一個字符串來生成一個UUID;
實際例子如下:
# uuid1(node=None, clock_seq=None) >>> uuid.uuid1() UUID('27751551-34cf-11e9-ac82-54bf64938e35') # uuid3(namespace, name) >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'cnblogs/alpha_panda') UUID('585ccea7-3560-3af5-a265-456e5944a39e') # uuid4() >>> uuid.uuid4() UUID('72148b42-eec8-4109-80e8-b157134c94d7') # uuid5(namespace, name) >>> uuid.uuid5(uuid.NAMESPACE_DNS, 'cnblogs/alpha_panda') UUID('a8fa1c8d-bff6-5df4-839b-7787b9094863')
通過str將其裝換成字符串,也可通過.bytes屬性得到16字節的編碼。
>>> uobj = uuid.uuid4() >>> uobj UUID('d172a1ed-71bb-4cb5-ac00-4d0dd6041128') >>> str(uobj) 'd172a1ed-71bb-4cb5-ac00-4d0dd6041128' >>> uobj.bytes '\xd1r\xa1\xedq\xbbL\xb5\xac\x00M\r\xd6\x04\x11(' >>> uuid.UUID(bytes=uobj.bytes) UUID('d172a1ed-71bb-4cb5-ac00-4d0dd6041128')
ObjectID
python中可以通過bson庫生成objectid,下面是我們封裝的一個用於生成和處理objectid的類:
import bson.objectid as objectid class ObjectIDMgr(object): @staticmethod def genid(): return objectid.ObjectId() @staticmethod def id2str(obj_id): return str(obj_id) @staticmethod def str2id(id_str): return objectid.ObjectId(id_str) @staticmethod def id2bytes(obj_id): return obj_id.binary @staticmethod def bytes2id(id_bytes): return objectid.ObjectId(id_bytes)
hash算法
python中有內置函數hash和庫hashlib中提供接口可供計算hash值。下面來一一介紹一下。
1.內置hash函數
內置hash函數的函數原型為:hash(object) -> integer
該函數主要用於程序運行時通過計算hash值來區分不同的對象,其散列的結果和對象的值以及id(內存中的地址)有關。
要想保證跨進程的結果一致性,可以使用hashlib庫中提供的函數。
2.hashlib
hashlib中包括md5和sha1的實現,以md5為例。
Python 2.x
import hashlib m1 = hashlib.md5('cnblogs/alpha_panda') m2 = hashlib.md5() m2.update('cnblogs/') m2.update('alpha_panda') hd1, hd2 = m1.hexdigest(), m2.hexdigest() print hd1, hd2, hd1 == hd2
結果:
fb9e9f0f84773f74587f2f05ae0e55d2 fb9e9f0f84773f74587f2f05ae0e55d2 True
Python 3.x
>>> import hashlib >>> print(hashlib.md5('cnblogs/alpha_panda'.encode('utf-8')).hexdigest()) fb9e9f0f84773f74587f2f05ae0e55d2
hashlib.md5算法對同一字符串計算必能得到相同的md5值,由於該函數計算的md5值可重現,因此可以進行保存和傳輸。
文件一致性校驗
對於文本文件和二進制文件均可以通過hashlib.md5進行計算。
def cal_file_md5(file_path): m = hashlib.md5() with open(file_path, 'rb') as fobj: for segment in iter(lambda : fobj.read(4096), b""): m.update(segment) return m.hexdigest()
上面的函數可以對較大的文件進行md5的值進行計算
明文轉密文
為安全考慮,防止用戶的密碼泄露,一般公司都不會直接存儲用戶的密碼明文,而是將密碼轉換成密文存放到后台數據庫中。
用戶登錄的時候,將密碼轉換成密文和數據庫中的存儲的密文進行比較以進行身份認證。
該過程要求不能有密文反推到出明文,而且明文和密文應該是一一對應的關系。消息摘要算法恰好可以滿足需求。
hash值可用作數據庫中用戶密碼的密文。下面是一個簡單的例子:
DB = { 'zhangsan':'305e4f55ce823e111a46a9d500bcb86c', 'lisi':'7c6a180b36896a0a8c02787eeafb0e4c', } def check_valid(user_name, pwd): cipher = hashlib.md5(pwd).hexdigest() valid_cipher = DB.get(user_name, None) return valid_cipher == cipher def login_request(user_name, pwd): if check_valid(user_name, pwd): # do_login() return True else: return False login_queue= (('zhangsan', 'password0'), ('zhangsan', 'hello'), ('lisi', 'password1')) for user, pwd in login_queue: is_suc = login_request(user, pwd) print is_suc
即使服務器端DB中的用戶賬號信息泄露,但是由於很難偽造一個明文使其md5值恰好等於密碼密文,因此有較高的安全性。
目前網絡上有傳言說md5已被破解,實際無從驗證。但是破解簡單密碼的暴力破解是可以實現的。
預先計算一些常見的字符串的密文,然后將密文作為key,明文作為value存放到數據庫中,這樣可以使用密文查詢得到明文。
重復文件檢測與引用
使用消息摘要算法計算文件的md5值,可以將改值作為文件的ID。
比如某網盤上傳文件的秒傳功能,我們將一個幾G大小的操作系統的鏡像文件上傳至網盤服務器,可能只需要幾秒鍾的時間。
客戶端或者瀏覽器先去讀文件,這個過程實際上是計算待上傳文件的hash值,然后將改值上傳到服務器,查找改值對應的文件是否在網盤中;
如果已經存在,則直接將服務端網盤文件的引用計數加一,然后將文件添加到在當前用戶的文件列表中,便完成上傳。如果沒有,這需要將文件上傳至服務器。
這樣如果幾萬個用戶網盤中存儲一個相同的文件,網盤服務器只需存放一份文件,分別被幾萬個用戶引用。當刪除一個文件而導致其引用計數為零時,便可真正刪除該文件。
此外,一些本地檢測重復文件的軟件同樣可以分別計算硬盤上文件的md5值,通過md5值來判斷文件是否重復。
消息摘要算法的有很廣泛的用途,限於篇幅就先介紹到這里。