很抱歉不能自动生成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_en
和content_en
的值都是1
(数据库中1
代表True
,0代表False
)。
有问题自然要进行调试,我修改了一下代码的最后几行:
print OGA(self, 'title')
super(Article, self).save(*args, **kwargs)
print OGA(self, 'title')
即在调用Model
的save
函数前分别打印了一次title
真正的值,发现在调用save
前,title
是被正常加密了的(注意,我这时没有注释掉那一行代码)。但是调用save
后就很有趣了,title
真正的值又变成了未加密的值。
而当注释掉那行代码后,数据库中的值才是被加密过后的。对此,我的猜想(没错,是猜想)是在调用save函数时,Django使用了类似这样的代码:
self.title = self.title
对,你没有看错,就是这样,你要知道,我们对Article
的__getattribute__
方法进行了重写,本来title
是加密了的,但是一旦使用一次self.title = self.title
,title
就会被解密,这样一来,插入到数据库中的字段就是被解密了的。
感谢您耐着性子看完我的文章,这是我第一次发表文章,您的阅读是我最大的荣幸。
如需转载,请注明出处,尊重作者的劳动成果