模拟CA系统

实现一个ca系统,可以接受用户的认证请求,安全储存用户信息,记录储存对用户的一些认证信息,给用户颁发证书,可以吊销。
(1)接受用户的提交申请,提交时候让用户自己产生公钥对;
(2)接受用户的申请,包括用户信息的表单提交,公钥的提交;
(3)在对用户实施认证的过程中,储存相应的电子文档,比如证书、营业执照的扫描文档;
(4)通过验证的给予颁发证书;
(5)用户密钥丢失时,可以吊销证书,密钥作废。

1.前言
1.1实验目的
实现一个ca系统,可以接受用户的认证请求,安全储存用户信息,记录储存对用户的一些认证信息,给用户颁发证书,可以吊销。
(1)接受用户的提交申请,提交时候让用户自己产生公钥对;
(2)接受用户的申请,包括用户信息的表单提交,公钥的提交;
(3)在对用户实施认证的过程中,储存相应的电子文档,比如证书、营业执照的扫描文档;
(4)通过验证的给予颁发证书;
(5)用户密钥丢失时,可以吊销证书,密钥作废。

1.2实验环境
运行系统:win10
开发环境:mysql、navicat、java,eclipse

1.3安装说明
1.3.1使用mysql(见附录)
1.3.2数据库连接(使用navicat)
1.3.3建立数据表

1.4准备工作
1.4.1 mysql连接eclipse
(1)关键步骤是下载mysql-connector-java-8.0.18.jar
这步非常关键,否则连接不上数据库

1-1建立成功
1.4.2新建jdbcutil.java
用于实现数据库和程序的连接
其关键代码如下:
driverClass=“com.mysql.jdbc.Driver”;
url=“jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC”;
user=“root”;
password="";
Driver是个实现类,它由具体的数据库厂商来实现。
它的connect方法可以获取数据库连接

1.4.3申请证书(详见附录)
(1)设置密钥库的口令,并填写信息申请证书,这里密码为111111
(2)将证书导出,存入文件中
(3)更改密码(不是必要步骤)设置密码为Changeit

2.系统分析
2.1系统需求概述
本次课程设计是实现一个简单的CA颁发系统,该系统主要实现以下几个需求:
(1) CA颁发机构(服务器)接受用户(客户机)的认证请求,(服务器)通过用户提交的信息,判断申请用户是否合法,包括用户的公钥是否已经申请注册过,是否注销过,是否接受用户的请求。
(2) 用户认证信息通过后,将认证信息存储,接下来可以接收用户信息和用户自己产生的公钥;
(3) 接受完成后,先通过获取CA证书获得CA颁发机构的信息以及CA的私钥,然后对接收到的用户信息和公钥进行数字签名,生成数字证书并颁发给用户,同时需要存储用户信息,证书
(4) 数字证书吊销:挂失操作具体为将用户需要挂失的公钥存入到不信任的公钥数据库中,这样,这个公钥就会被作废,但是在挂失前需要对用户进行身份验证,防止恶意挂失行为,验证通过后还要访问不信任公钥数据库中这个公钥是否已经存在,如果存在,弹出错误提示,如果不存在,则将公钥进行挂失。
(5) 客户机能够接收到服务器对自己请求的回复,如果证书申请成功,能将证书在
本地找到。

2.2系统体系结构设计
2.2.1系统实现流程图
首先从bytes[8]中提取判断符,判断用户的请求(a代表申请,b代表注销)
申请证书调用函数camain(bytes,apub),将用户提供的用户信息和公钥传给函数。
将处理结果用i保存。
注销证书调用函数cancel(bytes[6], bytes[7]);将用户提供的认证信息和公钥传给函数。将处理结果用i保存。
判断bytes[8]和i,判断给用户返回什么信息。

2-1总体功能流程图

2.2.2网络通信流程图
serverSocket = new ServerSocket(10086);
创建一个socket对象,监听10086接口。
in = new ObjectInputStream(socket.getInputStream());
将客户机传来的信息用in接收
然后将in中的内容用数组bytes[]存储
os = socket.getOutputStream();
将服务器要传给客户机的内容存在pw和newcert中,其中pw是系统消息,newcert是证书和签名。

图2-2socket流程图

3.功能设计
3.1判断用户
设计三个变量,ss,id,s1,分别用于判断申请人是否合法,用户信息表是否注册,是否挂失过,并对于结果存在i中,由i得到系统提示。
这三个功能分别调用函数
if_id_exists(userID) ,
userinfo_id_exist(userID)
if_pkey_exsit(apub);
判断过程如下:

3.2证书生成
函数Mycertificate(String []Personinfo,String Apub),用于证书生成,
我们传递了用户信息和公钥两个变量。
getSignCertInfo();
//获取签名证书信息
signCertificate(Personinfo,Apub);
这个函数用来用签名证书信息签发代签名证书,我们传递给它用户信息和公钥
boolean test=createNewCertificate(Personinfo) ;
创建并保存签发后的新证书,这时我们只需要将用户信息发给它。并用布尔型变量表示,证书是否申请成功。

3.3取得并保存证书
   Myc.java Line.149

用函数createNewCertificate(String []info) 取得并保存证书,用变量nerwcert保存证书,sig保存签名。
newcert=new X509CertImpl(ClientInfo);
newcert.sign(CAPrivateKey, “sha256WithRSA”);
取得这些信息后我们用文件保存函数将证书保存,用返回函数,将证书信息交给socket类,由它发给客户机。

3.4界面详细设计
程序的界面设计主要是显示数字签名、保存的结果,以及用户信息的设置输入等功能。申请证书之前,该程序要求用户输入信息,然后才能单击申请证书按钮,既可以进行证书申请,最后要在文本框中显示申请数字签名最后的结果,签名完成之后用户可以继续进行挂失申请或者是直接退出界面。
我们觉得采用Swing常用组件之文本区:JTextArea、按钮:button、、标签组:JLabel以及文本字段:JTextFileld来实现。
(1) 文本显示框:
具体位置:main2.java121行
private JButton getC2ConnJButton() {
if (canceljbutton==null) {
canceljbutton=new JButton();
canceljbutton.setBounds(new Rectangle(300,145,97,26));
canceljbutton.setText(“挂失”);
canceljbutton.addActionListener(this);
}
return canceljbutton;
}
(2) 名字编辑框
具体位置:main2.java35行
jusertextfield=new JTextField(“root”);
jusertextfield.setBounds(new Rectangle(70,10,70,26));
}
(3) 文本区域信息添加函数
具体位置:main2.java200行
jtextarea.append("\n"+s+"\n");
(4) 挂失按钮
具体位置:main2.java110行
canceljbutton=new JButton();
canceljbutton.setBounds(new Rectangle(300,145,97,26));
canceljbutton.setText(“挂失”);
canceljbutton.addActionListener(this);

3.5socket通信设计
上图体现的是,利用socket通信时,一段数据流的标准开销,也成为我们通信的报文头,其中id用来做身份认证,公钥作为客户签发自定义的公钥,请求类型用于我们判断对方的数据流的函数流向。

4.解决系统特色及关键技术
4.1将字符串转化为公钥
具体位置:myc.java220行
X509EncodedKeySpec apubkeyspec=new X509EncodedKeySpec(new BASE64Decoder().decodeBuffer(pubkey));
//RSA对称加密算法
KeyFactory keyFactory;
keyFactory= KeyFactory.getInstance(“RSA”);
//取公钥匙对象
publicKey=keyFactory.generatePublic(apubkeyspec);
在接收用户信息的时候,都是字符串,所以不能直接数字签用于数字签名,否则数字签名会失败,所以需要将字符串先转化为公钥,再将公钥返回才可以用

4.2验证签名
具体位置:main2.java246行
//获得签名实例
signature=Signature.getInstance(certificate.getSigAlgName());
//用证书公钥进行初始化
signature.initVerify(certificate.getPublicKey());
//更新资源
signature.update(original);
//验证数字签名
return signature.verify(sign);
首先,先获得签名实例,用证书公钥进行初始化,之后需要更新元数据,然后验证数字签名,并返回数字签名验证结果,验证时需要知道CA证书数字签名的算法,必须根据CA证书的算法采用相同的算法来确定验证数字签名的算法才能够保证验证签名成功。

4.3进入数据库
Myc.java L.152
KeyStore ks=KeyStore.getInstance(“JKS”);
ks.load(in,storePwd);//得到路径和密码,可进入
我们想要进入数据库,必须得到数据库路径,以及数据库密码,用函数load()就可以进入数据库了。

5.所遇到的问题及分析解决

5.1警告:SerialVersionUid
The serializable class main2 does not declare a static final serialVersionUID field of type long
5.1.1问题出现的原因:
只要任何类别实作了Serializable这个介面,如果没有加入serialVersionUID,Eclipse都会给你warning提示,这个serialVersionUID为了让该类别Serializable後兼容.(其实有这个功能是好的.)
5.1.2分析问题:
如果不显式设置SerialVersionUid,有什么后果?
jdk文档中有解释,建议我们显式声明,因为如果不声明,JVM会为我们自动产生一个值,但这个值和编译器的实现相关,并不稳定,这样就可能在不同JVM环境下出现反序列化时报InvalidClassException异常。
①两种SerialVersionUid有什么区别?
一种就是1L,一种是生成一个很大的数,这两种有什么区别呢?
看上去,好像每个类的这个类不同,似乎这个SerialVersionUid在类之间有某种关联。其实不然,两种都可以,从JDK文档也看不出这一点。我们只要保证在同一个类中,不同版本根据兼容需要,是否更改SerialVersionUid即可。
对于第一种,需要了解哪些情况是可兼容的,哪些根本就不兼容。
在可兼容的前提下,可以保留旧版本号,如果不兼容,或者想让它不兼容,就手工递增版本号。
1->2->3…
第二种方式,是根据类的结构产生的hash值。增减一个属性、方法等,都可能导致这个值产生变化。我想这种方式适用于这样的场景:
开发者认为每次修改类后就需要生成新的版本号,不想向下兼容,操作就是删除原有serialVesionUid声明语句,再自动生成一下。
个人认为,一般采用第一种就行了,简单。第二种能够保证每次更改类结构后改变版本号,但还是要手工去生成,并不是修改了类,会提示你要去更新这个SerialVersionUid
, 所以,实际上让人很迷惑。

5.2 警告:静态方法
This method has a constructor name :
第一:main方法是个静态方法如果你想调用本类中的其他方法要么将其他方法设置为静态的
要么创建一个该类的对象,调用之;
第二:尽量不要将你的方法的名字和类的名字相同。

5.3错误:驱动

5-2错误界面

