Django實(shí)現(xiàn)讀取數(shù)據(jù)庫時(shí)自動(dòng)加密解密

目錄

很抱歉簡書不能自動(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)

點(diǎn)擊此處閱讀詳細(xì)內(nèi)容

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_encontent_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)用Modelsave函數(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)成果

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容