Fork me on GitHub

UDP通信-AES和Base64加解密

基于UDP的通信和基于TCP的通信不同,基于UDP的信息传递更快,但不提供可靠性保证。也就是说,数据在传输时,用户无法知道能否正确地到达目的地主机,也不能确定数据到达目的地地顺序是否和发送地顺序相同。

基于UDP通信地基本模式如下:
(1)将数据打包,称为数据包,然后将数据包发往目的地。
(2)接收别人发来地数据包,然后查看数据包中地内容。

发送数据包

用DatagramPacket类将数据打包,即用DatagramPacket类创建一个对象,称为数据包。用DatagramPacket地以下两个构造方法创建待发送地数据包: DatagramPacket(byte data[], int length, InetAddtress address, int port);
使用该构造方法创建地数据包对象具有下列两个性质:
(1)含有data数组指定地数据
(2)该数据包将发送到地址是adress、端口号是port的主机上。
用DatagramSocket类的不带参数的构造方法DatagramSocket()创建一个对象,该对象负责发送数据包。例如:

DatagramSocket mail_out= new DatagramSocket();
mail_out.send(data_pack);

下面附上Mac.java, 其中使用了AES和Base64加解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.awt.event.*;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.*;
import org.apache.commons.codec.binary.Base64;
public class Mac{
public static void main(String[] args) {
MaCs maC= new MaCs();
}
}
class MaCs extends JFrame implements Runnable, ActionListener, KeyListener{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
JTextField out_message1= new JTextField(12);
JTextArea in_message= new JTextArea();
JButton send= new JButton("发送");
String password = "ABCDEFGHIJKLMNOP";
MaCs(){
setTitle("SS");
setSize(400, 200);
setBounds(700, 200, 350, 300);
setVisible(true);
send.addActionListener(this);
out_message1.addKeyListener(this);
JPanel pSouthJPanel= new JPanel();
pSouthJPanel.add(out_message1);
pSouthJPanel.add(send);
add(pSouthJPanel, "South");
add(new JScrollPane(in_message), "Center");
in_message.setEditable(false);
validate();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Thread thread= new Thread(this);
thread.start();//线程负责接收数据包
}
/**
* 使用参数中的密钥加密
*/
public static String Encrypt(String sSrc, String sKey) {
try{
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
//SecretKeySpec类是KeySpec接口的实现类,用于构建秘密密钥规范
//构造一个用AES算法加密的密钥
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式,创建密码器"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);//初始化为加密模式的密码器
//Cipher为加密解密提供密码功能
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));//加密
return new Base64().encodeToString(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}catch(Exception e){
e.printStackTrace();
//在命令行打印异常信息在程序中出错的位置及原因
return null;
}
}
/**
* 使用参数中的密钥解密
*/
public static String Decrypt(String sSrc, String sKey) {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);
String originalString = new String(original,"utf-8");
return originalString;
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
//回车发送
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode()== 10) {
(this).actionPerformed(null);
}
}
@Override
public void actionPerformed(ActionEvent event) {
// TODO Auto-generated method stub
String content1 = Encrypt(out_message1.getText(), password);
byte buffer[]= content1.trim().getBytes();
try {
InetAddress address= InetAddress.getByName("192.168.199.143");
DatagramPacket datagramPacket= new DatagramPacket(buffer, buffer.length, address, 888);
DatagramSocket mailDatagramSocket= new DatagramSocket();
mailDatagramSocket.send(datagramPacket);
String content2 = Decrypt(content1, password);
in_message.append("----------"+ df.format(new Date())+ "----------\nss: "+ content2+ "\n");
out_message1.setText(null);
} catch (Exception e) {
// TODO: handle exception
}
}
@Override
public void run() {
// TODO Auto-generated method stub
DatagramPacket packet= null;
DatagramSocket mailDatagramSocket=null;
byte data[]= new byte[8192];//定义数据包的大小
try{
packet= new DatagramPacket(data, data.length);
mailDatagramSocket= new DatagramSocket(666);
} catch(Exception e){
}
while(true){
if (mailDatagramSocket== null) {
break;
}
else {
try {
mailDatagramSocket.receive(packet);//可能会发生堵塞,直到收到数据包
String mesString= new String(packet.getData(), 0, packet.getLength());
String content3 = Decrypt(mesString, password);
//System.out.println("123");
in_message.append("**********"+ df.format(new Date())+ "**********\n");
in_message.append("mc: "+ content3+ "\n");
} catch (Exception e1) {
// TODO: handle exception
}
}
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}

接收数据包

用DatagramSocket的另一个构造方法DatagramSocket(int port)创建一个对象,其中的参数必须和待接收的数据包的端口号相同。例如,如果发送方发送的数据包的端口是5666,那么如下创建DatagramSocket对象:
DatagramSocket mail_in= new DatagramSocket(5666);
然后对象maill_in使用方法receive(DatagramPacket pack)接收数据包。该方法有一个数据包参数pack,方法receive把收到的数据包传递给该参数。因此,用户必须预备一个数据包,以便收取数据包。这时需使用DatagramPack类的另一个构造方法DatagramPacket(byte data[], int length)创建一个数据包,用于接收数据包,例如:

byte data[]= new byte[100];
int length= 90;
DatagramPacket pack= new DatagramPacket(data, length);
mail_in.receive(pack);

该数据包pack将接收长度是length字节的数据放入data。
值得注意的是: receive方法可能会堵塞,直到收到数据包。
下面附上Shens.java, 其中同样使用了AES、Base64加解密。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.awt.event.*;
import javax.crypto.Cipher;
import javax.crypto.spec.SecretKeySpec;
import javax.swing.*;
import org.apache.commons.codec.binary.Base64;
public class Shens{
public static void main(String[] args) {
ShenSs shenS= new ShenSs();
}
}
class ShenSs extends JFrame implements Runnable, ActionListener, KeyListener{
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
JTextField out_message= new JTextField(12);
JTextArea in_message= new JTextArea();
JButton send=new JButton("发送");
String password = "ABCDEFGHIJKLMNOP";
ShenSs(){
setTitle("MC");
//setSize(400, 200);
setBounds(300, 200, 350, 300);
setVisible(true);
send.addActionListener(this);
out_message.addKeyListener(this);
JPanel panel= new JPanel();//创建一个面板,里面可以布局
panel.add(out_message);
panel.add(send);
add(panel, "South");
add(new JScrollPane(in_message), "Center");
in_message.setEditable(false);
validate();//验证此容器及所有组件
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Thread thread= new Thread(this);
thread.start();//线程负责接收数据包
}
/**
* 使用参数中的密钥加密
*/
public static String Encrypt(String sSrc, String sKey) {
try{
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");//字节型数组
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
//SecretKeySpec类是KeySpec接口的实现类,用于构建秘密密钥规范
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//"算法/模式/补码方式"
cipher.init(Cipher.ENCRYPT_MODE, skeySpec);
//Cipher为加密解密提供密码功能
byte[] encrypted = cipher.doFinal(sSrc.getBytes("utf-8"));
return new Base64().encodeToString(encrypted);//此处使用BASE64做转码功能,同时能起到2次加密的作用。
}catch(Exception e){
e.printStackTrace();
//在命令行打印异常信息在程序中出错的位置及原因
return null;
}
}
/**
* 使用参数中的密钥解密
*/
public static String Decrypt(String sSrc, String sKey) {
try {
// 判断Key是否正确
if (sKey == null) {
System.out.print("Key为空null");
return null;
}
// 判断Key是否为16位
if (sKey.length() != 16) {
System.out.print("Key长度不是16位");
return null;
}
byte[] raw = sKey.getBytes("utf-8");
SecretKeySpec skeySpec = new SecretKeySpec(raw, "AES");
//SecretKeySpec类是KeySpec接口的实现类,用于构建秘密密钥规范
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");//只实例化一次
//Cipher为加密解密提供密码功能
cipher.init(Cipher.DECRYPT_MODE, skeySpec);
//初始化为解密模式的密码器
byte[] encrypted1 = new Base64().decode(sSrc);//先用base64解密
try {
byte[] original = cipher.doFinal(encrypted1);//解密
String originalString = new String(original,"utf-8");
return originalString;//明文
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
} catch (Exception ex) {
System.out.println(ex.toString());
return null;
}
}
//回车发送
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode()== 10) {
(this).actionPerformed(null);
}
}
@Override
public void actionPerformed(ActionEvent event) {//单击按钮发送数据包
// TODO Auto-generated method stub
String content1 = Encrypt(out_message.getText(), password);
byte buffer[]= content1.trim().getBytes();
try {
InetAddress address= InetAddress.getByName("127.0.0.1");
DatagramPacket datagramPacket= new DatagramPacket(buffer, buffer.length, address, 666);
//存放数据的数据报
DatagramSocket mailDatagramSocket= new DatagramSocket();
//接受或发送数据报的套接字
mailDatagramSocket.send(datagramPacket);
String content2 = Decrypt(content1, password);
in_message.append("**********"+ df.format(new Date())+ "**********\nmc: "+ content2+ "\n");
out_message.setText(null);
} catch (Exception e) {
// TODO: handle exception
}
}
@Override
public void run() {
// TODO Auto-generated method stub
DatagramPacket packet= null;
DatagramSocket mailDatagramSocket=null;
byte data[]= new byte[8192];//指定数据包的大小
try{
packet= new DatagramPacket(data, data.length);
mailDatagramSocket= new DatagramSocket(888);
} catch(Exception e){
}
while(true){
if (mailDatagramSocket== null) {
break;
}
else {
try {
mailDatagramSocket.receive(packet);
String mesString= new String(packet.getData(), 0, packet.getLength());
String content3 = Decrypt(mesString, password);
in_message.append("----------"+ df.format(new Date())+ "----------\n");
in_message.append("ss: "+ content3+ "\n");
} catch (Exception e1) {
// TODO: handle exception
}
}
}
}
@Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
@Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}

由于使用了Base64加解密,会出现Base64 cannot be resolved to a type这个错误。可以参考我的上一篇博文,那里有解决的方法。

Your support will encourage me to continue to create!