数据分析/机器学习模型的构建是极其复杂的活动,涵盖了业务的深入理解,数据的梳理选取,特征的表达试错,算法的设计改造,样本的逐条核对,模型的调参训练等一系列工作,有时甚至要对数据产生、传输、存储、加工等整个链路中可能存在的问题进行调研,以确保采用合适的方法对数据进行清理。因此每一个能够在生产环境产生有效价值的分析预测模型,都是应当有相应的知识产权保护的,这样才会有更多的人愿意沉下心来投入到这项高难度的工作中,生产更多的有价值的成果。
本文的protege就是为模型的产权保护而产生。
与PMML的理念完全不同,PMML的目的是实现模型的共享,如果模型采用了PMML,那么该模型所使用的数据、特征、算法完全是公开的。protege认为模型构建是一项创造性工作,模型的构建方法、思路是否公开,应该由创造者决定。
目录
What is it
protege是一个提供模块加密、授权和验证的Python包,用以保护创造性的程序开发。它的产生源于预测和分析类模型的商业授权。
protege is a Python package providing module encryption, authorization, and authentication to protect creative programming. It originated from the commercial authorization of prediction and analysis models.
Main Features
protege的主要功能特点:
- 开发者可以自定义长度为8的密钥(字符串)。
- 开发者可以通过授权项的参数来控制模块授权的时限、授权的机器,具体授权项如下:
授权项 | 注释 |
---|---|
module | 授权标识,参数为字符串,函数式应用可以自定义授权标识;作为protege.Protege子类应用,授权标识为module_name.class_name。 |
expire | 授权时限,参数为数值或日期字符串,默认值为无限期;数值表示授权的时长(单位:天);日期字符串表示授权的到期日(格式:YYYY-MM-DD)。 |
mid | 授权机器码,参数为字符串。 |
bind_mid | 是否绑定机器码,True/False,默认值为True,如果为True,将判断授权机器码与实际是否一致。 |
check_systime | 是否验证系统时间,True/False,默认值为False,如果为True,将判断操作系统的时间是否可能有误。 |
- 开发者可以对模块输出的关键信息进行加密,并通过授权项的参数控制关键信息解密的时限、次数、数量,具体授权项如下:
授权项 | 注释 |
---|---|
iden_expire | 解密时限,参数为数值或日期字符串,数值表示解密的时长(单位:天);日期字符串表示解密的到期日(格式:YYYY-MM-DD)。 |
iden_1st_expire | 首次解密时限,参数为数值或日期字符串,数值表示首次解密的时长(单位:天);日期字符串表示首次解密的到期日(格式:YYYY-MM-DD)。 |
iden_max_interval | 两次解密最大间隔,参数为数值,表示两次解密时间之间的最大间隔(单位:天)。 |
iden_times | 解密次数,参数为数值,解密的最大次数。 |
iden_quantity | 解密的数量,参数为数值,解密的最大数量。 |
iden_filt_func | 解密过滤条件,参数为以callable对象为内容的字符串,如’sum’、‘lambda x:x>1’,默认值是’lambda x:random.random()<0.1’。 |
iden_over | 解密数量或次数超限的处理,‘raise’/‘update’,默认值为’raise’,'raise’表示抛出异常,'update’表示更换解密密钥(该密钥用以加密模块输出的关键信息,由protege随机生成,不是开发者自定义的密钥)。 |
iden_imprint | 是否记录解密次数、数量,True/False,,默认值为False,如果为True,将记录解密的累计次数和数量。 |
- 当模块还依赖其它需要授权的模块时,开发者可以通过requires授权项,给予依赖模块同等授权,具体授权项如下:
授权项 | 注释 |
---|---|
requires | 依赖模块,参数为list,list中列出所有依赖模块的授权标识。 |
- 开发者可以新增自定义授权项,如授权等级。
- 开发者可以设置外部无法访问的类属性。
- 签发授权时,授权项可以忽略。如果授权项有默认值,忽略表示采用默认值;
如果授权项没有默认值,忽略表示不验证这一项。 - 指定授权标识和提供开发者自定义的密钥,可以查看授权项和参数。
- 开发者自定义的密钥不可查看。
- protege的验证功能不可更改,以防止欺骗验证。
- 开发者可以用protege对自己开发的模块进行编译,避免提供源码。
Where to get it
最新版本的protege源码和编译安装包可以在Python package index
获取。
The source code and binary installers for the latest released version are available at the [Python package index].
https://pypi.org/project/protege
可以用pip安装protege。
You can install protege like this:
pip install protege
也可以用setup.py
安装。
Or in the protege
directory, execute:
python setup.py install
How to use it
查看用户机器码
import protege
protege.identity()
运行结果示例
'896d9225bfb14429d603e5bad069a21a'
开发者签发授权
import protege
passport=[
{'module':'test_1',#第一个授权模块的授权标识
'level':'trail',#自定义的授权项
'expire':365*10,#授权时限3650天
'mid':'896d9225bfb14429d603e5bad069a21a',#授权机器码,与用户机器码保持一致
'bind_mid':True,#绑定机器码
'check_systime':True,#验证系统时间
'requires':['test_3'],#依赖模块
'iden_times':100,#解密10次
'iden_quantity':600000,#解密数量600000个
'iden_expire':365,#解密时限365天
'iden_1st_expire':'2022-03-01',#首次解密不晚于2022-03-01
'iden_max_interval':90,#两次解密最大间隔不超过90天
'iden_over':'update',#解密数量或次数超限后,更换解密密钥
'iden_filt_func':'lambda x:True',#解密过滤条件,当待解密信息满足“索引1的值能被5整除”时才解密
},
{'module':'__main__.test_2',#第二个授权模块的授权标识,__main__为模块名,test_2为类名
'mid':'123',#授权机器码,
'bind_mid':False,#不绑定机器码,授权机器码可不与用户机器码保持一致
'expire':'2022-12-31',#授权时限至2022-12-31
'iden_filt_func':'lambda x:int(x[1])%5==0',#解密过滤条件,全部解密
'iden_imprint':False#不记录解密次数、数量
}
]
protege.issue(passport,key='12345678')
运行结果示例
'<pass time="1655012229.5737512" cipher="jHjiOEBXSTHZPR5eqAHy/jp03cjRsaVUzKKFz3nadfAbHGNq5Xt3v/cTvHMFTub0xlafWlr2FZjUeXCA7pR/vv15L52BDN83+3oe" flag="receipt"/>'
将签发的授权保存为文件,passport
为文件名
protege.issue(passport,key='12345678',file_path='passport')
查看授权
import protege
protege.check('test_2',key='12345678',file_path='passport')
运行结果示例
{'bind_mid': True,
'check_systime': True,
'expire': 1970372507.8829,
'iden_1st_expire': 1665012507.0,
'iden_expire': 1695012507.0,
'iden_filt_func': <function protege.protege.<lambda>>,
'iden_max_interval': 90,
'iden_over': 'update',
'iden_quantity': 600000,
'iden_times': 100,
'issue_time': 1655012507.8829,#签发授权的时间
'level': 'trail',
'mid': '896d9225bfb14429d603e5bad069a21a',
'module': 'test_1',
'receipt_time': 1655012507.9315019,#接受授权的时间
'remain': 3649.9998979187785,#授权剩余时长(单位:天)
'requires': ['test_3'],
'status': {}}
用户接受授权
注意:用户接受授权为非必要操作,如果执行该操作,将会把授权信息嵌入protege包内,那么在验证授权时就无需再提供授权文件的路径
import protege
protege.receipt(file_path='passport')
模块验证授权
函数式应用
import protege
def test(file_path):
checked=protege.check('test_1',key='12345678',file_path=file_path)
print('test_1 checked success')
#do something, you can use the item of checked
test(file_path='passport')
protege.Protege子类应用
import protege,sys
class test_2(protege.Protege):
def __init__(self,**kwargs):
super(test_2,self).__init__(attrs=kwargs.get('attrs',[]),file_path=kwargs.get('file_path'))
self._1=1
self._2=2
def func_1(self):
f_global_name=sys._getframe().f_globals['__name__']; sys._getframe().f_globals['__name__']=self.__module__+'.'+self.__class__.__name__
print('self._1',self._1,'self._2',self._2)
sys._getframe().f_globals['__name__']=f_global_name
def func_2(self):
self.check='12345678'
checked=self.check
print('test_2.func_2 checked success')
def func_3(self):
self.check='12345678'
checked=self.check
checked['iden_filt_func']=lambda x:True
print('test_2.func_3.iden success:',self.iden([1,2,3],checked=checked))
def func_4(self,ids,id_var,file_path):
self.check='12345678'
checked=self.check
self.iden(ids,id_var=id_var,checked=checked,update=True,file_path=file_path)
print('test_2.func_4.iden success, 解密信息已保存在:',file_path)
t=test_2(attrs=['_1'],file_path='passport')
t=test_2(attrs=['_1'],file_path=['passport','passport'])#多个授权文件,文件名放在list里
#_1是外部无法访问的属性,查看_1、_2在内外部无法访问时的区别
print('self._1',t._1,'self._2',t._2)
t.func_1()
#验证
t.func_2()
#加密关键信息
t.func_3()
运行结果示例
self._1 None self._2 2
self._1 1 self._2 2
test_2.func_2 checked success
test_2.func_3.iden success: [(1, 'obA7E/ZedeQ='), (2, 'cvm4Ezf4A84='), (3, 'TRoMrlqLxls=')]
#制作关键信息解密文件,
import numpy,pandas
ids=pandas.DataFrame(numpy.random.randint(1,20,size=(100,3)))
t.func_4(ids,id_var=2,file_path='iden')
解密文件保存在iden
文件中,该文件内容如下所示:
4,IHvbSM2wwX8=
7,XnLremSGBNc=
12,DXre50LiYcc=
8,HL2kXHECV14=
8,HL2kXHECV14=
6,0iSEPK9aGEI=
7,XnLremSGBNc=
4,IHvbSM2wwX8=
1,mhhAZ5BSE9c=
5,UkSOItgDANM=
11,F2RpOJKIC70=
19,dW/H5Di+cDI=
...
按照授权项{'iden_filt_func':'lambda x:int(x[1])%5==0'}
, ids中当列1
的值能被5整除时,被加密的列2
才是正确的,当列1
的值不能被5整除时,被加密的列2
是随机输出的。
模块编译
开发者在应用protege时,需要在程序使用自定义的密钥,为了不泄漏密钥,开发者可以对程序进行编译,将程序编译成.c
、以及.pyd
(Windows)或.o
(Linux),编译后的模块可直接作为python模块,使用import导入。
注意:模块编译需要操作系统具备C编译库,Windows需要Microsoft Visual C++ 14.0
,Linux需要glibc-2.14
示例,将如下代码保存为test.py
文件。
import protege
def test(file_path):
checked=protege.check('test_1',key='12345678',file_path=file_path)
print('test_1 checked success')
#do something, you can use the item of checked
执行编译
import protege
protege.build('test.py')
运行成功后,即可在当前目录下看到test.pyd
或test.o
文件,在test.py
同目录下,可以看到test.c
文件
假设,test.pyd
或test.o
放在C:/
目录下,用如下方式导入和使用模块:
import sys
sys.path.append('C:/')
import test
test.test(file_path='passport')
Exceptions
-
key must be not None:
protege.issue()签发授权时,key不能为空 -
no pass:
protege.receipt()接受授权时,passport和file_path不能全为空 -
pass cannot be found:
protege.check()授权验证时,无法找到授权项 -
pass cannot be parsed:
protege.check()授权验证时,由于提供的key不对,无法解析授权项 -
pass not this module:
protege.check()授权验证时,没有发现本模块的授权,需检查授权标识是否正确,protege.Protege子类的授权标识为module_name.class_name,当类不属于任何module时,module_name是__main__ -
pass not this machine:
protege.check()授权验证时,没有与实际机器码一致的授权 -
sys time error:
protege.check()授权验证时,发现操作系统时间可能存在误差,如果是微小的误差,可以稍后再试 -
pass expired:
protege.check()授权验证时,已超过授权期限 -
used_times or used_quantity more than limit:
Protege.iden()制作关键信息解密文件时,已超过次数或数量限制 -
iden_expire:
Protege.iden()制作关键信息解密文件时,已超过期限 -
iden_1st_expire:
Protege.iden()制作关键信息解密文件时,已超过首次期限 -
iden_max_interval:
Protege.iden()制作关键信息解密文件时,已超过两次最大时间间隔 -
Microsoft Visual C++ 14.0 is required:
protege.build()编译模块时,操作系统没有C++编译库;Linux下会报glic错误。 -
each element of ‘ext_modules’ option must be an Extension instance or 2-tuple:
protege.build()编译模块时,操作系统没有C++编译库,在spyder等集成开发环境下会报这个错。
Others
如何确保protege不被修改
通过md5验证protege的文件是否被更改,可以确保protege不被修改
import protege,hashlib
hashlib.md5(open(protege.protege.__file__,'rb').read()).hexdigest()