20221320馮泰瑞-實(shí)驗(yàn)四密碼模塊應(yīng)用實(shí)踐過程記錄
20221320馮泰瑞-實(shí)驗(yàn)四密碼模塊應(yīng)用實(shí)踐過程記錄
實(shí)踐要求
- 完成電子公文交換系統(tǒng),系統(tǒng)功能,(15 分)

- 總體要求
- 項(xiàng)目類型必須是B/S或C/S架構(gòu)
- 項(xiàng)目程序設(shè)計(jì)語(yǔ)言可以是C,Python,Rust等
- 三員制度是指將系統(tǒng)管理員、安全保密管理員和安全審計(jì)員三個(gè)角色分離,分別負(fù)責(zé)系統(tǒng)運(yùn)行、安全保密和安全管理,相互制約,共同保障信息系統(tǒng)安全。三員職責(zé)
- 系統(tǒng)管理員
- 負(fù)責(zé)信息系統(tǒng)的日常維護(hù)、故障處理和升級(jí)更新。
- 確保系統(tǒng)正常運(yùn)行,對(duì)系統(tǒng)資源進(jìn)行合理分配。
- 負(fù)責(zé)用戶賬號(hào)的創(chuàng)建、修改和刪除。
- 定期備份重要數(shù)據(jù),確保數(shù)據(jù)安全。
- 安全保密管理員
- 負(fù)責(zé)制定和實(shí)施安全保密策略,確保信息系統(tǒng)安全。
- 對(duì)用戶進(jìn)行安全意識(shí)培訓(xùn),提高用戶安全防范能力。
- 監(jiān)控網(wǎng)絡(luò)安全狀況,發(fā)現(xiàn)異常情況及時(shí)處理。
- 負(fù)責(zé)信息系統(tǒng)安全事件的應(yīng)急響應(yīng)和處理。
- 安全審計(jì)員
- 負(fù)責(zé)對(duì)信息系統(tǒng)進(jìn)行安全審計(jì),評(píng)估安全風(fēng)險(xiǎn)。
- 監(jiān)督系統(tǒng)管理員和安全保密管理員的工作,確保其履行職責(zé)。
- 對(duì)信息系統(tǒng)安全事件進(jìn)行調(diào)查,提出整改建議。
- 黃金法則(5 分)
- 身份鑒別:口令不能存,數(shù)據(jù)庫(kù)要保存加鹽的SM3Hash值
- 訪問控制:操作員,審核員,安全三員的權(quán)限設(shè)置
- 安全審計(jì):至少完成日志查詢功能
- 密碼(15 分)
- 算法:SM2,SM3,SM4,推薦使用 Key
- 密鑰管理:所有私鑰,對(duì)稱算法密鑰等不能明存
- 系統(tǒng)量化評(píng)估(5分)
- 按照商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則,計(jì)算自己系統(tǒng)的得分,只計(jì)算應(yīng)用和數(shù)據(jù)安全。
- 提交要求:
-
提交實(shí)踐過程Markdown和轉(zhuǎn)化的PDF文件
-
代碼,文檔托管到gitee或github等,推薦 gitclone
-
記錄實(shí)驗(yàn)過程中遇到的問題,解決過程,反思等內(nèi)容,用于后面實(shí)驗(yàn)報(bào)告
開源倉(cāng)庫(kù)
我使用了前輩之前的倉(cāng)庫(kù),倉(cāng)庫(kù)地址為青青草原七匹狼/電子公文傳輸系統(tǒng),使用git clone命令下載即可。
環(huán)境配置
系統(tǒng)部分環(huán)境需要進(jìn)行修改,配置過程如下所示:
個(gè)人配置
Windows 11
Python: 3.13
參考博客:(超詳細(xì))Python+PyCharm的安裝步驟及PyCharm的使用(含快捷鍵)
Pycharm: Community Edition 2024.3
參考博客:(超詳細(xì))Python+PyCharm的安裝步驟及PyCharm的使用(含快捷鍵)
OpenSSL:3.4.0 22 Oct ~2024
參考博客:環(huán)境篇-Windows下安裝OpenSSL
MySQL:9.0
參考博客:Windows環(huán)境下MySQL安裝與配置(超詳細(xì)、超細(xì)致)
環(huán)境配置
在D盤(推薦)使用git clone https://gitee.com/Electronic-document-transfer-system/Document-transmission.git命令獲取源代碼,使用Pycharm打開項(xiàng)目,項(xiàng)目結(jié)構(gòu)如下所示。

