BUPT密码工程课后作业6
要求1
陈述
题目1 :基于国密系统算法实现简易密钥协商协议
1、自己写程序编写并调试SM2,来源可以是互联网上的,但是必须是自己调试的,不可以是成熟库的函数调用;
2、设计一个简易的密钥协商协议,使用SM2、SM3和SM4算法实现来实现它;
3、要求有交互,采用标准socket即可;
4、所有的要求文档和代码结合一起,以报告方式给出说明,并辅以代码。
调试SM2
要求2
陈述
题目2:基于国密搭建Web服务器
1、请大家搭建一个Web服务器,种类自选,可以是tomcat/jetty/nginx/apache等等,都可以;
2、不用设计Web页面,使用默认的就可以,需要大家配置成https访问;并且可以支持双向认证访问。也就是,客户端和服务器端均需要提供证书访问。注意:既需要客户端证书,也需要根证书和服务器端证书;
3、所有的证书需要使用国密算法,而不是通常用的RSA。此处国密算法可以利用开源的库,或者自己生成;
过程
题目1
SM2调试
SM2的架构如下图所示:
首先,找到一个SM2的程序
代码分析
- 首先从ecc_param中获取参数对pab赋值,来确定一条椭圆曲线
ecc_curve
- 同时获取gx、gy的值,得到基点G
- 然后实例化一个
ECKeyGenerationParameters
对象来得到密钥生成器的相关参数 - 最后实例化一个
ECKeyPairGenerator
对象生成密钥对
SM2.java
public SM2() {
this.ecc_p = new BigInteger(ecc_param[0], 16);
this.ecc_a = new BigInteger(ecc_param[1], 16);
this.ecc_b = new BigInteger(ecc_param[2], 16);
this.ecc_n = new BigInteger(ecc_param[3], 16);
this.ecc_gx = new BigInteger(ecc_param[4], 16);
this.ecc_gy = new BigInteger(ecc_param[5], 16);
this.ecc_w = 127;
this.ecc_gx_fieldelement = new Fp(this.ecc_p, this.ecc_gx);
this.ecc_gy_fieldelement = new Fp(this.ecc_p, this.ecc_gy);
this.ecc_curve = new ECCurve.Fp(this.ecc_p, this.ecc_a, this.ecc_b);
this.ecc_point_g = new ECPoint.Fp(this.ecc_curve, this.ecc_gx_fieldelement, this.ecc_gy_fieldelement);
this.ecc_bc_spec = new ECDomainParameters(this.ecc_curve, this.ecc_point_g, this.ecc_n);
ECKeyGenerationParameters ecc_ecgenparam;
ecc_ecgenparam = new ECKeyGenerationParameters(this.ecc_bc_spec, new SecureRandom());
this.ecc_key_pair_generator = new ECKeyPairGenerator();
this.ecc_key_pair_generator.init(ecc_ecgenparam);
}
调试过程中的问题记录
eclipse又出问题
解决方案原来是因为没有当成java application来运行,在文件名右键选择java application运行即可。
运行时又出现问题,找不到org.bouncycastle, 最终解决方案,首先下载,然后配置环境变量并按照这篇导入工程。 Access restriction: The type ‘BASE64Encoder’ is not API
最后一个error了
这个鼓捣了整整一天,后来换了一个jar包,就好了。。可能之前从网址下载的版本太新了但是代码旧了。
密钥协商协议
密钥协商的结果展示:
代码分析
- Client向Server发起连接请求,后实例化一个SM2_Exchange对象A,得到其公私钥,并计算A的身份标识ZA,接着实例化一个Exch对象,并使用A的私钥初始化,产生一个随机数r充当临时私钥,得到RA
String host = "127.0.0.1";
int port = 23333;
//和server建立连接
Socket socket = new Socket(host, port);
System.out.println("client is working...");
SM2_Exchange A = new SM2_Exchange();
A.generateKeyPair();//
byte[] pubK_A = A.getPubKey();//获得A的公钥
byte[] priK_A = A.getPriKey();//获得A的私钥
byte[] ZA =new byte[32];
A.computeZ("A is a client",ZA);
Exch A_EX = new Exch();
A_EX.Init(priK_A);
ECPoint R_A = A_EX.R1;//获得RA
byte []RA = R_A.getEncoded();
- 同时Server端接受Client发起的Socket连接后,进行与Client端进行相同的操作,得到RB
int port = 23333;
ServerSocket server = new ServerSocket(port);
// 服务器端监听
System.out.println("server is waiting...");
Socket socket = server.accept();
SM2_Exchange B = new SM2_Exchange();
B.generateKeyPair();
byte[] pubK_B = B.getPubKey();//获得B的公钥
byte[] priK_B = B.getPriKey();//获得B的私钥
byte[] ZB =new byte[32];
B.computeZ("B is a server",ZB);
Exch B_EX = new Exch();
B_EX.Init(priK_B);
ECPoint R_B = B_EX.R1;//获得RB
byte[] RB = R_B.getEncoded();
其中Exch对象初始化的代码如下,参数为私钥,计算R1的同时计算出X1和t的值。
public void Init(byte[] priKey)
{
SM2 sm2 = SM2.Instance();
w= sm2.ecc_w;
AsymmetricCipherKeyPair key = sm2.ecc_key_pair_generator.generateKeyPair();
ECPrivateKeyParameters ecpriv = (ECPrivateKeyParameters) key.getPrivate();
ECPublicKeyParameters ecpub = (ECPublicKeyParameters) key.getPublic();
BigInteger r = ecpriv.getD(); //随机数r,在这是用临时私钥代替
R1 = ecpub.getQ(); //临时私钥对应的临时公钥,也就是R1
BigInteger x1 = R1.getX().toBigInteger();//取出R1的横坐标x1
BigInteger x_1 = computeX(w,x1);
compute_t(priKey,x_1,r);//计算t
}
接下来A需要初始化一个Negotiation对象,即需要发送给B的交换数据包括PA、RA以及A的身份ZA,然后利用ObjectOutputStream发送给B,并接收从B发来的Negotiation对象,解析后利用PB、RB、ZA、ZB计算出共享密钥
OutputStream agree = socket.getOutputStream();
ObjectOutputStream negot = new ObjectOutputStream(agree);
negot.writeObject(new Negotiation(pubK_A,RA,ZA));
negot.flush();
InputStream fromB = socket.getInputStream();
ObjectInputStream isfromB = new ObjectInputStream(fromB);
Object obj = isfromB.readObject();
Negotiation B = (Negotiation) obj;
SM2 sm2 = new SM2();
ECPoint pubB = sm2.ecc_curve.decodePoint(B.getpub());
ECPoint R_B = sm2.ecc_curve.decodePoint(B.getR());
byte [] ZB = B.getZ();
String sharedkey= A_EX.computeKey(pubB,R_B,ZA,ZB);
B接收到A发来的Negotiation对象解析后,利用PA、RA、ZA、ZB 计算出共享密钥,然后发送自己的Negotiation对象给A;
InputStream fromA = socket.getInputStream();
ObjectInputStream isfromA = new ObjectInputStream(fromA);
Object obj = isfromA.readObject();
Negotiation A = (Negotiation)obj;
SM2 sm2 = new SM2();
ECPoint pubA = sm2.ecc_curve.decodePoint(A.getpub());
ECPoint R_A = sm2.ecc_curve.decodePoint(A.getR());
byte [] ZA = A.getZ();
String sharedkey = B_EX.computeKey(pubA,R_A,ZA,ZB);
OutputStream agree = socket.getOutputStream();
ObjectOutputStream negot = new ObjectOutputStream(agree);
negot.writeObject(new Negotiation(pubK_B,RB,ZB));
negot.flush();
其中Negotiation类的如下,需要进行Socket传输,继承了一个序列化接口
import java.io.Serializable;
public class Negotiation implements Serializable {
private static final long serialVersionUID = 1L;
private byte[] pub;
private byte[] R_;
private byte[] Z_;
public Negotiation(){
}
public Negotiation (byte[] pub, byte [] R_, byte[] Z_) {
this.pub = pub;
this.R_ = R_;
this.Z_ = Z_;
}
public byte[] getpub() {
return pub;
}
public byte[] getR() {
return R_;
}
public byte[] getZ() {
return Z_;
}
}
计算共享密钥computeKey的方法如下:1)获得X22)计算出U的纵横坐标3)利用KDF来生成256比特公钥。 因为AB接下来使用SM4进行对称加解密,所以暂时只取前128位作为公钥
public String computeKey(ECPoint pubKey_B,ECPoint RB,byte[] Z1,byte[] Z2) {
BigInteger x2 = RB.getX().toBigInteger();//取出RB的横坐标x2
BigInteger x_2 = computeX(w,x2);
ECPoint U = computePoint(x_2,pubKey_B,RB);
//取出U的横坐标ux,为字节数组
ux = Util.byteConvert32Bytes(U.getX().toBigInteger());
//取出U的横坐标uy,为字节数组
uy = Util.byteConvert32Bytes(U.getY().toBigInteger());
//用于存储KDF产生的256比特共享密钥
byte[] key = new byte[32];
//计算共享密钥
KDF(key,Z1,Z2);
//用于存储前128位共享密钥
byte[] result = new byte[8];
System.arraycopy(key,0,result,0,result.length);
//将公钥从字节数组转换为16进制字符串,输出公钥
System.out.print("public key:");
//将公钥从字节数组转换为16进制字符串,输出公钥
System.out.println(Util.getHexString(result,true));
return Util.getHexString(result,true);
}
AB双方都得到公钥后,A尝试使用SM4算法采用ECB模式加密一条消息发送给B;
OutputStream outputStream = socket.getOutputStream();
String message = "JRR";
SM4Utils sm4 = new SM4Utils();
sm4.setSecretKey(sharedkey.toString());
sm4.setHexString(false);
B收到A发来的密文,尝试用共享密钥利用SM4算法进行解密;
InputStream inputStream = socket.getInputStream();
byte[] bytes = new byte[1024];
int len;
StringBuilder enmessage = new StringBuilder();
inputStream.toString();
while ((len = inputStream.read(bytes)) != -1) {
//只有当客户端关闭它的输出流的时候,服务端才能取得结尾的-1
enmessage.append(new String(bytes, 0, len, "UTF-8"));
}
System.out.println("get enmessage from client: " + enmessage);
SM4Utils sm4 = new SM4Utils();
sm4.setSecretKey(sharedkey.toString());
sm4.setHexString(false);
String plainText = sm4.decryptData_ECB(enmessage.toString());
System.out.println("the messsge is: " + plainText);
题目2
安装配置nginx
sudo apt install nginx
设置防火墙,保证局域网可以访问web服务器。
这样是因为没有启用防火墙,如下操作:
(如果没有安装ufw—>sudo ufw version没有显示的话)
sudo apt-get install ufw
sudo ufw enable
创建nginx配置文件
cd /etc/nginx/sites-enabled
sudo vim https.conf
配置文件内容如下:
server {
listen 443 ssl;
server_name 192.168.1.4;
ssl on;
ssl_certificate /etc/nginx/keys/server.crt;#配置证书位置
ssl_certificate_key /etc/nginx/keys/server.key;#配置秘钥位置
ssl_client_certificate /etc/nginx/keys/client.crt;#双向认证
ssl_verify_client on; #双向认证
ssl_session_timeout 5m;
root html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
重载配置
nginx -s reload
HTTPS认证
- HTTPS单向认证 在HTTPS单向认证中,客户端发起连接请求,将版本信息等发送给服务器端,服务器端将本机的公钥证书发送给客户端,客户端读取公钥证书,取出服务器公钥,客户端生成密钥,并且用服务器公钥来加密形成密文,发送给服务器端,服务器用私钥解密密文,得到密钥,后续的通讯过程就会使用这个密钥。
- HTTPS双向认证 在双向认证当中,客户端发起连接请求,将SSL协议版本的信息发送给服务端,服务端将公钥证书发送给客户端,客户端读取公钥证书,取出服务器公钥,客户端将客户端公钥证书发送给服务器端,服务器端解密客户端公钥证书,拿到客户端公钥,客户端发送自己支持的加密方案给服务器端,服务器端选择加密方案,使用客户端的公钥加密之后发送回给客户端。客户端使用私钥解密,生成密钥,用服务器的公钥加密,发送给服务器端,服务器使用私钥解密得到通信密钥。
这个过程被描绘在下图中,其中橙色部分代表双向认证比单向认证多出来的过程。
安装GmSSL
OpenSSL
在GmSSL搞不定的时候试了一下openssl,因为看到issue里面有建议用另外一个branch(原来就是openssl)虽然openssl的官方文档给出了使用sm2/3生成证书和签名的方法,但是似乎虽然文档上面有,但是现在还做不到。
GmSSL
下载安装,遇见一个问题
一开始盲目寻找问题出处,花费了好多时间,后来静下心来看了一下报错,发现时sm9test.c这个文件的问题。提交了一个issue,这个项目后面也可能一直更新,如果再遇到什么错误的时候可以看报错然后去源代码里面修改。 在这里面把issue里面涉及到的片段注释掉就好了。
遇见的另外一个问题,和libcrypto.so有关
解压
sudo unzip GmSSL-master.zip
编译安装
./config
make
sudo make install
检查安装是否成功。
gmssl version
生成自签名根证书
sudo mkdir /etc/nginx/keys/
cd /etc/nginx/keys/
# 利用SM2创建根证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out root.key
# 利用SM3创建根证书请求文件
gmssl req -sm3 -new -out root.csr -key root.key
# 创建根证书
gmssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650
生成自签名服务器端证书
# 利用SM2生成服务器端证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out server.key
# 利用SM3生成服务器端证书请求文件
gmssl req -sm3 -new -out server.csr -key server.key
# 生成服务器端公钥证书
gmssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
生成自签名客户端证书
# 利用SM2生成客户端证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out client.key
# 利用SM3生成客户端证书请求文件
gmssl req -sm3 -new -out client.csr -key client.key
# 生成客户端公钥证书
gmssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650
验证
使用同一局域网内的另外一台机器访问该nginx服务器
出现问题 firefox显示:
Chrome显示
觉得可能和浏览器有关系,但是可以看出https已经被部署上去了。这个
Apache做了一点点
首先生成国密证书
使用apache配置https
参考内容
sudo apachectl start