BUPT密码工程课后作业6

 

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的架构 首先,找到一个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又出问题

select what to run

解决方案原来是因为没有当成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做了一点点

首先生成国密证书

github上国密证书

使用apache配置https

参考内容 sudo apachectl start