在左上角中,根據(jù)文件->設(shè)置->Project:Document-transmission->Python Interpreter的路徑打開解釋器,點(diǎn)擊加號(hào)以安裝其他包。

在里面搜索如下包進(jìn)行安裝:Django、PyPDF2、captcha、django-sslserver、filetype、gmssl、mysqlclient、standard-imghdr 、django-simple-captcha包。
接著,需要我們創(chuàng)建名為testdocument的數(shù)據(jù)庫(kù)。
在命令行中使用如下命令進(jìn)行數(shù)據(jù)表的創(chuàng)建。
python manage.py makemigrations
python manage.py migrate
顯示如下提示代表創(chuàng)建成功。
(.venv) PS D:\信息安全系統(tǒng)設(shè)計(jì)\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表里面有一個(gè)外鍵,不知道有什么用,但是帶著這個(gè)外鍵又會(huì)報(bào)錯(cuò)。我們?cè)?code>SQL命令行把這個(gè)外鍵刪了,使用如下命令:
use testdocument;
alter table index_document drop foreign key index_document_label_id_b5ce761f_fk_index_label_id;
之后的報(bào)錯(cuò)記錄就又少了一條。緊接著,在electronicDocument/settings.py中修改數(shù)據(jù)庫(kù)登錄的密碼,第91行中有如下代碼:
'PASSWORD': '1619553792',
把這個(gè)1619553792修改成數(shù)據(jù)庫(kù)的密碼123456即可。如果登錄賬戶不是root,在上一行中的'USER': 'root'進(jìn)行類似的修改即可。
代碼修改
首先是SSL的問題,如果直接運(yùn)行項(xiàng)目,可能會(huì)報(bào)這樣的錯(cuò):
AttributeError: module 'ssl' has no attribute 'wrap_socket'
這是因?yàn)榘陌姹靖铝税?,沒有這個(gè)用法了,把對(duì)應(yīng)文件修改一下就可以。
打開項(xiàng)目中的runsslserver.py文件,第48行有一個(gè)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行有一個(gè)uploadView函數(shù),這個(gè)函數(shù)需要大改一下。
我們可以看到里面有很多D:/electronicDocument/meida/...這樣的路徑。這也是我推薦把項(xiàng)目克隆到D盤的原因,這個(gè)項(xiàng)目里有很多的文件路徑都是絕對(duì)路徑。
我們的路徑里面沒有electronicDocument。如果直接把項(xiàng)目克隆到了D盤,直接把它替換成Document-transmission-master即可。不在D盤的情況下把替換為對(duì)應(yīng)路徑。其他文件中也會(huì)出現(xiàn)類似的問題,如果在運(yùn)行項(xiàng)目時(shí),出現(xiàn)FileNotFound的問題,通常就是因?yàn)檫@個(gè)沒有修改,運(yùn)行過程中出現(xiàn)報(bào)錯(cuò)再修改也可以。
還有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))
遺憾的是一些方法已經(jīng)使用不了了,將其修改為如下代碼:
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])
這樣上傳公文的功能就可以使用了。
修改到目前,這種狀況只能查看文件名為純英文的文件,不能上傳為文件名含有其他語(yǔ)言字符的文件,這是由于查看公文時(shí)傳到后臺(tái)的文件名中的其他語(yǔ)言字符進(jìn)行了URL編碼,但是查找文件的沒有將編碼轉(zhuǎn)化為對(duì)應(yīng)字符。
在play/views.py中,開頭第一行寫入from urllib.parse import unquote進(jìn)行包的導(dǎo)入。在第10行左右有一個(gè)playview函數(shù),下滑到65行左右有一行代碼file = documents.file.url[1::],在這行的下一行添加如下代碼:
file = unquote(file)
再下滑到最底部,有一個(gè)downloadr函數(shù)。同理,在115行左右的file = document.file.url[1::]下一行中添加如下代碼:
file = unquote(file)
這樣就能進(jìn)行相關(guān)的文件查看和下載了。
到此,代碼幾乎修改完成。
項(xiàng)目運(yùn)行
項(xiàng)目需要我們找到development.crt和development.key兩個(gè)文件,應(yīng)該在Python中Lib\site-package\sslserver\certs文件夾里面,我的路徑是D:\信息安全系 ocument-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crt和D:\信息安全系統(tǒng)設(shè)計(jì)\Document-transmission-master\.venv\Libckages\sslserver\certs\development.key 。
在終端中使用如下命令啟動(dòng)服務(wù):
(.venv) PS D:\信息安全系統(tǒng)設(shè)計(jì)\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:\信息安全系統(tǒng)設(shè)計(jì)\Document-transmission-master\.venv\Libckages\sslserver\certs\development.key
--certificate用于指定證書,--key用于指定私鑰。
之后可能會(huì)有一些報(bào)錯(cuò),應(yīng)該是因?yàn)槿笔承┌凑丈鲜霭惭b包的方式一起安裝就可以了;也可能是因?yàn)槎丝谝呀?jīng)被占用,這時(shí)候需要?dú)⑺蓝丝谒谶M(jìn)程。
在這之后,如果出現(xiàn)如下提升,便證明服務(wù)啟動(dòng)成功:
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:\信息安全系統(tǒng)設(shè)計(jì)\Document-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.crt
Using SSL key: D:\信息安全系統(tǒng)設(shè)計(jì)\Document-transmission-master\.venv\Lib\site-packages\sslserver\certs\development.key
Quit the server with CTRL-BREAK.
此時(shí),你的服務(wù)已經(jīng)啟動(dòng)成功,可以在https://127.0.0.1:8000/進(jìn)行相關(guān)操作了。
完成電子公文交換系統(tǒng),系統(tǒng)功能展示
操作員界面
注冊(cè)界面

