现代密码学基础技能实验,感觉挺有意思的,就简单记录一下。

2022.4.8 20:00

验收成功,9.8分,应该是最高分吧,哈哈。

内容和要求

  1. 真实网络环境实现任意类型文件的远程传输。
  2. 采用socket进行网络传输。
  3. 支持任意类型的文件。
  4. 能够实现文件的正确加解密。
  5. 相同文件每次发送的加密文件不同。

一些原理

Socket

TCP/IP UDP

  • TCP/IP:传输控制协议/网间协议,是为广域网设计的。
  • UDP:用户数据报协议,与 TCP 相对应。

1

Socket

Socket 是应用层与 TCP/IP 协议族通信的中间软件抽象层,是一组接口。

它把复杂的 TCP/IP 协议族隐藏在 Socket 接口后面,让 Socket 去组织数据,以符合指定的协议。

2

交互

服务器端初始化 Socket,与端口绑定 bind,对端口监听 listen,调用 accept 阻塞,等待客户端连接。若有个客户端初始化一个 Socket,连接服务器 connect,若成功就建立了连接。客户端发送数据请求,服务器端接收数据请求并处理,然后向客户端发送回应数据,客户端读取数据,最后关闭连接,一次交互结束。

3

DH 密钥交换

理论依据:离散对数难解问题。

Alice 和 Bob需要共享一个对称密码的密钥,而不安全信道被 Eve 窃听。

Alice 和 Bob可以通过 DH 密钥交换,生成共享密钥。

  1. Alice 向 Bob 发送两个质数 P、G。
    • P 必须是大质数,G 是和 P 相关的数,称为生成元。
    • P 和 G 不需要保密,被窃听者窃听也没关系。
  2. Alice 生成随机数 A。
    • A 是一个1~P-2之间的整数,只有 Alice 知道。
  3. Alice 生成随机数 B。
    • B 是一个1~P-2之间的整数,只有 Bob 知道。
  4. Alice 将 GAmodPG^{A}modP 发给 Bob。
    • 被窃听者窃听也没关系。
  5. Bob 将 GBmodPG^{B}modP 发给 Alice。
    • 被窃听者窃听也没关系。
  6. 通信双方计算密钥,得到相等的共享密钥。
    • Alice:(GBmodP)AmodP=GABmodP(G^BmodP)^AmodP=G^{AB}modP
    • Bob:(GAmodP)BmodP=GABmodP(G^AmodP)^BmodP=G^{AB}modP

密钥交换/协商机制、迪菲-赫尔曼(DH)密钥交换

  1. 甲方构建密钥对,公钥公布给乙方,私钥保留。双方约定数据加密算法。乙方通过甲方公钥构建密钥对,公钥公布给甲方,私钥保留。
  2. 甲方通过私钥和乙方公钥,用约定数据加密算法构建本地密钥,用本地密钥加密数据,发送给乙方。乙方使用私钥、甲方公钥、约定数据加密算法构建本地密钥,然后通过本地密钥对数据解密。
  3. 乙方通过私钥和甲方公钥,用约定数据加密算法构建本地密钥,用本地密钥加密数据,发送给甲方。甲方使用私钥、乙方公钥、约定数据加密算法构建本地密钥,然后通过本地密钥对数据解密。

4

5

6

实验开发

环境配置

eclipse上的Java开发。

文件结构与效果图

文件结构

7

效果图

主界面

8

文件选择

9

功能实现

  • 多线程提高程序效率。
  • 文件选择的可视化操作。
  • 时间戳+名称+扩展名的方式实现同一文件,传输不可覆盖。
  • 传输文件扩展名实现任意类型的文件传输过程中不改变文件类型。
  • 通过DH密钥协商AES密钥,实现文件的加解密。

服务端 Server

  1. 随机生成服务器端的公私钥对。
  2. 创建一个服务端 Socket 对象,和系统要指定的端口号。
  3. 使用 accept 方法获取到请求的客户端 Socket 对象。
  4. 创建 DataInputStream 对象,将服务器端的公钥发给客户端。
  5. 创建 DataOutputStream 对象,接收来自客户端的公钥。
  6. 通过客户端的公钥和服务器端的私钥生成本地密钥。
  7. 读取客户端发来的文件名。
  8. 创建 FileOutputStream 对象,保存客户端发来的加密文件。
  9. 写回上传成功。
  10. 对加密文件进行解密。
  11. 释放资源。

code

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
package FileUpload;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import dh.AESFileUtil;
import dh.DHCoder;

