20221320冯泰瑞-实验四密码模块应用实践过程记录

20221320冯泰瑞-实验四密码模块应用实践过程记录

实践要求

  1. 完成电子公文交换系统,系统功能,(15 分)
电子公文系统发文公文起草公文查看发文审核(审核员)公文发送公文查询收文公文签收公文查看公文处理公文查询系统管理组织单位用户管理操作员(科员)审核员(科长,处长):至少有一级审核功能系统管理员:至少管理用户账号,组织单位功安全保密管理员:至少有权限管理功能,密钥管理功能安全审计员:至少有日志查看功能权限设置(安全保密管理员)系统日志数据字典(选做)

系统功能

  • 总体要求
    • 项目类型必须是B/S或C/S架构
    • 项目程序设计语言可以是C,Python,Rust等
  1. 三员制度是指将系统管理员、安全保密管理员和安全审计员三个角色分离,分别负责系统运行、安全保密和安全管理,相互制约,共同保障信息系统安全。三员职责
  • 系统管理员
    • 负责信息系统的日常维护、故障处理和升级更新。
    • 确保系统正常运行,对系统资源进行合理分配。
    • 负责用户账号的创建、修改和删除。
    • 定期备份重要数据,确保数据安全。
  • 安全保密管理员
    • 负责制定和实施安全保密策略,确保信息系统安全。
    • 对用户进行安全意识培训,提高用户安全防范能力。
    • 监控网络安全状况,发现异常情况及时处理。
    • 负责信息系统安全事件的应急响应和处理。
  • 安全审计员
    • 负责对信息系统进行安全审计,评估安全风险。
    • 监督系统管理员和安全保密管理员的工作,确保其履行职责。
    • 对信息系统安全事件进行调查,提出整改建议。
  1. 黄金法则(5 分)
    • 身份鉴别:口令不能存,数据库要保存加盐的SM3Hash值
    • 访问控制:操作员,审核员,安全三员的权限设置
    • 安全审计:至少完成日志查询功能
  2. 密码(15 分)
    • 算法:SM2,SM3,SM4,推荐使用 Key
    • 密钥管理:所有私钥,对称算法密钥等不能明存
  3. 系统量化评估(5分)
  4. 提交要求:
  • 提交实践过程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的路径打开解释器,点击加号以安装其他包。

在里面搜索如下包进行安装:DjangoPyPDF2captchadjango-sslserverfiletypegmsslmysqlclientstandard-imghdrdjango-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... OK
  Applying contenttypes.0002_remove_content_type_name... OK
  Applying auth.0001_initial... OK
  Applying auth.0002_alter_permission_name_max_length... OK
  Applying auth.0003_alter_user_email_max_length... OK
  Applying auth.0004_alter_user_username_opts... OK
  Applying auth.0005_alter_user_last_login_null... OK
  Applying auth.0006_require_contenttypes_0002... OK
  Applying auth.0007_alter_validators_add_error_messages... OK
  Applying auth.0008_alter_user_username_max_length... OK
  Applying auth.0009_alter_user_last_name_max_length... OK
  Applying auth.0010_alter_group_name_max_length... OK
  Applying auth.0011_update_proxy_permissions... OK
  Applying user.0001_initial... OK
  Applying admin.0001_initial... OK
  Applying admin.0002_logentry_remove_auto_add... OK
  Applying admin.0003_logentry_add_action_flag_choices... OK
  Applying auth.0012_alter_user_first_name_max_length... OK
  Applying captcha.0001_initial... OK
  Applying captcha.0002_alter_captchastore_id... OK
  Applying index.0001_initial... OK
  Applying index.0002_auto_20210601_1010... OK
  Applying index.0003_document_type... OK
  Applying index.0004_document_lyrics... OK
  Applying index.0005_auto_20210602_0952... OK
  Applying index.0006_auto_20210602_0953... OK
  Applying index.0007_document_key... OK
  Applying index.0008_auto_20210605_1920... OK
  Applying sessions.0001_initial... OK
  Applying user.0002_auto_20210605_1743... OK
  Applying 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.crtdevelopment.key两个文件,应该在Python中Lib\site-package\sslserver\certs文件夹里面,我的路径是D:\信息安全系 ocument-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crtD:\信息安全系统设计\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_doc
        bash_r = os.popen(sm3_hash)
        info = bash_r.readlines()  # 读取命令行的输出到一个list

        for 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_key
        self.public_key = public_key.lstrip("04") if public_key.startswith("04") else public_key
        self.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_table
        assert mode in (0, 1), 'mode must be one of (0, 1)'
        self.mode = mode
        self.asn1 = asn1

    def _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 = Point
        flag = False
        for 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 = True
                    Temp = Point
            k = k << 1
        return self._convert_jacb_to_nor(Temp)

    def _double_point(self, Point):  # 倍点
        l = len(Point)
        len_2 = 2 * self.para_len
        if l < self.para_len * 2:
            return None
        else:
            x1 = int(Point[0:self.para_len], 16)
            y1 = int(Point[self.para_len:len_2], 16)
            if l == len_2:
                z1 = 1
            else:
                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_len
            form = form * 3
            return form % (x3, y3, z3)

    def _add_point(self, P1, P2):  # 点加函数,P2点为仿射坐标即z=1,P1为Jacobian加重射影坐标
        len_2 = 2 * self.para_len
        l1 = len(P1)
        l2 = len(P2)
        if (l1 < len_2) or (l2 < len_2):
            return None
        else:
            X1 = int(P1[0:self.para_len], 16)
            Y1 = int(P1[self.para_len:len_2], 16)
            if (l1 == len_2):
                Z1 = 1
            else:
                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_len
            form = form * 3
            return form % (X3, Y3, Z3)

    def _convert_jacb_to_nor(self, Point):  # Jacobian加重射影坐标转换成仿射坐标
        len_2 = 2 * self.para_len
        x = 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_len
            form = form * 2
            return form % (x_new, y_new)
        else:
            return None

    def 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 0

        P1 = 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 None
        d_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 None
        elif 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 None
        else:
            form = '%%0%dx' % ml
            C2 = 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_len
        len_3 = len_2 + 64
        C1 = 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 None
        else:
            form = '%%0%dx' % cl
            M = 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_key
        z = 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 e

    def 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 sign

    def 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_list

