基于铜锁构建Web在线加密工具库
搭建运行环境
实验⼿册中的实验都是以 docker
和 docker-compose
环境为主,基于 Ubuntu 20.04
容器镜像。
初始化项目
首先利用 IDE 创建一个 tongsuo_web
的空项目,接下来我们所有的文件都会创建在该项目中,我们首先构建运行环境,整个环境都是由 Dockerfile
进行构建,然后使用 docker-compose.yml
进行编排,使得宿主机不用进行环境依赖安装,实验完也可以快速删除环境,不对宿主机造成过多影响。
创建sources.list
因为 ubuntu
的默认 apt
源是比较慢的,对于我们实验会造成过慢,所以这里提前准备了 apt
源,在项目里新建 sources.list
文件,并将如下内容写入到文件中,下文就会使用到该配置
deb http://repo.huaweicloud.com/ubuntu/ focal main restricted universe multiverse deb-src http://repo.huaweicloud.com/ubuntu/ focal main restricted universe multiverse deb http://repo.huaweicloud.com/ubuntu/ focal-security main restricted universe multiverse #deb-src http://repo.huaweicloud.com/ubuntu/ focal-security main restricted universe multiverse deb http://repo.huaweicloud.com/ubuntu/ focal-updates main restricted universe multiverse #deb-src http://repo.huaweicloud.com/ubuntu/ focal-updates main restricted universe multiverse deb http://repo.huaweicloud.com/ubuntu/ focal-proposed main restricted universe multiverse #deb-src http://repo.huaweicloud.com/ubuntu/ focal-proposed main restricted universe multiverse deb http://repo.huaweicloud.com/ubuntu/ focal-backports main restricted universe multiverse #deb-src http://repo.huaweicloud.com/ubuntu/ focal-backports main restricted universe multiverse
创建Dockerfile
在项目中首先创建 Dockerfile
文件,并把以下内容写入到文件中
FROM ubuntu:20.04 WORKDIR "/application" ADD sources.list /etc/apt/ RUN apt-get update \ && apt-get -y install git make gcc python3.7 python3-pip \ && git config --global http.sslverify false \ && python3 --version \ && pip3 --version RUN git clone https://atomgit.com/tongsuo/Tongsuo.git \ && cd Tongsuo \ && ./config --prefix=/opt/tongsuo enable-ntls enable-ssl-trace -Wl,-rpath,/opt/tongsuo/lib64 --debug \ && make -j \ && make install \ && /opt/tongsuo/bin/tongsuo version
这是一个基于 ubuntu:20.04
的环境,他在构建的过程中替换了 apt
的源,并且将 gcc
、make
、python
、git
、pip
等环境所需工具进行了安装;通过 git
将铜锁的仓库克隆到容器中并进行编译,使得环境中拥有了铜锁。
通过docker-compose进行dockerfile构建
在项目中新建 docker-compose.yml
文件然后指定当前目录下的 Dockerfile
version: '3.1' services: tongsuo: build: context: . working_dir: /application volumes: - '.:/application' ports: - "5000:5000" tty: true stdin_open: true
最终项目文件如下所示:
- 创建环境
docker-compose up -d
出现下图输出即环境创建成功:
因为我们需要使用到 Python
的 Flask
作为 Web
框架,所以我们需要先进容器进行安装,命令如下
docker-compose exec -it tongsuo bash pip3 install flask
安装完成后我们就可以来创建 Web
Api
了
基于铜锁构建API
对铜锁SDK能力进行封装
新建一个名为 tongsuo
的 package
创建一个 tools.py
,我们将铜锁SDK提供的 sm2
、sm3
、sm4
能力进行封装,相关函数如下:
import subprocess # SM4-CBC加密 def sm4_cbc_encrypt(plaintext: str, key: str): command = 'echo ' + plaintext + ' | /opt/tongsuo/bin/tongsuo enc -K ' + key + ' -e -sm4-cbc ' \ '-iv 1fb2d42fb36e2e88a220b04f2e49aa13 -nosalt -base64', return shell(command) # SM4-CBC解密 def sm4_cbc_decrypt(ciphertext: str, key: str): command = 'echo ' + ciphertext + '| /opt/tongsuo/bin/tongsuo enc -K '+key+' -d -sm4-cbc ' \ '-iv 1fb2d42fb36e2e88a220b04f2e49aa13 -nosalt -base64' return shell(command) # sm3杂凑算法 def sm3_dgst(ciphertext: str): command = 'echo -n ' + ciphertext + ' | /opt/tongsuo/bin/tongsuo dgst -sm3' return shell(command) # sm2签名验证 def sm2_verify(sig_file: str, file_path: str): command = "/opt/tongsuo/bin/tongsuo dgst -sm3 -verify ./sm2pub.key -signature {} {}".format(sig_file, file_path) return shell(command) # sm2 签名 def sm2_sign(file_path: str, sig_file: str): command = "/opt/tongsuo/bin/tongsuo dgst -sm3 -sign ./sm2.key -out {} {}".format(sig_file, file_path) return shell(command) def shell(command): process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) process.wait() # 获取命令的输出和错误信息 output = process.stdout.read() error = process.stderr.read() # 将输出和错误信息解码为字符串 output = output.decode(encoding="utf8") error = error.decode(encoding="utf8") # 返回命令的输出和错误信息 result = {"output": output, "error": error} print(result) return result
在 main
文件中将封装好的函数能力通过 api
进行暴露
import random from functools import wraps from flask import Flask, send_file from flask import render_template from flask import request from tongsuo.tools import sm3_dgst, sm4_cbc_encrypt, sm4_cbc_decrypt, sm2_verify, sm2_sign app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') def check_param(param_name): """ 装饰器,用于检查请求参数是否存在 """ def decorator(func): @wraps(func) def wrapper(*args, **kwargs): param_value = request.form.get(param_name) if param_value is None: return f"Error: '{param_name}' parameter is missing", 400 else: return func(param_value, *args, **kwargs) return wrapper return decorator @app.route('/sm4_cbc_encrypt', methods=['POST']) @check_param('msg') @check_param('key') def sm4_cbc_encrypt_endpoint(key, msg): return sm4_cbc_encrypt(msg, key) @app.route('/sm4_cbc_decrypt', methods=['POST']) @check_param('msg') @check_param('key') def sm4_cbc_decrypt_endpoint(key, msg): return sm4_cbc_decrypt(msg, key) @app.route('/sm3_dgst', methods=['POST']) @check_param('msg') def sm3_dgst_endpoint(msg): return sm3_dgst(msg) @app.route('/sm2_sign', methods=['POST']) @check_param('path') def sm2_sign_endpoint(path): sign_file = path + '_sign' sm2_sign(path, sign_file) return {'path': sign_file} @app.route('/sm2_verify', methods=['POST']) @check_param('file_path') @check_param('sign_file_path') def sm2_verify_endpoint(sign_file_path, file_path): return sm2_verify(sign_file_path, file_path) @app.route('/upload', methods=['POST']) def upload(): file = request.files['file'] random_num = str(random.randint(10000, 99999)) filename = file.filename new_filename = random_num + '_' + filename save_path = './file/' + new_filename file.save(save_path) return {'path': save_path} @app.route('/download', methods=['POST']) @check_param('file_path') def download_file(file_path): return send_file(file_path, as_attachment=True) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000, debug=True)
我们还需要创建 file
目录,用来保存上传的文件,在我们进行 sm2签名和sm2验证的时候还需要公钥和私钥,我们在项目根目录中创建 sm2.key 和 sm2pub.key,内容如下:
- sm2.key
-----BEGIN PRIVATE KEY----- MIGHAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBG0wawIBAQQgSKhk+4xGyDI+IS2H WVfFPDxh1qv5+wtrddaIsGNXGZihRANCAAQwqeNkWp7fiu1KZnuDkAucpM8piEzE TL1ymrcrOBvv8mhNNkeb20asbWgFQI2zOrSM99/sXGn9rM2/usM/Mlca -----END PRIVATE KEY-----
- sm2pub.key
-----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEMKnjZFqe34rtSmZ7g5ALnKTPKYhM xEy9cpq3Kzgb7/JoTTZHm9tGrG1oBUCNszq0jPff7Fxp/azNv7rDPzJXGg== -----END PUBLIC KEY-----
这个时候我们的 API
就构建好了,可以从代码中看到,我们在 /
路由上还进行了前端页面的渲染,我们需要在项目目录中创建 templates
目录,用来放置我们的前端程序,在该目录中创建 index.html
作为客户端的入口
当前项目目录结构如下:
构建Web页面
- index.html
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>铜锁SM2、SM3、SM4在线加密解密</title> <link rel="stylesheet" href="//unpkg.com/layui@2.6.8/dist/css/layui.css"/> <script src="//unpkg.com/layui@2.6.8/dist/layui.js"></script> </head> <body> <div style="padding: 20px 20px"> <div class="layui-tab"> <ul class="layui-tab-title"> <li class="layui-this">SM4加解密算法</li> <li>SM3杂凑算法</li> <li>SM2签名和验签</li> </ul> <div class="layui-tab-content"> <div class="layui-tab-item layui-show"> <div class="layui-row layui-anim-scale pt10" style="width: 100%;"> <div class="layui-col-md5"> <textarea style="resize: none;min-height: 280px;" placeholder="在此输入明文,选择对应的加密方式,也可以加入密钥,然后加密即可。" class="layui-textarea w99 fl m-h100" id="msg_source"></textarea> </div> <div class="layui-col-md2"> <form class="layui-form" action="" onsubmit="return !1;"> <div class="layui-form-item layui-col-md-offset2"> <div class="layui-input-inline fr w200 xpswd"><input type="text" name="text" id="pwd" placeholder="在此输入密钥" autocomplete="off" class="layui-input"> </div> </div> <div class="layui-form-item layui-col-md-offset2"> <div class="layui-input-inline fr w200 mbt"> <button class="layui-btn" onclick="decrypt();"><i class="layui-icon m-hide"></i>解密 </button> <button class="layui-btn" onclick="encrypt();">加密<i class="layui-icon m-hide"></i> </button> </div> </div> </form> </div> <div class="layui-col-md5"><textarea name="cipher" class="layui-textarea w99 fr " placeholder="加密后的密文会显示在这里。也可以是待解密的密文。" style="resize: none;min-height: 280px;" id="encrypt_result" ondblclick="this.focus();this.select();"></textarea></div> </div> </div> <div class="layui-tab-item"> <div> <textarea style="resize: none;height: 200px;" placeholder="原文放到这里即可。" class="layui-textarea w99 fl " id="source" maxlength="1000"></textarea> </div> <div style="display: inline-block;margin-top: 10px;margin-bottom:20px;"> <button class="layui-btn" id="ensm3" onclick="ensm3()">SM3加密<i class="layui-icon"></i></button> </div> </div> <div class="layui-tab-item"> <div> <button type="button" class="layui-btn" id="sign_file"> <i class="layui-icon"></i>上传需要签名的文件 </button> <div style="display: inline-block;margin-top: 10px;margin-bottom:20px;"> <button class="layui-btn" id="sign" onclick="download()">下载签名文件<i class="layui-icon"></i> </button> </div> </div> <div> <button type="button" class="layui-btn" id="upload_origin_file"> <i class="layui-icon"></i>上传源文件 </button> <button type="button" class="layui-btn" id="upload_sign_file"> <i class="layui-icon"></i>上传签名文件 </button> <div style="display: inline-block;margin-top: 10px;margin-bottom:20px;"> <button class="layui-btn" onclick="verify()">验证签名<i class="layui-icon"></i></button> </div> </div> </div> </div> </div> </div> </body> <script> function ajaxRequest(url, data, onSuccess, onError) { var xhr = new XMLHttpRequest(); xhr.open('POST', url, true); xhr.onreadystatechange = function () { if (xhr.readyState === XMLHttpRequest.DONE) { if (xhr.status === 200) { onSuccess(JSON.parse(xhr.responseText)); } else { onError(JSON.parse(xhr.statusText)); } } }; var formData = new FormData(); for (var key in data) { formData.append(key, data[key]); } xhr.send(formData); } // 获取 id 为 msg_source 的元素 var source = document.getElementById('msg_source'); var source_text = document.getElementById('source'); var pwd = document.getElementById('pwd'); // 获取 id 为 encrypt_result 的元素 var result = document.getElementById('encrypt_result'); function encrypt() { console.log('加密') const v = source.value const pass = pwd.value if (!v || v === '') { return alert('加密数据为空') } if (!pass || pass === '') { return alert('密钥为空') } ajaxRequest('http://127.0.0.1:5000/sm4_cbc_encrypt', {'msg': v.trim(), 'key': pass.trim()}, function (res) { console.log(res); result.value = res.output; }, function (err) { console.log(err); }) } function decrypt() { console.log('解密') const v = result.value.trim() if (!v || v === '') { return alert('解密数据为空') } const pass = pwd.value.trim() if (!pass || pass === '') { return alert('密钥为空') } ajaxRequest('http://127.0.0.1:5000/sm4_cbc_decrypt', {'msg': v, 'key': pass}, function (res) { console.log(res); if (res.error !== '') { return layer.alert('解密失败:' + res.error, {'icon': 2}); } source.value = res.output; }, function (err) { console.log(err); }) } function ensm3() { console.log('sm3加密') const v = source_text.value if (!v || v === '') { return alert('加密数据为空') } ajaxRequest('http://127.0.0.1:5000/sm3_dgst', {'msg': v.trim()}, function (res) { console.log(res); source_text.value = res.output; }, function (err) { console.log(err); }) } var file_path = '' var origin_file_path = '' var sign_file_path = '' function download() { console.log('file_path') if (!file_path || file_path === '') { return alert('签名文件未上传') } var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://127.0.0.1:5000/download', true); xhr.responseType = 'blob'; xhr.onload = function () { if (xhr.status === 200) { var blob = new Blob([xhr.response], {type: 'application/octet-stream'}); var a = document.createElement('a'); a.href = window.URL.createObjectURL(blob); a.download = Math.floor(Math.random() * 10000) + '_sign_file'; document.body.appendChild(a); a.click(); document.body.removeChild(a); layer.alert('下载签名文件成功', {'icon': 1}); } else { console.log(xhr.statusText); } }; var fData = new FormData(); fData.append('file_path', file_path); console.log(file_path); console.log(fData) xhr.send(fData); } function verify() { console.log('verify') const v = source.value if (!origin_file_path || origin_file_path === '') { return alert('原始文件未上传!') } if (!sign_file_path || sign_file_path === '') { return alert('签名文件未上传!') } ajaxRequest('http://127.0.0.1:5000/sm2_verify', { 'file_path': origin_file_path, 'sign_file_path': sign_file_path }, function (res) { console.log(res); layer.alert(res.output); }, function (err) { console.log(err); }) } uploadFile('#sign_file', function (res) { ajaxRequest('http://127.0.0.1:5000/sm2_sign', {'path': res.path}, function (sign_res) { file_path = sign_res.path }, function (sign_err) { console.log(sign_err); }) }) uploadFile('#upload_origin_file', function (res) { origin_file_path = res.path }) uploadFile('#upload_sign_file', function (res) { sign_file_path = res.path }) function uploadFile(elem, doneCallback, errorCallback) { layui.use('upload', function () { var upload = layui.upload; //执行实例 var uploadInst = upload.render({ elem: elem, accept: 'file', url: 'http://127.0.0.1:5000/upload', done: function (res) { doneCallback(res); }, error: function () { errorCallback(); } }); }); } </script> </html>
项目预览
我们需要在容器中启动该项目:
python3 main.py
并输入 127.0.0.1:5000
在浏览器中进行预览,最终效果如下: