20221320冯泰瑞-实验四密码模块应用实践过程记录
实践要求
- 完成电子公文交换系统,系统功能,(15 分)
- 总体要求
- 项目类型必须是B/S或C/S架构
- 项目程序设计语言可以是C,Python,Rust等
- 三员制度是指将系统管理员、安全保密管理员和安全审计员三个角色分离,分别负责系统运行、安全保密和安全管理,相互制约,共同保障信息系统安全。三员职责
- 系统管理员
- 负责信息系统的日常维护、故障处理和升级更新。
- 确保系统正常运行,对系统资源进行合理分配。
- 负责用户账号的创建、修改和删除。
- 定期备份重要数据,确保数据安全。
- 安全保密管理员
- 负责制定和实施安全保密策略,确保信息系统安全。
- 对用户进行安全意识培训,提高用户安全防范能力。
- 监控网络安全状况,发现异常情况及时处理。
- 负责信息系统安全事件的应急响应和处理。
- 安全审计员
- 负责对信息系统进行安全审计,评估安全风险。
- 监督系统管理员和安全保密管理员的工作,确保其履行职责。
- 对信息系统安全事件进行调查,提出整改建议。
- 黄金法则(5 分)
- 身份鉴别:口令不能存,数据库要保存加盐的SM3Hash值
- 访问控制:操作员,审核员,安全三员的权限设置
- 安全审计:至少完成日志查询功能
- 密码(15 分)
- 算法:SM2,SM3,SM4,推荐使用 Key
- 密钥管理:所有私钥,对称算法密钥等不能明存
- 系统量化评估(5分)
- 按照商用密码应用安全性评估量化评估规则,计算自己系统的得分,只计算应用和数据安全。
- 提交要求:
-
提交实践过程Markdown和转化的PDF文件
-
代码,文档托管到gitee或github等,推荐 gitclone
-
记录实验过程中遇到的问题,解决过程,反思等内容,用于后面实验报告
开源仓库
我使用了前辈之前的仓库,仓库地址为青青草原七匹狼/电子公文传输系统,使用git clone
命令下载即可。
环境配置
系统部分环境需要进行修改,配置过程如下所示:
个人配置
Windows 11
Python: 3.13
参考博客:(超详细)Python+PyCharm的安装步骤及PyCharm的使用(含快捷键)
Pycharm: Community Edition 2024.3
参考博客:(超详细)Python+PyCharm的安装步骤及PyCharm的使用(含快捷键)
OpenSSL:3.4.0 22 Oct ~2024
参考博客:环境篇-Windows下安装OpenSSL
MySQL:9.0
参考博客:Windows环境下MySQL安装与配置(超详细、超细致)
环境配置
在D盘(推荐)使用git clone https://gitee.com/Electronic-document-transfer-system/Document-transmission.git
命令获取源代码,使用Pycharm打开项目,项目结构如下所示。
在左上角中,根据文件->设置->Project:Document-transmission->Python Interpreter的路径打开解释器,点击加号以安装其他包。
在里面搜索如下包进行安装:Django
、PyPDF2
、captcha
、django-sslserver
、filetype
、gmssl
、mysqlclient
、standard-imghdr
、django-simple-captcha
包。
接着,需要我们创建名为testdocument
的数据库。
在命令行中使用如下命令进行数据表的创建。
python manage.py makemigrations
python manage.py migrate
显示如下提示代表创建成功。
(.venv) PS D:\信息安全系统设计\Document-transmission-master\Document-transmission-master> python manage.py migrate
System check identified some issues:WARNINGS:
user.MyUser: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'.HINT: Configure the DEFAULT_AUTO_FIELD setting or the UserConfig.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'.
Operations to perform:Apply all migrations: admin, auth, captcha, contenttypes, index, sessions, user
Running migrations:Applying contenttypes.0001_initial... OKApplying contenttypes.0002_remove_content_type_name... OKApplying auth.0001_initial... OKApplying auth.0002_alter_permission_name_max_length... OKApplying auth.0003_alter_user_email_max_length... OKApplying auth.0004_alter_user_username_opts... OKApplying auth.0005_alter_user_last_login_null... OKApplying auth.0006_require_contenttypes_0002... OKApplying auth.0007_alter_validators_add_error_messages... OKApplying auth.0008_alter_user_username_max_length... OKApplying auth.0009_alter_user_last_name_max_length... OKApplying auth.0010_alter_group_name_max_length... OKApplying auth.0011_update_proxy_permissions... OKApplying user.0001_initial... OKApplying admin.0001_initial... OKApplying admin.0002_logentry_remove_auto_add... OKApplying admin.0003_logentry_add_action_flag_choices... OKApplying auth.0012_alter_user_first_name_max_length... OKApplying captcha.0001_initial... OKApplying captcha.0002_alter_captchastore_id... OKApplying index.0001_initial... OKApplying index.0002_auto_20210601_1010... OKApplying index.0003_document_type... OKApplying index.0004_document_lyrics... OKApplying index.0005_auto_20210602_0952... OKApplying index.0006_auto_20210602_0953... OKApplying index.0007_document_key... OKApplying index.0008_auto_20210605_1920... OKApplying sessions.0001_initial... OKApplying user.0002_auto_20210605_1743... OKApplying user.0003_alter_myuser_first_name... OK
index_document
表里面有一个外键,不知道有什么用,但是带着这个外键又会报错。我们在SQL
命令行把这个外键删了,使用如下命令:
use testdocument;
alter table index_document drop foreign key index_document_label_id_b5ce761f_fk_index_label_id;
之后的报错记录就又少了一条。紧接着,在electronicDocument/settings.py
中修改数据库登录的密码,第91行中有如下代码:
'PASSWORD': '1619553792',
把这个1619553792
修改成数据库的密码123456
即可。如果登录账户不是root
,在上一行中的'USER': 'root'
进行类似的修改即可。
代码修改
首先是SSL
的问题,如果直接运行项目,可能会报这样的错:
AttributeError: module 'ssl' has no attribute 'wrap_socket'
这是因为包的版本更新了啊,没有这个用法了,把对应文件修改一下就可以。
打开项目中的runsslserver.py
文件,第48行有一个SecureHTTPServer
类,将其修改为如下所示:
class SecureHTTPServer(ThreadedWSGIServer):def __init__(self, address, handler_cls, certificate, key, ipv6=False):super(SecureHTTPServer, self).__init__(address, handler_cls, ipv6=ipv6)context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)context.load_cert_chain(certificate, key)self.socket = context.wrap_socket(self.socket,server_side=True,)
之后SSL
的配置就完成了。
在users/views.py
,第88行有一个uploadView
函数,这个函数需要大改一下。
我们可以看到里面有很多D:/electronicDocument/meida/...
这样的路径。这也是我推荐把项目克隆到D盘的原因,这个项目里有很多的文件路径都是绝对路径。
我们的路径里面没有electronicDocument
。如果直接把项目克隆到了D盘,直接把它替换成Document-transmission-master
即可。不在D盘的情况下把替换为对应路径。其他文件中也会出现类似的问题,如果在运行项目时,出现FileNotFound
的问题,通常就是因为这个没有修改,运行过程中出现报错再修改也可以。
还有PyPDF2
的一些方法也被修改了,在200行附近,可以看到如下代码:
file_reader = PdfFileReader("D:\\electronicDocument\\media\\documentFile\\"+str(myFile))file_writer = PdfFileWriter()for page in range(file_reader.getNumPages()):file_writer.addPage(file_reader.getPage(page))
遗憾的是一些方法已经使用不了了,将其修改为如下代码:
file_reader = PdfReader("D:\\Document-transmission-msaster\\media\\documentFile\\"+str(myFile))file_writer = PdfWriter()for page in range(len(file_reader.pages)):file_writer.add_page(file_reader.pages[page])
这样上传公文的功能就可以使用了。
修改到目前,这种状况只能查看文件名为纯英文的文件,不能上传为文件名含有其他语言字符的文件,这是由于查看公文时传到后台的文件名中的其他语言字符进行了URL
编码,但是查找文件的没有将编码转化为对应字符。
在play/views.py
中,开头第一行写入from urllib.parse import unquote
进行包的导入。在第10行左右有一个playview
函数,下滑到65行左右有一行代码file = documents.file.url[1::]
,在这行的下一行添加如下代码:
file = unquote(file)
再下滑到最底部,有一个downloadr
函数。同理,在115行左右的file = document.file.url[1::]
下一行中添加如下代码:
file = unquote(file)
这样就能进行相关的文件查看和下载了。
到此,代码几乎修改完成。
项目运行
项目需要我们找到development.crt
和development.key
两个文件,应该在Python中Lib\site-package\sslserver\certs
文件夹里面,我的路径是D:\信息安全系 ocument-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crt
和D:\信息安全系统设计\Document-transmission-master\.venv\Libckages\sslserver\certs\development.key
。
在终端中使用如下命令启动服务:
(.venv) PS D:\信息安全系统设计\Document-transmission-master\Document-transmission-master> python manage.py runsslserver --certificate D:\信息安全系 ocument-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crt --key D:\信息安全系统设计\Document-transmission-master\.venv\Libckages\sslserver\certs\development.key
--certificate
用于指定证书,--key
用于指定私钥。
之后可能会有一些报错,应该是因为缺失某些包,按照上述安装包的方式一起安装就可以了;也可能是因为端口已经被占用,这时候需要杀死端口所在进程。
在这之后,如果出现如下提升,便证明服务启动成功:
Watching for file changes with StatReloader
Validating models...System check identified some issues:WARNINGS:
user.MyUser: (models.W042) Auto-created primary key used when not defining a primary key type, by default 'django.db.models.AutoField'.HINT: Configure the DEFAULT_AUTO_FIELD setting or the UserConfig.default_auto_field attribute to point to a subclass of AutoField, e.g. 'django.db.models.BigAutoField'.System check identified 1 issue (0 silenced).
December 08, 2024 - 15:54:27
Django version 5.1.4, using settings 'electronicDocument.settings'
Starting development server at https://127.0.0.1:8000/
Using SSL certificate: D:\信息安全系统设计\Document-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crt
Using SSL key: D:\信息安全系统设计\Document-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.key
Quit the server with CTRL-BREAK.
此时,你的服务已经启动成功,可以在https://127.0.0.1:8000/
进行相关操作了。
完成电子公文交换系统,系统功能展示
操作员界面
注册界面
登陆界面
首页
用户中心
发文
发送公文
从本地上传公文(公开)
成功上传
从本地上传公文(私密)
成功上传
查看公文(附有SM3哈希值后16位,可用于文件完整性检验)
公开的公文
私密的公文
审核公文(公文批复)
查询公文
收文
公文签收(下载公文)
查看公文(附有哈希值,可用于文件完整性检验)
查询公文
后台管理员界面(系统管理员、用户管理员)
修改数据库内is_superuser
is_staff
is_secert
的值从0变为1,使得我能顺利地在站点管理页面成功登陆
登录界面
首页
用户管理员
增、删、改用户
需要录入用户名、密码等信息
权限设置(用户认证授权)[是否为超级用户(审计员、安全审计员、安全保密管理员、系统管理员、操作员)]
系统管理员
系统日志(查看公文信息)
可以查看公文标题、上传者、发文机关、上传时间、公文缩略图
组织单位(根据发文单位筛选公文)
以中办电科院为发文机关的公文
以北京电子科技学院为发文机关的公文
黄金法则
身份鉴别:口令不能明存,数据库要保存加盐的SM3Hash值
file = documents.file.url[1::]file = unquote(file)path_doc = os.path.join("D:/信息安全系统设计/Document-transmission-master/Document-transmission-master/", file)# print(path_doc)try:sm3_hash = "openssl dgst -sm3 " + path_docbash_r = os.popen(sm3_hash)info = bash_r.readlines() # 读取命令行的输出到一个listfor line in info: # 按行遍历line = line.strip('\r\n')hash_16 =line[-16::]print(hash_16)# print(line)except:print('sm3 error')return render(request, 'play.html', locals())
访问控制:操作员,审核员,安全三员的权限设置
详见前面系统展示部分
安全审计:至少完成日志查询功能
可以查看公文标题、上传者、发文机关、上传时间、公文缩略图
密码
算法:SM2,SM3,SM4,推荐使用 Key
部分代码展示如下:
sm2.py
import binascii
from random import choice
from . import sm3, func
from Cryptodome.Util.asn1 import DerSequence, DerInteger
from binascii import unhexlify
# 选择素域,设置椭圆曲线参数default_ecc_table = {'n': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123','p': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF','g': '32c4ae2c1f1981195f9904466a39c9948fe30bbff2660be1715a4589334c74c7''bc3736a2f4f6779c59bdcee36b692153d0a9877cc62a474002df32e52139f0a0','a': 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC','b': '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
}class CryptSM2(object):def __init__(self, private_key, public_key, ecc_table=default_ecc_table, mode=0, asn1=False):"""mode: 0-C1C2C3, 1-C1C3C2 (default is 1)"""self.private_key = private_keyself.public_key = public_key.lstrip("04") if public_key.startswith("04") else public_keyself.para_len = len(ecc_table['n'])self.ecc_a3 = (int(ecc_table['a'], base=16) + 3) % int(ecc_table['p'], base=16)self.ecc_table = ecc_tableassert mode in (0, 1), 'mode must be one of (0, 1)'self.mode = modeself.asn1 = asn1def _kg(self, k, Point): # kP运算Point = '%s%s' % (Point, '1')mask_str = '8'for i in range(self.para_len - 1):mask_str += '0'mask = int(mask_str, 16)Temp = Pointflag = Falsefor n in range(self.para_len * 4):if (flag):Temp = self._double_point(Temp)if (k & mask) != 0:if (flag):Temp = self._add_point(Temp, Point)else:flag = TrueTemp = Pointk = k << 1return self._convert_jacb_to_nor(Temp)def _double_point(self, Point): # 倍点l = len(Point)len_2 = 2 * self.para_lenif l < self.para_len * 2:return Noneelse:x1 = int(Point[0:self.para_len], 16)y1 = int(Point[self.para_len:len_2], 16)if l == len_2:z1 = 1else:z1 = int(Point[len_2:], 16)T6 = (z1 * z1) % int(self.ecc_table['p'], base=16)T2 = (y1 * y1) % int(self.ecc_table['p'], base=16)T3 = (x1 + T6) % int(self.ecc_table['p'], base=16)T4 = (x1 - T6) % int(self.ecc_table['p'], base=16)T1 = (T3 * T4) % int(self.ecc_table['p'], base=16)T3 = (y1 * z1) % int(self.ecc_table['p'], base=16)T4 = (T2 * 8) % int(self.ecc_table['p'], base=16)T5 = (x1 * T4) % int(self.ecc_table['p'], base=16)T1 = (T1 * 3) % int(self.ecc_table['p'], base=16)T6 = (T6 * T6) % int(self.ecc_table['p'], base=16)T6 = (self.ecc_a3 * T6) % int(self.ecc_table['p'], base=16)T1 = (T1 + T6) % int(self.ecc_table['p'], base=16)z3 = (T3 + T3) % int(self.ecc_table['p'], base=16)T3 = (T1 * T1) % int(self.ecc_table['p'], base=16)T2 = (T2 * T4) % int(self.ecc_table['p'], base=16)x3 = (T3 - T5) % int(self.ecc_table['p'], base=16)if (T5 % 2) == 1:T4 = (T5 + ((T5 + int(self.ecc_table['p'], base=16)) >> 1) - T3) % int(self.ecc_table['p'], base=16)else:T4 = (T5 + (T5 >> 1) - T3) % int(self.ecc_table['p'], base=16)T1 = (T1 * T4) % int(self.ecc_table['p'], base=16)y3 = (T1 - T2) % int(self.ecc_table['p'], base=16)form = '%%0%dx' % self.para_lenform = form * 3return form % (x3, y3, z3)def _add_point(self, P1, P2): # 点加函数,P2点为仿射坐标即z=1,P1为Jacobian加重射影坐标len_2 = 2 * self.para_lenl1 = len(P1)l2 = len(P2)if (l1 < len_2) or (l2 < len_2):return Noneelse:X1 = int(P1[0:self.para_len], 16)Y1 = int(P1[self.para_len:len_2], 16)if (l1 == len_2):Z1 = 1else:Z1 = int(P1[len_2:], 16)x2 = int(P2[0:self.para_len], 16)y2 = int(P2[self.para_len:len_2], 16)T1 = (Z1 * Z1) % int(self.ecc_table['p'], base=16)T2 = (y2 * Z1) % int(self.ecc_table['p'], base=16)T3 = (x2 * T1) % int(self.ecc_table['p'], base=16)T1 = (T1 * T2) % int(self.ecc_table['p'], base=16)T2 = (T3 - X1) % int(self.ecc_table['p'], base=16)T3 = (T3 + X1) % int(self.ecc_table['p'], base=16)T4 = (T2 * T2) % int(self.ecc_table['p'], base=16)T1 = (T1 - Y1) % int(self.ecc_table['p'], base=16)Z3 = (Z1 * T2) % int(self.ecc_table['p'], base=16)T2 = (T2 * T4) % int(self.ecc_table['p'], base=16)T3 = (T3 * T4) % int(self.ecc_table['p'], base=16)T5 = (T1 * T1) % int(self.ecc_table['p'], base=16)T4 = (X1 * T4) % int(self.ecc_table['p'], base=16)X3 = (T5 - T3) % int(self.ecc_table['p'], base=16)T2 = (Y1 * T2) % int(self.ecc_table['p'], base=16)T3 = (T4 - X3) % int(self.ecc_table['p'], base=16)T1 = (T1 * T3) % int(self.ecc_table['p'], base=16)Y3 = (T1 - T2) % int(self.ecc_table['p'], base=16)form = '%%0%dx' % self.para_lenform = form * 3return form % (X3, Y3, Z3)def _convert_jacb_to_nor(self, Point): # Jacobian加重射影坐标转换成仿射坐标len_2 = 2 * self.para_lenx = int(Point[0:self.para_len], 16)y = int(Point[self.para_len:len_2], 16)z = int(Point[len_2:], 16)z_inv = pow(z, int(self.ecc_table['p'], base=16) - 2, int(self.ecc_table['p'], base=16))z_invSquar = (z_inv * z_inv) % int(self.ecc_table['p'], base=16)z_invQube = (z_invSquar * z_inv) % int(self.ecc_table['p'], base=16)x_new = (x * z_invSquar) % int(self.ecc_table['p'], base=16)y_new = (y * z_invQube) % int(self.ecc_table['p'], base=16)z_new = (z * z_inv) % int(self.ecc_table['p'], base=16)if z_new == 1:form = '%%0%dx' % self.para_lenform = form * 2return form % (x_new, y_new)else:return Nonedef verify(self, Sign, data):# 验签函数,sign签名r||s,E消息hash,public_key公钥if self.asn1:unhex_sign = unhexlify(Sign.encode())seq_der = DerSequence()origin_sign = seq_der.decode(unhex_sign)r = origin_sign[0]s = origin_sign[1]else:r = int(Sign[0:self.para_len], 16)s = int(Sign[self.para_len:2*self.para_len], 16)e = int(data.hex(), 16)t = (r + s) % int(self.ecc_table['n'], base=16)if t == 0:return 0P1 = self._kg(s, self.ecc_table['g'])P2 = self._kg(t, self.public_key)# print(P1)# print(P2)if P1 == P2:P1 = '%s%s' % (P1, 1)P1 = self._double_point(P1)else:P1 = '%s%s' % (P1, 1)P1 = self._add_point(P1, P2)P1 = self._convert_jacb_to_nor(P1)x = int(P1[0:self.para_len], 16)return r == ((e + x) % int(self.ecc_table['n'], base=16))def sign(self, data, K):"""签名函数, data消息的hash,private_key私钥,K随机数,均为16进制字符串:param self: :param data: data消息的hash:param K: K随机数:return: """E = data.hex() # 消息转化为16进制字符串e = int(E, 16)d = int(self.private_key, 16)k = int(K, 16)P1 = self._kg(k, self.ecc_table['g'])x = int(P1[0:self.para_len], 16)R = ((e + x) % int(self.ecc_table['n'], base=16))if R == 0 or R + k == int(self.ecc_table['n'], base=16):return Noned_1 = pow(d+1, int(self.ecc_table['n'], base=16) - 2, int(self.ecc_table['n'], base=16))S = (d_1*(k + R) - R) % int(self.ecc_table['n'], base=16)if S == 0:return Noneelif self.asn1:return DerSequence([DerInteger(R), DerInteger(S)]).encode().hex()else:return '%064x%064x' % (R, S)def encrypt(self, data):# 加密函数,data消息(bytes)msg = data.hex() # 消息转化为16进制字符串k = func.random_hex(self.para_len)C1 = self._kg(int(k, 16), self.ecc_table['g'])xy = self._kg(int(k, 16), self.public_key)x2 = xy[0:self.para_len]y2 = xy[self.para_len:2*self.para_len]ml = len(msg)t = sm3.sm3_kdf(xy.encode('utf8'), ml/2)if int(t, 16) == 0:return Noneelse:form = '%%0%dx' % mlC2 = form % (int(msg, 16) ^ int(t, 16))C3 = sm3.sm3_hash([i for i in bytes.fromhex('%s%s%s' % (x2, msg, y2))])if self.mode:return bytes.fromhex('%s%s%s' % (C1, C3, C2))else:return bytes.fromhex('%s%s%s' % (C1, C2, C3))def decrypt(self, data):# 解密函数,data密文(bytes)data = data.hex()len_2 = 2 * self.para_lenlen_3 = len_2 + 64C1 = data[0:len_2]if self.mode:C3 = data[len_2:len_3]C2 = data[len_3:]else:C2 = data[len_2:-64]C3 = data[-64:]xy = self._kg(int(self.private_key, 16), C1)# print('xy = %s' % xy)x2 = xy[0:self.para_len]y2 = xy[self.para_len:len_2]cl = len(C2)t = sm3.sm3_kdf(xy.encode('utf8'), cl/2)if int(t, 16) == 0:return Noneelse:form = '%%0%dx' % clM = form % (int(C2, 16) ^ int(t, 16))u = sm3.sm3_hash([i for i in bytes.fromhex('%s%s%s' % (x2, M, y2))])return bytes.fromhex(M)def _sm3_z(self, data):"""SM3WITHSM2 签名规则: SM2.sign(SM3(Z+MSG),PrivateKey)其中: z = Hash256(Len(ID) + ID + a + b + xG + yG + xA + yA)"""# sm3withsm2 的 z 值z = '0080'+'31323334353637383132333435363738' + \self.ecc_table['a'] + self.ecc_table['b'] + self.ecc_table['g'] + \self.public_keyz = binascii.a2b_hex(z)Za = sm3.sm3_hash(func.bytes_to_list(z))M_ = (Za + data.hex()).encode('utf-8')e = sm3.sm3_hash(func.bytes_to_list(binascii.a2b_hex(M_)))return edef sign_with_sm3(self, data, random_hex_str=None):sign_data = binascii.a2b_hex(self._sm3_z(data).encode('utf-8'))if random_hex_str is None:random_hex_str = func.random_hex(self.para_len)sign = self.sign(sign_data, random_hex_str) # 16进制return signdef verify_with_sm3(self, sign, data):sign_data = binascii.a2b_hex(self._sm3_z(data).encode('utf-8'))return self.verify(sign, sign_data)
sm3.py
import binascii
from math import ceil
from .func import rotl, bytes_to_listIV = [1937774191, 1226093241, 388252375, 3666478592,2842636476, 372324522, 3817729613, 2969243214,
]T_j = [2043430169, 2043430169, 2043430169, 2043430169, 2043430169, 2043430169,2043430169, 2043430169, 2043430169, 2043430169, 2043430169, 2043430169,2043430169, 2043430169, 2043430169, 2043430169, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042, 2055708042, 2055708042,2055708042, 2055708042, 2055708042, 2055708042
]def sm3_ff_j(x, y, z, j):if 0 <= j and j < 16:ret = x ^ y ^ zelif 16 <= j and j < 64:ret = (x & y) | (x & z) | (y & z)return retdef sm3_gg_j(x, y, z, j):if 0 <= j and j < 16:ret = x ^ y ^ zelif 16 <= j and j < 64:#ret = (X | Y) & ((2 ** 32 - 1 - X) | Z)ret = (x & y) | ((~ x) & z)return retdef sm3_p_0(x):return x ^ (rotl(x, 9 % 32)) ^ (rotl(x, 17 % 32))def sm3_p_1(x):return x ^ (rotl(x, 15 % 32)) ^ (rotl(x, 23 % 32))def sm3_cf(v_i, b_i):w = []for i in range(16):weight = 0x1000000data = 0for k in range(i*4,(i+1)*4):data = data + b_i[k]*weightweight = int(weight/0x100)w.append(data)for j in range(16, 68):w.append(0)w[j] = sm3_p_1(w[j-16] ^ w[j-9] ^ (rotl(w[j-3], 15 % 32))) ^ (rotl(w[j-13], 7 % 32)) ^ w[j-6]str1 = "%08x" % w[j]w_1 = []for j in range(0, 64):w_1.append(0)w_1[j] = w[j] ^ w[j+4]str1 = "%08x" % w_1[j]a, b, c, d, e, f, g, h = v_ifor j in range(0, 64):ss_1 = rotl(((rotl(a, 12 % 32)) +e +(rotl(T_j[j], j % 32))) & 0xffffffff, 7 % 32)ss_2 = ss_1 ^ (rotl(a, 12 % 32))tt_1 = (sm3_ff_j(a, b, c, j) + d + ss_2 + w_1[j]) & 0xfffffffftt_2 = (sm3_gg_j(e, f, g, j) + h + ss_1 + w[j]) & 0xffffffffd = cc = rotl(b, 9 % 32)b = aa = tt_1h = gg = rotl(f, 19 % 32)f = ee = sm3_p_0(tt_2)a, b, c, d, e, f, g, h = map(lambda x:x & 0xFFFFFFFF ,[a, b, c, d, e, f, g, h])v_j = [a, b, c, d, e, f, g, h]return [v_j[i] ^ v_i[i] for i in range(8)]def sm3_hash(msg):# print(msg)len1 = len(msg)reserve1 = len1 % 64msg.append(0x80)reserve1 = reserve1 + 1# 56-64, add 64 byterange_end = 56if reserve1 > range_end:range_end = range_end + 64for i in range(reserve1, range_end):msg.append(0x00)bit_length = (len1) * 8bit_length_str = [bit_length % 0x100]for i in range(7):bit_length = int(bit_length / 0x100)bit_length_str.append(bit_length % 0x100)for i in range(8):msg.append(bit_length_str[7-i])group_count = round(len(msg) / 64)B = []for i in range(0, group_count):B.append(msg[i*64:(i+1)*64])V = []V.append(IV)for i in range(0, group_count):V.append(sm3_cf(V[i], B[i]))y = V[i+1]result = ""for i in y:result = '%s%08x' % (result, i)return resultdef sm3_kdf(z, klen): # z为16进制表示的比特串(str),klen为密钥长度(单位byte)klen = int(klen)ct = 0x00000001rcnt = ceil(klen/32)zin = [i for i in bytes.fromhex(z.decode('utf8'))]ha = ""for i in range(rcnt):msg = zin + [i for i in binascii.a2b_hex(('%08x' % ct).encode('utf8'))]ha = ha + sm3_hash(msg)ct += 1return ha[0: klen * 2]
sm4.py
# -*-coding:utf-8-*-
import copy
from .func import xor, rotl, get_uint32_be, put_uint32_be, \bytes_to_list, list_to_bytes, pkcs7_padding, pkcs7_unpadding, zero_padding, zero_unpadding# Expanded SM4 box table
SM4_BOXES_TABLE = [0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c,0x05, 0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86,0x06, 0x99, 0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed,0xcf, 0xac, 0x62, 0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa,0x75, 0x8f, 0x3f, 0xa6, 0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c,0x19, 0xe6, 0x85, 0x4f, 0xa8, 0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb,0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35, 0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25,0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87, 0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52,0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e, 0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38,0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1, 0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34,0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3, 0x1d, 0xf6, 0xe2, 0x2e, 0x82,0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f, 0xd5, 0xdb, 0x37, 0x45,0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51, 0x8d, 0x1b, 0xaf,0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8, 0x0a, 0xc1,0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0, 0x89,0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39,0x48,
]# System parameter
SM4_FK = [0xa3b1bac6, 0x56aa3350, 0x677d9197, 0xb27022dc]# fixed parameter
SM4_CK = [0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279
]SM4_ENCRYPT = 0
SM4_DECRYPT = 1PKCS7 = 0
ZERO = 1class CryptSM4(object):def __init__(self, mode=SM4_ENCRYPT, padding_mode=PKCS7):self.sk = [0] * 32self.mode = modeself.padding_mode = padding_mode# Calculating round encryption key.# args: [in] a: a is a 32 bits unsigned value;# return: sk[i]: i{0,1,2,3,...31}.@classmethoddef _round_key(cls, ka):b = [0, 0, 0, 0]a = put_uint32_be(ka)b[0] = SM4_BOXES_TABLE[a[0]]b[1] = SM4_BOXES_TABLE[a[1]]b[2] = SM4_BOXES_TABLE[a[2]]b[3] = SM4_BOXES_TABLE[a[3]]bb = get_uint32_be(b[0:4])rk = bb ^ (rotl(bb, 13)) ^ (rotl(bb, 23))return rk# Calculating and getting encryption/decryption contents.# args: [in] x0: original contents;# args: [in] x1: original contents;# args: [in] x2: original contents;# args: [in] x3: original contents;# args: [in] rk: encryption/decryption key;# return the contents of encryption/decryption contents.@classmethoddef _f(cls, x0, x1, x2, x3, rk):# "T algorithm" == "L algorithm" + "t algorithm".# args: [in] a: a is a 32 bits unsigned value;# return: c: c is calculated with line algorithm "L" and nonline# algorithm "t"def _sm4_l_t(ka):b = [0, 0, 0, 0]a = put_uint32_be(ka)b[0] = SM4_BOXES_TABLE[a[0]]b[1] = SM4_BOXES_TABLE[a[1]]b[2] = SM4_BOXES_TABLE[a[2]]b[3] = SM4_BOXES_TABLE[a[3]]bb = get_uint32_be(b[0:4])c = bb ^ (rotl(bb,2)) ^ (rotl(bb,10)) ^ (rotl(bb,18)) ^ (rotl(bb,24))return creturn (x0 ^ _sm4_l_t(x1 ^ x2 ^ x3 ^ rk))def set_key(self, key, mode):key = bytes_to_list(key)MK = [0, 0, 0, 0]k = [0] * 36MK[0] = get_uint32_be(key[0:4])MK[1] = get_uint32_be(key[4:8])MK[2] = get_uint32_be(key[8:12])MK[3] = get_uint32_be(key[12:16])k[0:4] = xor(MK[0:4], SM4_FK[0:4])for i in range(32):k[i + 4] = k[i] ^ (self._round_key(k[i + 1] ^ k[i + 2] ^ k[i + 3] ^ SM4_CK[i]))self.sk[i] = k[i + 4]self.mode = modeif mode == SM4_DECRYPT:for idx in range(16):t = self.sk[idx]self.sk[idx] = self.sk[31 - idx]self.sk[31 - idx] = tdef one_round(self, sk, in_put):out_put = []ulbuf = [0] * 36ulbuf[0] = get_uint32_be(in_put[0:4])ulbuf[1] = get_uint32_be(in_put[4:8])ulbuf[2] = get_uint32_be(in_put[8:12])ulbuf[3] = get_uint32_be(in_put[12:16])for idx in range(32):ulbuf[idx + 4] = self._f(ulbuf[idx],ulbuf[idx + 1],ulbuf[idx + 2],ulbuf[idx + 3],sk[idx])out_put += put_uint32_be(ulbuf[35])out_put += put_uint32_be(ulbuf[34])out_put += put_uint32_be(ulbuf[33])out_put += put_uint32_be(ulbuf[32])return out_putdef crypt_ecb(self, input_data):# SM4-ECB block encryption/decryptioninput_data = bytes_to_list(input_data)if self.mode == SM4_ENCRYPT:if self.padding_mode == PKCS7:input_data = pkcs7_padding(input_data)elif self.padding_mode == ZERO:input_data = zero_padding(input_data)length = len(input_data)i = 0output_data = []while length > 0:output_data += self.one_round(self.sk, input_data[i:i + 16])i += 16length -= 16if self.mode == SM4_DECRYPT:if self.padding_mode == PKCS7:return list_to_bytes(pkcs7_unpadding(output_data))elif self.padding_mode == ZERO:return list_to_bytes(zero_unpadding(output_data))return list_to_bytes(output_data)def crypt_cbc(self, iv, input_data):# SM4-CBC buffer encryption/decryptioni = 0output_data = []tmp_input = [0] * 16iv = bytes_to_list(iv)if self.mode == SM4_ENCRYPT:input_data = pkcs7_padding(bytes_to_list(input_data))length = len(input_data)while length > 0:tmp_input[0:16] = xor(input_data[i:i + 16], iv[0:16])output_data += self.one_round(self.sk, tmp_input[0:16])iv = copy.deepcopy(output_data[i:i + 16])i += 16length -= 16return list_to_bytes(output_data)else:length = len(input_data)while length > 0:output_data += self.one_round(self.sk, input_data[i:i + 16])output_data[i:i + 16] = xor(output_data[i:i + 16], iv[0:16])iv = copy.deepcopy(input_data[i:i + 16])i += 16length -= 16return list_to_bytes(pkcs7_unpadding(output_data))
详细代码详见提交在青青草原七匹狼/电子公文传输系统Gitee库或压缩包里的代码
密钥管理:所有私钥,对称算法密钥等不能明存
系统量化评估
按照商用密码应用安全性评估量化评估规则,计算自己系统的得分,只计算应用和数据安全
1.根据《商用密码应用安全性评估量化评估规则》文件,我们需要关注的测评指标包括:
- 身份鉴别
- 通信数据完整性
- 通信过程中重要数据的机密性
- 数据存储和传输的安全性
测评对象包括:
- 用户账号管理
- 公文的起草、审核、发送和查询
- 系统日志和审计
2. 评估密码使用安全(D)
用户账号管理:
- 系统实现了身份认证,对口令做了加盐的SHA-256哈希保护。这符合密码使用安全的要求。
公文传输:
- 系统使用非对称密钥(SM2)保护对称密钥,再用对称密钥实现对文件的加解密。这符合密码使用安全的要求。
3. 评估密码算法/技术合规性(A)
密码算法使用:
- 系统使用了SM2算法进行加密,符合商用密码应用安全性评估量化评估规则中对算法的要求。
4. 评估密钥管理安全(K)
密钥管理:
- 私钥保护:系统使用SM2算法,这是一种合规的国产密码算法,用于保护对称密钥,符合密钥管理安全要求。
- 对称密钥管理:系统使用加盐的SHA-256哈希存储密码,符合安全要求。
5. 计算测评对象评分(Si,j,k)
根据上述评估,每个测评对象的得分如下:
- 用户账号管理:1(完全符合)
- 公文传输:1(完全符合)
- 系统日志和审计:0.5(部分符合,因为虽然有日志记录,但未明确说明密钥管理细节)
6. 计算测评单元得分(Si,j)
测评单元得分为测评对象得分的算术平均值:
7. 计算安全层面得分(Si)
应用和数据安全层面的权重为30(根据文件《商用密码应用安全性评估量化评估规则》中的权重设置),则:
8. 整体得分计算
根据文件《商用密码应用安全性评估量化评估规则》中的权重和得分计算方法,整体得分为:
计算结果为:
9. 结合高风险判定
根据《商用密码应用安全性评估量化评估规则》,如果系统存在高风险问题,即使得分较高,也需要结合高风险判定来确定最终的安全评估结果。系统实现了多种安全措施,包括防御XSS、CSRF、SQL注入等,没有明确指出存在高风险问题。
综上所述,电子公文传输系统在应用与数据安全层面的商用密码应用安全性评估量化评估得分较高,表明系统在密码应用安全性方面表现良好。