Django实现读取数据库时自动加密解密

目录

很抱歉简书不能自动生成Markdown的目录,所以我做成了截图以便读者大致了解该文章的结构

引言

最近在开发一个网站,使用的是Python2.7+Django,在向BAE发布的时候,数据库出现了异常,原因是BAE基础版提供的免费数据库中,不能插入含有数据库关键字的字段,所以想出了通过使用ROT13算法对字段的内容进行转换后在添加进数据库的办法。

正文

实现Rot13算法

ROT13算法比较特殊,加密和解密都是同一个算法。如果你要换成其他加密解密算法,请注意区分加密和解密。

ROT13 = string.maketrans(\
         "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
         "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")

def rot13_encode(text):
    return string.translate(text, ROT13)

点击此处阅读详细内容

object(新式类)

实现自动加密解密的功能需要对新式类有一个最基本的了解。

新式类python2.2版本中引入的,所有新式类全部继承自object类。
为什么要引入新式类呢?官方的解释是:

为了统一类(class)和类型(type)。

这个和我们今天的内容没有太大关系,如果有兴趣可以自己问度娘。
我们只需要知道新式类中添加了__getattribute____setattr__方法。

  • 每次访问新式类的实例的属性时,都会调用__getattribute__方法。
  • 而通过__setattr__方法,我们可以实现在程序运行时通过字符串动态的设置实例属性的功能,大大的方便我们的开发。

__getattribute__

#coding=utf-8

class A(object):    #类A继承自object,是一个新式类
    def __init__(self, id=100):
        self.id = id

a = A()

print a.id
#output: 100

print a.__getattribute__('id')  #等价于a.id
#output: 100

我们尝试着对__getattribute__重写

#coding=utf-8

class A(object):    #类A继承自object,是一个新式类
    def __init__(self, id=100):
        self.id = id

    def __getattribute__(self, attr):
        #注意:我们使用A父类的__getattribute__方法获取属性
        #如果直接使用getattr(self, attr)获取attr属性会造成死循环
        #有兴趣可以尝试一下
        return object.__getattribute__(self, attr) + 10

a = A()

print a.id
#output: 110

print object.__getattribute__(a, 'id')
#output: 100

我们可以看出访问a的属性id时,调用了a的__getattribute__方法,得到的值是经过处理的,而object.__getattribute__方法可以获取到真正的内容,请记住这两个方法和它们的特性,一会儿我们会用到(这两个方法就是这篇文章最核心的内容)。

__setattr__

虽说__getattribute__是主角,但是少了__setattr__,我们的功能虽然也能完成,但是会很麻烦。

#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

好了,如果你学会了如何使用__getattribute____setattr__方法,那我们的预备工作就做好了,下面进入正戏。

字段插入数据库时自动加密

通过对models.py中模型的save函数进行了重写,我们可以实现在保存时自动调用ROT13算法进行转换的功能。

Article模型为例:

#coding=utf-8
from django.db import models

#实现ROT13算法
#当然,真正开发时该算法肯定不会写在models.py里
#这里为了简化代码,直接写在了这里
ROT13 = string.maketrans(\
         "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
         "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
def rot13_encode(text):
    return string.translate(text, ROT13)


class Article(models.Model, object):
    #以下为最初的字段(仅挑选出了相关字段)
    title = models.CharField(max_length=200)
    content = models.TextField()
    hot = models.IntegerField(default=0) #文章的热度,此字段无需加密

    #使用以下字段记录相关字段是否是加密状态
    #使用"{name}_en"的方式命名,便于操作
    title_en = models.BooleanField(default=False)
    content_en = models.BooleanField(default=False)

    #encrypt_items记录哪些字段需要加密
    encrypt_items = ['content', 'title']

    #重写Model的save函数,实现当向数据库插入字段时自动加密的功能
    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)
        #调用父类Model的save函数,进行真正的数据库插入操作
        super(Article, self).save(*args, **kwargs)

从数据库读取时自动进行解密

重写save函数的不足

一般我们用读取数据库内容时会这样写:

art = Article.object.get(id=1)
title = art.title
content = art.content

但是在上一小节,重写过save后,读取数据库内容时我们需要这样写:

art = Article.object.get(id=1)
title = rot13_encode(art.title)
content = rot13_encode(art.content)

这样的话写代码的工作量会剧增,而且如果其他代码已经写好,那就需要一行一行的改代码——非常痛苦的工作。所以我们需要对Article进行改进。

实现读取数据库时自动解密

#coding=utf-8
from django.db import models

#实现ROT13算法
ROT13 = string.maketrans(\
         "ABCDEFGHIJKLMabcdefghijklmNOPQRSTUVWXYZnopqrstuvwxyz",\
         "NOPQRSTUVWXYZnopqrstuvwxyzABCDEFGHIJKLMabcdefghijklm")
def rot13_encode(text):
    return string.translate(text, ROT13)

#简化函数名称,便于调用
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):
                #若所查询属性(字段)需要加密且已经被加密,解密后再返回
                return REP(OGA(self, attr))
            else:
                #所查询属性无需加密或未被加密,直接返回
                return OGA(self, attr)
        except AttributeError as err:
            #可能出现AttributeError异常,捕获后直接上抛即可
            raise err

    #重写Model的save函数,实现当向数据库插入字段时自动加密的功能
    def save(self, *args, **kwargs):
        for attr in self.encrypt_items:
            if not OGA(self, '%s_en' % attr):
                #注意:这里注释掉了进行加密的这行代码。只有这样才能正常加密!!!
                #请往下阅读,我会说明原因
                #self.__setattr__(attr, REP(OGA(self, attr)))
                self.__setattr__('%s_en' % attr, True)

        #调用Model的save函数
        super(Article, self).save(*args, **kwargs)

有的读者可能会疑惑,为何去掉了那行真正进行加密的代码,才能正常加密呢?(没有疑惑的话,这篇文章就已经结束了,点个赞再走呗~)

最初我自己实现这个功能的时候并没有去掉那行代码(废话!),然而当我测试时发现数据库中需要加密的字段并没有被加密,但是title_encontent_en的值都是1(数据库中1代表True,0代表False)。

有问题自然要进行调试,我修改了一下代码的最后几行:

print OGA(self, 'title')
super(Article, self).save(*args, **kwargs)
print OGA(self, 'title')

即在调用Modelsave函数前分别打印了一次title真正的值,发现在调用save前,title是被正常加密了的(注意,我这时没有注释掉那一行代码)。但是调用save后就很有趣了,title真正的值又变成了未加密的值。

而当注释掉那行代码后,数据库中的值才是被加密过后的。对此,我的猜想(没错,是猜想)是在调用save函数时,Django使用了类似这样的代码:

self.title = self.title

对,你没有看错,就是这样,你要知道,我们对Article__getattribute__方法进行了重写,本来title是加密了的,但是一旦使用一次self.title = self.titletitle就会被解密,这样一来,插入到数据库中的字段就是被解密了的。

感谢您耐着性子看完我的文章,这是我第一次发表文章,您的阅读是我最大的荣幸。

如需转载,请注明出处,尊重作者的劳动成果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容