登陸界面

首頁(yè)

用戶中心

發(fā)文
發(fā)送公文
從本地上傳公文(公開)

成功上傳

從本地上傳公文(私密)

成功上傳

查看公文(附有SM3哈希值后16位,可用于文件完整性檢驗(yàn))
公開的公文


私密的公文


審核公文(公文批復(fù))

查詢公文

收文
公文簽收(下載公文)


查看公文(附有哈希值,可用于文件完整性檢驗(yàn))


查詢公文

后臺(tái)管理員界面(系統(tǒng)管理員、用戶管理員)
修改數(shù)據(jù)庫(kù)內(nèi)is_superuser is_staff is_secert的值從0變?yōu)?,使得我能順利地在站點(diǎn)管理頁(yè)面成功登陸

登錄界面

首頁(yè)

用戶管理員
增、刪、改用戶
需要錄入用戶名、密碼等信息

權(quán)限設(shè)置(用戶認(rèn)證授權(quán))[是否為超級(jí)用戶(審計(jì)員、安全審計(jì)員、安全保密管理員、系統(tǒng)管理員、操作員)]

系統(tǒng)管理員
系統(tǒng)日志(查看公文信息)
可以查看公文標(biāo)題、上傳者、發(fā)文機(jī)關(guān)、上傳時(shí)間、公文縮略圖

組織單位(根據(jù)發(fā)文單位篩選公文)
以中辦電科院為發(fā)文機(jī)關(guān)的公文

以北京電子科技學(xué)院為發(fā)文機(jī)關(guān)的公文

黃金法則
身份鑒別:口令不能明存,數(shù)據(jù)庫(kù)要保存加鹽的SM3Hash值
file = documents.file.url[1::]
file = unquote(file)
path_doc = os.path.join("D:/信息安全系統(tǒng)設(shè)計(jì)/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() # 讀取命令行的輸出到一個(gè)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())

訪問控制:操作員,審核員,安全三員的權(quán)限設(shè)置
詳見前面系統(tǒng)展示部分
安全審計(jì):至少完成日志查詢功能
可以查看公文標(biāo)題、上傳者、發(fā)文機(jī)關(guān)、上傳時(shí)間、公文縮略圖

密碼
算法: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
# 選擇素域,設(shè)置橢圓曲線參數(shù)
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運(yùn)算
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): # 倍點(diǎn)
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): # 點(diǎn)加函數(shù),P2點(diǎn)為仿射坐標(biāo)即z=1,P1為Jacobian加重射影坐標(biāo)
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加重射影坐標(biāo)轉(zhuǎn)換成仿射坐標(biāo)
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):
# 驗(yàn)簽函數(shù),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):
"""
簽名函數(shù), data消息的hash,private_key私鑰,K隨機(jī)數(shù),均為16進(jìn)制字符串
:param self:
:param data: data消息的hash
:param K: K隨機(jī)數(shù)
:return:
"""
E = data.hex() # 消息轉(zhuǎn)化為16進(jìn)制字符串
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):
# 加密函數(shù),data消息(bytes)
msg = data.hex() # 消息轉(zhuǎn)化為16進(jìn)制字符串
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):
# 解密函數(shù),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 簽名規(guī)則: 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進(jìn)制
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進(jìn)制表示的比特串(str),klen為密鑰長(zhǎng)度(單位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))
詳細(xì)代碼詳見提交在青青草原七匹狼/電子公文傳輸系統(tǒng)Gitee庫(kù)或壓縮包里的代碼
密鑰管理:所有私鑰,對(duì)稱算法密鑰等不能明存
系統(tǒng)量化評(píng)估
按照商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則,計(jì)算自己系統(tǒng)的得分,只計(jì)算應(yīng)用和數(shù)據(jù)安全
1.根據(jù)《商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則》文件,我們需要關(guān)注的測(cè)評(píng)指標(biāo)包括:
- 身份鑒別
- 通信數(shù)據(jù)完整性
- 通信過程中重要數(shù)據(jù)的機(jī)密性
- 數(shù)據(jù)存儲(chǔ)和傳輸?shù)陌踩?/li>
測(cè)評(píng)對(duì)象包括:
- 用戶賬號(hào)管理
- 公文的起草、審核、發(fā)送和查詢
- 系統(tǒng)日志和審計(jì)
2. 評(píng)估密碼使用安全(D)
用戶賬號(hào)管理:
- 系統(tǒng)實(shí)現(xiàn)了身份認(rèn)證,對(duì)口令做了加鹽的SHA-256哈希保護(hù)。這符合密碼使用安全的要求。
公文傳輸:
- 系統(tǒng)使用非對(duì)稱密鑰(SM2)保護(hù)對(duì)稱密鑰,再用對(duì)稱密鑰實(shí)現(xiàn)對(duì)文件的加解密。這符合密碼使用安全的要求。
3. 評(píng)估密碼算法/技術(shù)合規(guī)性(A)
密碼算法使用:
- 系統(tǒng)使用了SM2算法進(jìn)行加密,符合商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則中對(duì)算法的要求。
4. 評(píng)估密鑰管理安全(K)
密鑰管理:
- 私鑰保護(hù):系統(tǒng)使用SM2算法,這是一種合規(guī)的國(guó)產(chǎn)密碼算法,用于保護(hù)對(duì)稱密鑰,符合密鑰管理安全要求。
- 對(duì)稱密鑰管理:系統(tǒng)使用加鹽的SHA-256哈希存儲(chǔ)密碼,符合安全要求。
5. 計(jì)算測(cè)評(píng)對(duì)象評(píng)分(Si,j,k)
根據(jù)上述評(píng)估,每個(gè)測(cè)評(píng)對(duì)象的得分如下:
- 用戶賬號(hào)管理:1(完全符合)
- 公文傳輸:1(完全符合)
- 系統(tǒng)日志和審計(jì):0.5(部分符合,因?yàn)殡m然有日志記錄,但未明確說明密鑰管理細(xì)節(jié))
6. 計(jì)算測(cè)評(píng)單元得分(Si,j)
測(cè)評(píng)單元得分為測(cè)評(píng)對(duì)象得分的算術(shù)平均值:

7. 計(jì)算安全層面得分(Si)
應(yīng)用和數(shù)據(jù)安全層面的權(quán)重為30(根據(jù)文件《商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則》中的權(quán)重設(shè)置),則:

8. 整體得分計(jì)算
根據(jù)文件《商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則》中的權(quán)重和得分計(jì)算方法,整體得分為:

計(jì)算結(jié)果為:

9. 結(jié)合高風(fēng)險(xiǎn)判定
根據(jù)《商用密碼應(yīng)用安全性評(píng)估量化評(píng)估規(guī)則》,如果系統(tǒng)存在高風(fēng)險(xiǎn)問題,即使得分較高,也需要結(jié)合高風(fēng)險(xiǎn)判定來確定最終的安全評(píng)估結(jié)果。系統(tǒng)實(shí)現(xiàn)了多種安全措施,包括防御XSS、CSRF、SQL注入等,沒有明確指出存在高風(fēng)險(xiǎn)問題。
綜上所述,電子公文傳輸系統(tǒng)在應(yīng)用與數(shù)據(jù)安全層面的商用密碼應(yīng)用安全性評(píng)估量化評(píng)估得分較高,表明系統(tǒng)在密碼應(yīng)用安全性方面表現(xiàn)良好。