5.3.1No suitable driver found for jdbc:mysql://localhost:3306/mydb
出现这样的情况,一般有四种原因:
①连接URL格式出现了问题(Connection conn=DriverManager.getConnection(“jdbc:mysql://localhost:3306/XX”,“root”,“XXXX”)
②驱动字符串出错(com.mysql.jdbc.Driver)
③Classpath中没有加入合适的mysql_jdbc驱动(驱动要和你的数据库版本一致)
④驱动jar包放的位置不对
首先,解决版本不一致的问题,因为我的mysql为mysql server 8.0 而我用的connector 为5.0 必须重新下载

5-3解决错误
然后,我的驱动复制错了,不应复制压缩包,应该复制以.jar结尾的connector
再然后,系统提示,应该将驱动字符串改为:driverClass=“com.mysql.cj.jdbc.Driver”;

5.3.2问题三解决了之后,出现了新的问题
The server time zone value ‘?й???’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
为URL添加参数serverTimezone=UTC即可,这里的时区可以根据自己数据库的设定来设置(GMT/UTC )。
jdbc:mysql://localhost:3306/dbname?useUnicode=true&characterEncoding=UTF-8&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC

5.3.3新的问题又出现了:Parameter index out of range (1 > number of parameters, which is 0).
String sql=“select pkey form mypub where pkey=?”;原来是?是中文的?,改成英文的就好了

5.4 错误:keystore入口
Keytool存储keystore位置不对
通过CMD命令创建的keystore存储在c:\user下
而我们的程序执行命令时,是在iava下,必须指定好具体详细的位置要不然找不到

5.5 错误:Premature EOF

属于IOException异常中,在解决这个问题中查阅了很多资料,甚至源码。目前在网络里的说法是:EOF是end of file的缩写,premature eof指的是读取文件时非正常的提前进入EOF状态
问题解决:用户输入的密钥不符合算法规定

6.测试
6.1 首次申请
6.1.1服务器接受用户的提交申请,提交时候让用户自己产生公钥对. 该界面提供了用户输入的界面,其中认证信息用于验证用户的合法性,公钥为用户自己的公钥,其他信息根据用户情况进行填写。

6-1服务器接收申请界面
6.1.2系统提示:申请成功

6-2申请成功

图6-3 用户证书文档
6.1.3以下为用户得到的证书与签名(图6-3)
6.1.4保存成功
此时我们可以看到数据库中保存了用户信息

6-4数据库成功保存信息
6.2重复申请
该用户已经得到证书后,再次申请
此时,系统则会提示,该用户已经注册过

6-4再次申请
6.3挂失
该用户密钥丢失需要挂失
系统提示,挂失成功

6-5挂失成功
6.4挂失后重复申请
此时如果我们再用挂失过的公钥,再次申请
系统则会显示失败

6-6挂失失败

最后,附上源码链接:客户端 https://download.csdn.net/download/luckism/12652274
主机端 https://download.csdn.net/download/luckism/12652278

大概地给大家展示一下证书
在这里插入图片描述

  • 10
    点赞
  • 80
    收藏
    觉得还不错? 一键收藏
  • 20
    评论
好的,我来给您展示一个简单的模拟航班售票系统的程序,使用Java中的Runnable接口实现多线程。 首先,我们需要定义一个Flight类来表示航班,包括航班号、起点城市、终点城市、出发时间、座位数等信息: ```java public class Flight { private String flightNo; private String from; private String to; private Date departureTime; private int seatNum; // 构造函数 public Flight(String flightNo, String from, String to, Date departureTime, int seatNum) { this.flightNo = flightNo; this.from = from; this.to = to; this.departureTime = departureTime; this.seatNum = seatNum; } // getter 和 setter 方法 // ... } ``` 然后,我们定义一个Ticket类来表示机票,包括航班号、座位号等信息: ```java public class Ticket { private String flightNo; private int seatNo; // 构造函数 public Ticket(String flightNo, int seatNo) { this.flightNo = flightNo; this.seatNo = seatNo; } // getter 和 setter 方法 // ... } ``` 接下来,我们定义一个FlightTicketSystem类来模拟航班售票系统,包括航班列表、机票列表等信息,并实现Runnable接口来模拟售票线程: ```java public class FlightTicketSystem implements Runnable { private List<Flight> flights; private List<Ticket> tickets; // 构造函数 public FlightTicketSystem(List<Flight> flights) { this.flights = flights; this.tickets = new ArrayList<>(); } // 售票方法 public synchronized boolean sellTicket(String flightNo) { Flight flight = null; for (Flight f : flights) { if (f.getFlightNo().equals(flightNo)) { flight = f; break; } } if (flight == null || flight.getSeatNum() <= 0) { return false; } Ticket ticket = new Ticket(flightNo, flight.getSeatNum()); flight.setSeatNum(flight.getSeatNum() - 1); tickets.add(ticket); System.out.println(Thread.currentThread().getName() + " sells ticket: " + ticket); return true; } // run方法,模拟售票过程 @Override public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } int index = (int) (Math.random() * flights.size()); String flightNo = flights.get(index).getFlightNo(); if (!sellTicket(flightNo)) { System.out.println(Thread.currentThread().getName() + " fails to sell ticket for flight " + flightNo); } } } } ``` 最后,我们在主函数中创建多个线程来模拟售票过程: ```java public class Main { public static void main(String[] args) { List<Flight> flights = new ArrayList<>(); flights.add(new Flight("CA001", "Beijing", "Shanghai", new Date(), 10)); flights.add(new Flight("CA002", "Shanghai", "Beijing", new Date(), 10)); flights.add(new Flight("CA003", "Beijing", "Guangzhou", new Date(), 10)); flights.add(new Flight("CA004", "Guangzhou", "Beijing", new Date(), 10)); flights.add(new Flight("CA005", "Shanghai", "Guangzhou", new Date(), 10)); flights.add(new Flight("CA006", "Guangzhou", "Shanghai", new Date(), 10)); FlightTicketSystem system = new FlightTicketSystem(flights); Thread t1 = new Thread(system, "Seller A"); Thread t2 = new Thread(system, "Seller B"); Thread t3 = new Thread(system, "Seller C"); t1.start(); t2.start(); t3.start(); } } ``` 以上就是一个简单的航班售票系统模拟程序,使用了Java中的Runnable接口来实现多线程。当线程在售票时,会随机选择一个航班并尝试售出一张机票,如果售票成功,会将机票信息打印出来;如果售票失败,会输出相应的提示信息。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值