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

Django实现读取数据库时自动加密解密_第1张图片
目录

很抱歉不能自动生成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就会被解密,这样一来,插入到数据库中的字段就是被解密了的。

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

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

你可能感兴趣的:(Django实现读取数据库时自动加密解密)