RSA加密Socket传输文件、签名
转自 CSDN /dyyaries
(一)
RSA加密分为公钥加密和私钥解密以及可能的数字签名。
公钥、私钥分居客户端和服务器端,分别用于加密和解密。同时,私钥还用于签名,公钥还用于验证签名。
解密加密用到JDK中Java.security、javax.crypto两个包中相关的接口和类
1.生成密钥的代码
- SecureRandom sr = new SecureRandom();
- KeyPairGenerator kg = KeyPairGenerator.getInstance(”RSA”);
-
- kg.initialize(1024, sr);
- KeyPair kp = kg.generateKeyPair();
-
- String privateKeyName = keyFile.substring(0,keyFile.lastIndexOf(“.”))+“_private”+keyFile.substring(keyFile.lastIndexOf(“.”));
- String publicKeyName = keyFile.substring(0,keyFile.lastIndexOf(“.”))+“_public”+keyFile.substring(keyFile.lastIndexOf(“.”));
- FileOutputStream fos = new FileOutputStream(privateKeyName);
- ObjectOutputStream oos = new ObjectOutputStream(fos);
-
- oos.writeObject(kp.getPrivate());
- oos.close();
-
-
- fos = new FileOutputStream(publicKeyName);
- oos = new ObjectOutputStream(fos);
- oos.writeObject(kp.getPublic());
- oos.close();
SecureRandom sr = new SecureRandom();
KeyPairGenerator kg = KeyPairGenerator.getInstance("RSA");
// 注意密钥大小最好为1024,否则解密会有乱码情况.
kg.initialize(1024, sr);
KeyPair kp = kg.generateKeyPair();
String privateKeyName = keyFile.substring(0,keyFile.lastIndexOf("."))+"_private"+keyFile.substring(keyFile.lastIndexOf("."));
String publicKeyName = keyFile.substring(0,keyFile.lastIndexOf("."))+"_public"+keyFile.substring(keyFile.lastIndexOf("."));
FileOutputStream fos = new FileOutputStream(privateKeyName);
ObjectOutputStream oos = new ObjectOutputStream(fos);
// 生成私钥
oos.writeObject(kp.getPrivate());
oos.close();
//生成公钥
fos = new FileOutputStream(publicKeyName);
oos = new ObjectOutputStream(fos);
oos.writeObject(kp.getPublic());
oos.close();
2.加密、解密
(RSA加密速度比较慢,原因是每次只能为最多117 bytes加密,加密之后为128 Bytes)
-
- Cipher cipher = Cipher.getInstance(”RSA/ECB/PKCS1Padding”);
- cipher.init(Cipher.ENCRYPT_MODE, (PublicKey)getKey(keyFile,”public”));
- return cipher.doFinal(text);
-
- Cipher cipher = Cipher.getInstance(”RSA/ECB/PKCS1Padding”);
- cipher.init(Cipher.DECRYPT_MODE, (PrivateKey)getKey(keyFile,”private”));
- return cipher.doFinal(text);
//加密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, (PublicKey)getKey(keyFile,"public"));
return cipher.doFinal(text);
//加密
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, (PrivateKey)getKey(keyFile,"private"));
return cipher.doFinal(text);
3.数字签名
用私钥进行数字签名,用于服务器端验证客户端数据是否是正确数据。
- public byte[] getSignature(byte[] cipherText){
- try {
- coder = new RSASecurityCoder();
- Signature sig = Signature.getInstance(”SHA1withRSA”);
- PrivateKey privateKey = (PrivateKey)coder.getKey(this.key, “private”);
- sig.initSign(privateKey);
- sig.update(cipherText);
- return sig.sign();
- } catch (Exception e) {
- e.printStackTrace();
- }
- return null;
- }
-
- public boolean verifySignature(byte[] cipherText,byte[] signature){
- try {
- coder = new RSASecurityCoder();
- Signature sig = Signature.getInstance(”SHA1withRSA”);
- PublicKey publicKey = (PublicKey)coder.getKey(this.key, “public”);
- sig.initVerify(publicKey);
- sig.update(cipherText);
- return sig.verify(signature);
- } catch (Exception e) {
- e.printStackTrace();
- }
- return false;
- }
public byte[] getSignature(byte[] cipherText){
try {
coder = new RSASecurityCoder();
Signature sig = Signature.getInstance("SHA1withRSA");
PrivateKey privateKey = (PrivateKey)coder.getKey(this.key, "private");
sig.initSign(privateKey);
sig.update(cipherText);
return sig.sign();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public boolean verifySignature(byte[] cipherText,byte[] signature){
try {
coder = new RSASecurityCoder();
Signature sig = Signature.getInstance("SHA1withRSA");
PublicKey publicKey = (PublicKey)coder.getKey(this.key, "public");
sig.initVerify(publicKey);
sig.update(cipherText);
return sig.verify(signature);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
数字签名是在客户端进行,所用的私钥不是和用于加密的公钥成对的。
也就是说这样的通信 要在客户端保存有 服务器端的公钥和本地的私钥,而服务器端应该保存有本地的私钥和客户端的公钥。
(二)
服务器端采用多线程方式并行处理传输请求以提高效率。
因为我想用JavaServiceWrapper将 程序作为windows服务运行,所以main方法中需接收一些参数,比如端口、密钥文件位置、文件存放路径等。
1.从main方法接受配置参数
- for(String arg : args){
- String argName = arg.substring(0,arg.indexOf(“:”)).trim();
- String argValue = arg.substring(arg.indexOf(”:”)+1).trim();
- if(argName.equals(“rsa_private”)){
- privateKey = argValue;
- }else if(argName.equals(“rsa_public”)){
- publicKey = argValue;
- }else if(argName.equals(“port”)){
- port = Integer.parseInt(argValue);
- }else if(argName.equals(“file_dir”)){
- file_dir = argValue;
- }
- }
for(String arg : args){
String argName = arg.substring(0,arg.indexOf(":")).trim();
String argValue = arg.substring(arg.indexOf(":")+1).trim();
if(argName.equals("rsa_private")){
privateKey = argValue;
}else if(argName.equals("rsa_public")){
publicKey = argValue;
}else if(argName.equals("port")){
port = Integer.parseInt(argValue);
}else if(argName.equals("file_dir")){
file_dir = argValue;
}
}
2.初始化解密、签名类实例
- RSASecurityCoder coder = new RSASecurityCoder(privateKey);
- RSASecuritySignature sign = new RSASecuritySignature(publicKey);
RSASecurityCoder coder = new RSASecurityCoder(privateKey);
RSASecuritySignature sign = new RSASecuritySignature(publicKey);
3.建立服务器
- private static void runServer(String[] args){
- long start = System.currentTimeMillis();
-
- for(String arg : args){
- String argName = arg.substring(0,arg.indexOf(“:”)).trim();
- String argValue = arg.substring(arg.indexOf(”:”)+1).trim();
- if(argName.equals(“rsa_private”)){
- privateKey = argValue;
- }else if(argName.equals(“rsa_public”)){
- publicKey = argValue;
- }else if(argName.equals(“port”)){
- port = Integer.parseInt(argValue);
- }else if(argName.equals(“file_dir”)){
- file_dir = argValue;
- }
- }
-
-
- coder = new RSASecurityCoder(privateKey);
- sign = new RSASecuritySignature(publicKey);
-
- System.out.println(”服务器启动中…”);
- try {
- ss = new ServerSocket(port);
- clientCount = 0;
- } catch (NumberFormatException e) {
- System.err.println(”端口配置错误”);
- } catch (IOException e) {
- System.err.println(”服务器在端口”+port+“启动失败”);
- }
-
-
- dir = new File(file_dir);
- if(!dir.exists()){
- dir.mkdir();
- }
- System.out.println(”服务器已启动,端口: ”+port);
-
- long end = System.currentTimeMillis();
- System.out.println(”共消耗 ”+(end-start)+“ ms.”);
-
-
- while(true){
- try {
- if(ss == null){
- ss = new ServerSocket(port);
- clientCount = 0;
- }
- Socket socket = ss.accept();
- clientCount += 1;
- System.out.println(”有新的连接,当前总连接数:”+clientCount);
- Thread th = new Thread(new ServerProcessor(socket));
- th.start();
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
-
- }

private static void runServer(String[] args){
long start = System.currentTimeMillis();
//获取配置参数
for(String arg : args){
String argName = arg.substring(0,arg.indexOf(":")).trim();
String argValue = arg.substring(arg.indexOf(":")+1).trim();
if(argName.equals("rsa_private")){
privateKey = argValue;
}else if(argName.equals("rsa_public")){
publicKey = argValue;
}else if(argName.equals("port")){
port = Integer.parseInt(argValue);
}else if(argName.equals("file_dir")){
file_dir = argValue;
}
}
//配置
coder = new RSASecurityCoder(privateKey);
sign = new RSASecuritySignature(publicKey);
//启动服务器
System.out.println("服务器启动中...");
try {
ss = new ServerSocket(port);
clientCount = 0;
} catch (NumberFormatException e) {
System.err.println("端口配置错误");
} catch (IOException e) {
System.err.println("服务器在端口"+port+"启动失败");
}
//文件存放目录
dir = new File(file_dir);
if(!dir.exists()){
dir.mkdir();
}
System.out.println("服务器已启动,端口: "+port);
//计算启动时间
long end = System.currentTimeMillis();
System.out.println("共消耗 "+(end-start)+" ms.");
//接收数据
while(true){
try {
if(ss == null){
ss = new ServerSocket(port);
clientCount = 0;
}
Socket socket = ss.accept();
clientCount += 1;
System.out.println("有新的连接,当前总连接数:"+clientCount);
Thread th = new Thread(new ServerProcessor(socket));
th.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
4.建立线程类ServerProcessor处理每个文件
- static class ServerProcessor extends Thread{
- private Socket socket;
- private InputStream in;
- private String filename;
- private long totalBytes;
- private File file;
- private FileOutputStream fos;
- public ServerProcessor(Socket socket){
- this.socket = socket;
- }
- @Override
- public void run(){
- try {
- in = socket.getInputStream();
- DataInputStream dis = new DataInputStream(in);
- filename = dis.readUTF();
- totalBytes = dis.readLong();
- file = new File(dir,filename);
- fos = new FileOutputStream(file);
-
-
-
-
- byte[] buf = new byte[128];
- int available;
- while((available = in.read(buf)) != -1){
-
- byte[] signature = buf;
-
- buf = new byte[128];
- available = in.read(buf);
- byte[] availableBytes = null;
- if(available == buf.length){
- availableBytes = buf;
- }else{
- availableBytes = new byte[available];
- for (int i = 0; i < available; i++) {
- availableBytes[i] = (Byte) buf[i];
- }
- }
-
-
- boolean flag = sign.verifySignature(availableBytes, signature);
-
- if(flag){
-
- fos.write(coder.decrypt(availableBytes));
-
- }
- }
- fos.close();
- socket.shutdownInput();
- clientCount -= 1;
- System.out.println(”有连接断开,当前总连接数:”+clientCount);
- System.out.println(new SimpleDateFormat(“[yyyy-MM-DD HH:mm:ss ]”).format(new Date())
- +filename+” 接收完毕. 大小 ”+(totalBytes/1024)+“ kb,保存路径:”+dir.getAbsolutePath());
- } catch (NumberFormatException e) {
- System.err.println(”端口配置错误”);
- e.printStackTrace();
- } catch (FileNotFoundException e) {
- System.err.println(”文件没找到,文件名:”+filename+“,可能是因为读取socket数据失败”);
- e.printStackTrace();
- } catch (IOException e) {
- System.err.println(”文件读/写错误”);
- try {
- fos.close();
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- file.delete();
- e.printStackTrace();
- }
- }
- }

static class ServerProcessor extends Thread{
private Socket socket;
private InputStream in;
private String filename;
private long totalBytes;
private File file;
private FileOutputStream fos;
public ServerProcessor(Socket socket){
this.socket = socket;
}
@Override
public void run(){
try {
in = socket.getInputStream();
DataInputStream dis = new DataInputStream(in);
filename = dis.readUTF(); //读取文件名
totalBytes = dis.readLong(); //读取文件大小
file = new File(dir,filename);
fos = new FileOutputStream(file);
//FileOutputStream fos = new FileOutputStream(File.createTempFile(filename, null, dir));
//输出日志
//RSA加密以128 bytes位单位,一次最多加密117bytes.
byte[] buf = new byte[128];
int available;
while((available = in.read(buf)) != -1){
//读取签名
byte[] signature = buf;
//读取数据
buf = new byte[128];
available = in.read(buf);
byte[] availableBytes = null;
if(available == buf.length){
availableBytes = buf;
}else{
availableBytes = new byte[available];
for (int i = 0; i < available; i++) {
availableBytes[i] = (Byte) buf[i];
}
}
//验证数据签名
boolean flag = sign.verifySignature(availableBytes, signature);
//写入数据
if(flag){
//count += availableBytes.length;
fos.write(coder.decrypt(availableBytes));
//System.out.println(""+count+" bytes received");
}
}
fos.close();
socket.shutdownInput();
clientCount -= 1;
System.out.println("有连接断开,当前总连接数:"+clientCount);
System.out.println(new SimpleDateFormat("[yyyy-MM-DD HH:mm:ss ]").format(new Date())
+filename+" 接收完毕. 大小 "+(totalBytes/1024)+" kb,保存路径:"+dir.getAbsolutePath());
} catch (NumberFormatException e) {
System.err.println("端口配置错误");
e.printStackTrace();
} catch (FileNotFoundException e) {
System.err.println("文件没找到,文件名:"+filename+",可能是因为读取socket数据失败");
e.printStackTrace();
} catch (IOException e) {
System.err.println("文件读/写错误");
try {
fos.close();
} catch (IOException e1) {
e1.printStackTrace();
}
file.delete();
e.printStackTrace();
}
}
}
main方法
- public static void main(String[] args) {
-
- runServer(args);
-
- }
public static void main(String[] args) {
runServer(args);
}
(三)
接下来是客户端类CoderClient的编写。
1.配置信息
首先配置如下信息,包括服务器地址、端口、密钥文件位置.
conf.properties
- #configure private key on server
- rsa_private=d:/rsa1_private.key
- #public key from client
- rsa_public=d:/rsa_public.key
- #server
- server=127.0.0.1
- #server port
- port=888
#configure private key on server
rsa_private=d:/rsa1_private.key
#public key from client
rsa_public=d:/rsa_public.key
#server
server=127.0.0.1
#server port
port=888
可以到http://wrapper.tanukisoftware.com/doc/english/download.jsp这里下载到wrapper-windows-x86-32-3.2.3.zip
解压wrapper-windows-x86-32-3.2.3.zip到本地,暂且把解压后的文件夹叫做wrapper_home.