
很抱歉簡書不能自動(dòng)生成Markdown的目錄,所以我做成了截圖以便讀者大致了解該文章的結(jié)構(gòu)
引言
最近在開發(fā)一個(gè)網(wǎng)站,使用的是Python2.7+Django,在向BAE發(fā)布的時(shí)候,數(shù)據(jù)庫出現(xiàn)了異常,原因是BAE基礎(chǔ)版提供的免費(fèi)數(shù)據(jù)庫中,不能插入含有數(shù)據(jù)庫關(guān)鍵字的字段,所以想出了通過使用ROT13算法對字段的內(nèi)容進(jìn)行轉(zhuǎn)換后在添加進(jìn)數(shù)據(jù)庫的辦法。
正文
實(shí)現(xiàn)Rot13算法
ROT13算法比較特殊,加密和解密都是同一個(gè)算法。如果你要換成其他加密解密算法,請注意區(qū)分加密和解密。
ROT13 = string.maketrans(\
"ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
"NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
def rot13_encode(text):
return string.translate(text, ROT13)
object(新式類)
實(shí)現(xiàn)自動(dòng)加密解密的功能需要對新式類有一個(gè)最基本的了解。
新式類是python2.2版本中引入的,所有新式類全部繼承自object類。
為什么要引入新式類呢?官方的解釋是:
為了統(tǒng)一類(class)和類型(type)。
這個(gè)和我們今天的內(nèi)容沒有太大關(guān)系,如果有興趣可以自己問度娘。
我們只需要知道新式類中添加了__getattribute__和__setattr__方法。
- 每次訪問新式類的實(shí)例的屬性時(shí),都會(huì)調(diào)用
__getattribute__方法。 - 而通過
__setattr__方法,我們可以實(shí)現(xiàn)在程序運(yùn)行時(shí)通過字符串動(dòng)態(tài)的設(shè)置實(shí)例屬性的功能,大大的方便我們的開發(fā)。
__getattribute__
#coding=utf-8
class A(object): #類A繼承自object,是一個(gè)新式類
def __init__(self, id=100):
self.id = id
a = A()
print a.id
#output: 100
print a.__getattribute__('id') #等價(jià)于a.id
#output: 100
我們嘗試著對__getattribute__重寫
#coding=utf-8
class A(object): #類A繼承自object,是一個(gè)新式類
def __init__(self, id=100):
self.id = id
def __getattribute__(self, attr):
#注意:我們使用A父類的__getattribute__方法獲取屬性
#如果直接使用getattr(self, attr)獲取attr屬性會(huì)造成死循環(huán)
#有興趣可以嘗試一下
return object.__getattribute__(self, attr) + 10
a = A()
print a.id
#output: 110
print object.__getattribute__(a, 'id')
#output: 100
我們可以看出訪問a的屬性id時(shí),調(diào)用了a的__getattribute__方法,得到的值是經(jīng)過處理的,而object.__getattribute__方法可以獲取到真正的內(nèi)容,請記住這兩個(gè)方法和它們的特性,一會(huì)兒我們會(huì)用到(這兩個(gè)方法就是這篇文章最核心的內(nèi)容)。
__setattr__
雖說__getattribute__是主角,但是少了__setattr__,我們的功能雖然也能完成,但是會(huì)很麻煩。
#coding=utf-8
class A(object):
def __init__(self, id = 100):
self.id = id
a = A()
print a.id
#output: 100
a.__setattr__('id', 120)
print a.id
#output: 120
好了,如果你學(xué)會(huì)了如何使用__getattribute__和__setattr__方法,那我們的預(yù)備工作就做好了,下面進(jìn)入正戲。
字段插入數(shù)據(jù)庫時(shí)自動(dòng)加密
通過對models.py中模型的save函數(shù)進(jìn)行了重寫,我們可以實(shí)現(xiàn)在保存時(shí)自動(dòng)調(diào)用ROT13算法進(jìn)行轉(zhuǎn)換的功能。
以Article模型為例:
#coding=utf-8
from django.db import models
#實(shí)現(xiàn)ROT13算法
#當(dāng)然,真正開發(fā)時(shí)該算法肯定不會(huì)寫在models.py里
#這里為了簡化代碼,直接寫在了這里
ROT13 = string.maketrans(\
"ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
"NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
def rot13_encode(text):
return string.translate(text, ROT13)
class Article(models.Model, object):
#以下為最初的字段(僅挑選出了相關(guān)字段)
title = models.CharField(max_length=200)
content = models.TextField()
hot = models.IntegerField(default=0) #文章的熱度,此字段無需加密
#使用以下字段記錄相關(guān)字段是否是加密狀態(tài)
#使用"{name}_en"的方式命名,便于操作
title_en = models.BooleanField(default=False)
content_en = models.BooleanField(default=False)
#encrypt_items記錄哪些字段需要加密
encrypt_items = ['content', 'title']
#重寫Model的save函數(shù),實(shí)現(xiàn)當(dāng)向數(shù)據(jù)庫插入字段時(shí)自動(dòng)加密的功能
def save(self, *args, **kwargs):
for attr in self.encrypt_items:
if not getattr(self, '%s_en' % attr):
self.__setattr__(attr, rot13_encode(getattr(self, attr)))
self.__setattr__('%s_en' % attr, True)
#調(diào)用父類Model的save函數(shù),進(jìn)行真正的數(shù)據(jù)庫插入操作
super(Article, self).save(*args, **kwargs)
從數(shù)據(jù)庫讀取時(shí)自動(dòng)進(jìn)行解密
重寫save函數(shù)的不足
一般我們用讀取數(shù)據(jù)庫內(nèi)容時(shí)會(huì)這樣寫:
art = Article.object.get(id=1)
title = art.title
content = art.content
但是在上一小節(jié),重寫過save后,讀取數(shù)據(jù)庫內(nèi)容時(shí)我們需要這樣寫:
art = Article.object.get(id=1)
title = rot13_encode(art.title)
content = rot13_encode(art.content)
這樣的話寫代碼的工作量會(huì)劇增,而且如果其他代碼已經(jīng)寫好,那就需要一行一行的改代碼——非常痛苦的工作。所以我們需要對Article進(jìn)行改進(jìn)。
實(shí)現(xiàn)讀取數(shù)據(jù)庫時(shí)自動(dòng)解密
#coding=utf-8
from django.db import models
#實(shí)現(xiàn)ROT13算法
ROT13 = string.maketrans(\
"ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
"NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
def rot13_encode(text):
return string.translate(text, ROT13)
#簡化函數(shù)名稱,便于調(diào)用
OGA = object.__getattribute__
REP = rot13_encode
class Article(models.Model, object):
title = models.CharField(max_length=200)
content = models.TextField()
hot = models.IntegerField(default=0) #文章的熱度,此字段無需加密
title_en = models.BooleanField(default=False)
content_en = models.BooleanField(default=False)
#encrypt_items記錄哪些字段需要加密
encrypt_items = ['content', 'title']
def __getattribute__(self, attr):
try:
if (attr in OGA(self, 'encrypt_items')) and OGA(self, '%s_en' % attr):
#若所查詢屬性(字段)需要加密且已經(jīng)被加密,解密后再返回
return REP(OGA(self, attr))
else:
#所查詢屬性無需加密或未被加密,直接返回
return OGA(self, attr)
except AttributeError as err:
#可能出現(xiàn)AttributeError異常,捕獲后直接上拋即可
raise err
#重寫Model的save函數(shù),實(shí)現(xiàn)當(dāng)向數(shù)據(jù)庫插入字段時(shí)自動(dòng)加密的功能
def save(self, *args, **kwargs):
for attr in self.encrypt_items:
if not OGA(self, '%s_en' % attr):
#注意:這里注釋掉了進(jìn)行加密的這行代碼。只有這樣才能正常加密!??!
#請往下閱讀,我會(huì)說明原因
#self.__setattr__(attr, REP(OGA(self, attr)))
self.__setattr__('%s_en' % attr, True)
#調(diào)用Model的save函數(shù)
super(Article, self).save(*args, **kwargs)
有的讀者可能會(huì)疑惑,為何去掉了那行真正進(jìn)行加密的代碼,才能正常加密呢?(沒有疑惑的話,這篇文章就已經(jīng)結(jié)束了,點(diǎn)個(gè)贊再走唄~)
最初我自己實(shí)現(xiàn)這個(gè)功能的時(shí)候并沒有去掉那行代碼(廢話?。?,然而當(dāng)我測試時(shí)發(fā)現(xiàn)數(shù)據(jù)庫中需要加密的字段并沒有被加密,但是title_en和content_en的值都是1(數(shù)據(jù)庫中1代表True,0代表False)。
有問題自然要進(jìn)行調(diào)試,我修改了一下代碼的最后幾行:
print OGA(self, 'title')
super(Article, self).save(*args, **kwargs)
print OGA(self, 'title')
即在調(diào)用Model的save函數(shù)前分別打印了一次title真正的值,發(fā)現(xiàn)在調(diào)用save前,title是被正常加密了的(注意,我這時(shí)沒有注釋掉那一行代碼)。但是調(diào)用save后就很有趣了,title真正的值又變成了未加密的值。
而當(dāng)注釋掉那行代碼后,數(shù)據(jù)庫中的值才是被加密過后的。對此,我的猜想(沒錯(cuò),是猜想)是在調(diào)用save函數(shù)時(shí),Django使用了類似這樣的代碼:
self.title = self.title
對,你沒有看錯(cuò),就是這樣,你要知道,我們對Article的__getattribute__方法進(jìn)行了重寫,本來title是加密了的,但是一旦使用一次self.title = self.title,title就會(huì)被解密,這樣一來,插入到數(shù)據(jù)庫中的字段就是被解密了的。
感謝您耐著性子看完我的文章,這是我第一次發(fā)表文章,您的閱讀是我最大的榮幸。
如需轉(zhuǎn)載,請注明出處,尊重作者的勞動(dòng)成果