public class TCPServer {

//甲方公钥
private static byte[] publicKey1 ;
//乙方公钥
private static byte[] publicKey2;
//甲方私钥
private static byte[] privateKey1;
//甲方本地密钥
private static byte[] key1;

//生成甲方密钥对
private void initKey() throws Exception{
Map<String, Object> keyMap1 = DHCoder.initKey();
publicKey1 = DHCoder.getPublicKey(keyMap1);
privateKey1 = DHCoder.getPrivateKey(keyMap1);
System.out.println("甲方公钥:\n" + Base64.encodeBase64String(publicKey1));
System.out.println("甲方私钥:\n" + Base64.encodeBase64String(privateKey1));
}
//生成甲方本地密钥
private void initMyKey()
{
try {
key1 = DHCoder.getSecretKey(publicKey2, privateKey1);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("甲方本地密钥:\n" + Base64.encodeBase64String(key1));
}
public TCPServer() throws IOException
{
try {
initKey();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
init();
}
private void init() throws IOException
{
//系统指定的端口号
final int PORT = 8888;
//接收文件的相对路径
final String receive_file_path = "ReceiveFile";
ServerSocket server = new ServerSocket(PORT);
while(true)
{
Socket socket = server.accept();
System.out.println("来访客户信息:\n" + "客户端IP:" + socket.getInetAddress()
+ " 客户端端口:" + socket.getInetAddress().getLocalHost() + "已连接服务器");
//将公钥发给客户端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(publicKey1.length);
dos.write(publicKey1);
//接收客户端的公钥
DataInputStream dis=new DataInputStream(socket.getInputStream());
publicKey2 = new byte[dis.readInt()];
dis.readFully(publicKey2);
System.out.println("乙方公钥:\n" + Base64.encodeBase64String(publicKey2));
initMyKey();
//读取文件名
String receive_file_name = dis.readUTF();
new Thread(new Runnable()
{
@Override
public void run()
{
try
{
//构造待解密文件存放的路径
InputStream is = socket.getInputStream();
String decode_file_path=receive_file_path+File.separator+"decode"+receive_file_name;
//若路径不存在,创建文件夹
File file = new File(receive_file_path);
if(!file.exists())
{
file.mkdir();
}
//添加时间戳和扩展名
SimpleDateFormat df = new SimpleDateFormat("yyyyMMddHHmmss");
String receive_file_path = file + File.separator + df.format(new Date()) + receive_file_name;
System.out.println("receive_file_name:" + receive_file_name);
System.out.println("receive_file_path:" + receive_file_path);
//接收待解密文件
FileOutputStream fos = new FileOutputStream(decode_file_path);
int len = 0;
byte[] bytes = new byte[1024];
while((len = is.read(bytes)) != -1)
{
fos.write(bytes,0,len);
}
//告诉客户端上传成功
socket.getOutputStream().write("上传成功".getBytes());
//解密
AESFileUtil.decryptFile( decode_file_path,receive_file_path, Base64.encodeBase64String(key1));
//资源释放
fos.close();
socket.close();
}
catch(IOException e)
{
e.printStackTrace();
}
}
}).start();
}
}

public static void main(String[] args) throws IOException
{
TCPServer server = new TCPServer();
}
}

客户端 Client

  1. 创建一个客户端 Socket 对象,构造方法中绑定服务器的 IP 地址和端口号。
  2. 接收服务端发来的公钥,生成客户端的公私钥对和本地密钥。
  3. 将公钥发送给服务端。
  4. 将文件名发送给服务器端。
  5. 对待发送文件进行加密,发送加密后的文件。
  6. 读取服务器返回的数据。
  7. 释放资源。

Code

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
package FileUpload;

import java.io.*;
import java.net.Socket;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import dh.AESFileUtil;
import dh.DHCoder;

public class TCPClient {
//乙方公钥
private static byte[] publicKey2;
//乙方私钥
private static byte[] privateKey2;
//乙方密钥
private static byte[] key2;
//甲方公钥
private static byte[] publicKey1;

public TCPClient(String IP,int PORT,String send_file_path,String send_file_name) throws IOException
{
init(IP,PORT,send_file_path,send_file_name);
}
//生成密钥
private void initMyKey()
{
try {
Map<String, Object> keyMap2 = DHCoder.initKey(publicKey1);
publicKey2 = DHCoder.getPublicKey(keyMap2);
privateKey2 = DHCoder.getPrivateKey(keyMap2);
key2 = DHCoder.getSecretKey(publicKey1, privateKey2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("乙方公钥:\n" + Base64.encodeBase64String(publicKey2));
System.out.println("乙方私钥:\n" + Base64.encodeBase64String(privateKey2));
System.out.println("乙方本地密钥:\n" + Base64.encodeBase64String(key2));
}
private void init(String IP,int PORT,String send_file_path,String send_file_name) throws IOException
{
Socket socket = new Socket(IP,PORT);
OutputStream os = socket.getOutputStream();
InputStream is = socket.getInputStream();

//接收服务器端的公钥
DataInputStream dis=new DataInputStream(socket.getInputStream());
publicKey1 = new byte[dis.readInt()];
dis.readFully(publicKey1);
System.out.println("甲方公钥:\n" + Base64.encodeBase64String(publicKey1));
//生成密钥
initMyKey();
try {
key2 = DHCoder.getSecretKey(publicKey1, privateKey2);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("乙方本地密钥:\n" + Base64.encodeBase64String(key2));
//将公钥发给服务器端
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt(publicKey2.length);
dos.write(publicKey2);
//发送文件名
dos.writeUTF(send_file_name);
System.out.println("send_file_name:"+send_file_name);
//加密文件的存储路径
File file = new File(send_file_path);
String encode_file_path=String.valueOf(file.getParent())+File.separator+"encode"+send_file_name;
System.out.println(encode_file_path);
//加密
AESFileUtil.encryptFile(send_file_path, encode_file_path, Base64.encodeBase64String(key2));
//发送加密文件
FileInputStream fis = new FileInputStream(encode_file_path);
int len=0;
byte[] bytes = new byte[1024];
while((len = fis.read(bytes))!=-1)
{
os.write(bytes,0,len);
}
//告诉服务器已发送完毕
socket.shutdownOutput();
//读取服务器返回的数据
while((len = is.read(bytes)) != -1)
{
System.out.println(new String(bytes,0,len));
}
//释放资源
fis.close();
socket.close();
}
public static void main(String[] args) throws IOException
{
TCPClient client = new TCPClient("127.0.0.1",8888,"sendFile/ttt.png","ttt.png");
}
}


公私钥和本地密钥的生成

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
package dh;  

import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import javax.crypto.KeyAgreement;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;

public abstract class DHCoder {
//非对称加密算法
private static final String KEY_ALGORITHM = "DH";
//本地密钥算法,即对称加密算法
private static final String SELECT_ALGORITHM = "AES";
//密钥长度
private static final int KEY_SIZE = 512;
//公钥
private static final String PUBLIC_KEY = "DHPublicKey";
//私钥
private static final String PRIVATE_KEY = "DHPrivateKey";

//初始化甲方密钥
public static Map<String, Object> initKey() throws Exception{
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
//初始化密钥对生成器
keyPairGenerator.initialize(KEY_SIZE);
//生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//甲方公钥
DHPublicKey publicKey = (DHPublicKey)keyPair.getPublic();
//甲方私钥
DHPrivateKey privateKey = (DHPrivateKey)keyPair.getPrivate();
//将密钥对存储在Map中
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
//初始化乙方密钥
public static Map<String, Object> initKey(byte[] key) throws Exception{
//解析甲方公钥转换公钥材料
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//由甲方公钥构建乙方密钥
DHParameterSpec dhParameterSpec = ((DHPublicKey)pubKey).getParams();
//实例化密钥对生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
//初始化密钥对生成器
keyPairGenerator.initialize(KEY_SIZE);
//产生密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//乙方公钥
DHPublicKey publicKey = (DHPublicKey) keyPair.getPublic();
//乙方私约
DHPrivateKey privateKey = (DHPrivateKey) keyPair.getPrivate();
//将密钥对存储在Map中
Map<String, Object> keyMap = new HashMap<String, Object>(2);
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
//构建密钥
public static byte[] getSecretKey(byte[] publicKey, byte[] privateKey) throws Exception{
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(publicKey);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//初始化私钥
//密钥材料转换
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(privateKey);
//产生私钥
PrivateKey priKey = keyFactory.generatePrivate(pkcs8KeySpec);
//实例化
KeyAgreement keyAgree = KeyAgreement.getInstance(keyFactory.getAlgorithm());
//初始化
keyAgree.init(priKey);
keyAgree.doPhase(pubKey, true);
//生成本地密钥
SecretKey secretKey = keyAgree.generateSecret(SELECT_ALGORITHM);
return secretKey.getEncoded();
}
//取私钥
public static byte[] getPrivateKey(Map<String, Object> keyMap) throws Exception{
Key key = (Key) keyMap.get(PRIVATE_KEY);
return key.getEncoded();
}
//取公钥
public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception{
Key key = (Key) keyMap.get(PUBLIC_KEY);
return key.getEncoded();
}
}

文件加解密

Java AES文件加密

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
package dh;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

public class AESFileUtil {

private static final String key = "k+mmgEfezd8RNdmqmt9SfsV866Cjt0fADyDzZgolBGA=";

public static Cipher initAESCipher(String passsword, int cipherMode) {
Cipher cipher = null;
try {
SecretKey key = getKey(passsword);
cipher = Cipher.getInstance("AES/ECB/PKCS7Padding");
cipher.init(cipherMode, key);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
return cipher;
}

private static SecretKey getKey(String password) {
int keyLength = 256;
byte[] keyBytes = new byte[keyLength / 8];
SecretKeySpec key = null;
try {
Arrays.fill(keyBytes, (byte) 0x0);
Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
byte[] passwordBytes = password.getBytes("UTF-8");
int length = passwordBytes.length < keyBytes.length ? passwordBytes.length : keyBytes.length;
System.arraycopy(passwordBytes, 0, keyBytes, 0, length);

key = new SecretKeySpec(keyBytes, "AES");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return key;
}
//AES 加密
public static boolean encryptFile(String encryptPath, String decryptPath, String sKey){
File encryptFile = null;
File decryptfile = null;
CipherOutputStream cipherOutputStream = null;
BufferedInputStream bufferedInputStream = null;
try {
encryptFile = new File(encryptPath);
if(!encryptFile.exists()) {
throw new NullPointerException("Encrypt file is empty");
}
decryptfile = new File(decryptPath);
if(decryptfile.exists()) {
decryptfile.delete();
}
decryptfile.createNewFile();

Cipher cipher = initAESCipher(sKey, Cipher.ENCRYPT_MODE);
cipherOutputStream = new CipherOutputStream(new FileOutputStream(decryptfile), cipher);
bufferedInputStream = new BufferedInputStream(new FileInputStream(encryptFile));

byte[] buffer = new byte[1024];
int bufferLength;

while ((bufferLength = bufferedInputStream.read(buffer)) != -1) {
cipherOutputStream.write(buffer, 0, bufferLength);
}
bufferedInputStream.close();
cipherOutputStream.close();
//delFile(encryptPath);
} catch (IOException e) {
delFile(decryptfile.getAbsolutePath());
e.printStackTrace();
return false;
}
return true;
}
//AES 解密
public static boolean decryptFile(String encryptPath, String decryptPath, String mKey){
File encryptFile = null;
File decryptFile = null;
BufferedOutputStream outputStream = null;
CipherInputStream inputStream = null;
try {
encryptFile = new File(encryptPath);
if(!encryptFile.exists()) {
throw new NullPointerException("Decrypt file is empty");
}
decryptFile = new File(decryptPath);
if(decryptFile.exists()) {
decryptFile.delete();
}
decryptFile.createNewFile();

Cipher cipher = initAESCipher(mKey, Cipher.DECRYPT_MODE);

outputStream = new BufferedOutputStream(new FileOutputStream(decryptFile));
inputStream = new CipherInputStream(new FileInputStream(encryptFile), cipher);

int bufferLength;
byte[] buffer = new byte[1024];

while ((bufferLength = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bufferLength);
}
inputStream.close();
outputStream.close();
//delFile(encryptPath);
} catch (IOException e) {
delFile(decryptFile.getAbsolutePath());
e.printStackTrace();
return false;
}
return true;
}
//删除文件
public static boolean delFile(String pathFile) {
boolean flag = false;
if(pathFile == null && pathFile.length() <= 0) {
throw new NullPointerException("文件不能为空");
}else {
File file = new File(pathFile);
// 路径为文件且不为空则进行删除
if (file.isFile() && file.exists()) {
file.delete();
flag = true;
}
}
return flag;
}
public static void main(String[] args) {
boolean flag = AESFileUtil.encryptFile
("SendFile/ttt.png", "SendFile/ttt.png"+"", key);
System.out.println(flag);
flag = AESFileUtil.decryptFile
( "SendFile/加密后.txt","SendFile/解密后.txt", key);
System.out.println(flag);
System.out.println(key);
}
}

写在最后

本来想写python来着,不过如果没考上研究生的话大概率是要靠Java吃饭 QAQ……

(os:没有对象的面向对象编程)

小写一下练练手好了,感觉是比 python 跑得快一点。写了一天,还是比较有收获的,对dh协议还有socket有了更深刻的理解,也遇到了一些奇奇怪怪的bug,不过最后都解决了。

orz orz orz

打完明天下午的4c训练赛,就又可以继续泡馆子的生活。