IV = [
    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 ^ z
    elif 16 <= j and j < 64:
        ret = (x & y) | (x & z) | (y & z)
    return ret

def sm3_gg_j(x, y, z, j):
    if 0 <= j and j < 16:
        ret = x ^ y ^ z
    elif 16 <= j and j < 64:
        #ret = (X | Y) & ((2 ** 32 - 1 - X) | Z)
        ret = (x & y) | ((~ x) & z)
    return ret

def 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 = 0x1000000
        data = 0
        for k in range(i*4,(i+1)*4):
            data = data + b_i[k]*weight
            weight = 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_i

    for 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]) & 0xffffffff
        tt_2 = (sm3_gg_j(e, f, g, j) + h + ss_1 + w[j]) & 0xffffffff
        d = c
        c = rotl(b, 9 % 32)
        b = a
        a = tt_1
        h = g
        g = rotl(f, 19 % 32)
        f = e
        e = 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 % 64
    msg.append(0x80)
    reserve1 = reserve1 + 1
    # 56-64, add 64 byte
    range_end = 56
    if reserve1 > range_end:
        range_end = range_end + 64

    for i in range(reserve1, range_end):
        msg.append(0x00)

    bit_length = (len1) * 8
    bit_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 result

def sm3_kdf(z, klen): # z为16进制表示的比特串(str),klen为密钥长度(单位byte)
    klen = int(klen)
    ct = 0x00000001
    rcnt = 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 += 1
    return 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 = 1

PKCS7 = 0
ZERO = 1


class CryptSM4(object):

    def __init__(self, mode=SM4_ENCRYPT, padding_mode=PKCS7):
        self.sk = [0] * 32
        self.mode = mode
        self.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}.

    @classmethod
    def _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.
    @classmethod
    def _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 c
        return (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] * 36
        MK[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 = mode
        if mode == SM4_DECRYPT:
            for idx in range(16):
                t = self.sk[idx]
                self.sk[idx] = self.sk[31 - idx]
                self.sk[31 - idx] = t

    def one_round(self, sk, in_put):
        out_put = []
        ulbuf = [0] * 36
        ulbuf[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_put

    def crypt_ecb(self, input_data):
        # SM4-ECB block encryption/decryption
        input_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 = 0
        output_data = []
        while length > 0:
            output_data += self.one_round(self.sk, input_data[i:i + 16])
            i += 16
            length -= 16
        if 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/decryption
        i = 0
        output_data = []
        tmp_input = [0] * 16
        iv = 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 += 16
                length -= 16
            return 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 += 16
                length -= 16
            return 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)

测评单元得分为测评对象得分的算术平均值:
image

7. 计算安全层面得分(Si)

应用和数据安全层面的权重为30(根据文件《商用密码应用安全性评估量化评估规则》中的权重设置),则:
image

8. 整体得分计算

根据文件《商用密码应用安全性评估量化评估规则》中的权重和得分计算方法,整体得分为:
image

计算结果为:

image

9. 结合高风险判定

根据《商用密码应用安全性评估量化评估规则》,如果系统存在高风险问题,即使得分较高,也需要结合高风险判定来确定最终的安全评估结果。系统实现了多种安全措施,包括防御XSS、CSRF、SQL注入等,没有明确指出存在高风险问题。

综上所述,电子公文传输系统在应用与数据安全层面的商用密码应用安全性评估量化评估得分较高,表明系统在密码应用安全性方面表现良好。

posted @   20221320冯泰瑞  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 为DeepSeek添加本地知识库
· 精选4款基于.NET开源、功能强大的通讯调试工具
· DeepSeek智能编程
· 大模型工具KTransformer的安装
· [计算机/硬件/GPU] 显卡
点击右上角即可分享
微信分享提示