RSA加密Socket传输文件、签名

RSA加密Socket传输文件、签名

转自 CSDN /dyyaries

(一)

RSA加密分为公钥加密和私钥解密以及可能的数字签名。

公钥、私钥分居客户端和服务器端,分别用于加密和解密。同时,私钥还用于签名,公钥还用于验证签名。

解密加密用到JDK中java.security、javax.crypto两个包中相关的接口和类

1.生成密钥的代码

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);

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;
    }

数字签名是在客户端进行,所用的私钥不是和用于加密的公钥成对的。

也就是说这样的通信 要在客户端保存有 服务器端的公钥和本地的私钥,而服务器端应该保存有本地的私钥和客户端的公钥。

(二)

服务器端采用多线程方式并行处理传输请求以提高效率。

因为我想用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;
			}
		}

2.初始化解密、签名类实例

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();
			}
		}
		
	}

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);
				//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);
		
	}

(三)

接下来是客户端类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

2.发送文件

编写发送文件的方法,其中byte[] buf=new byte[117]的原因是RSA加密算法支持的最大字节数为117,而加密后变成128位,所以服务器端解密的时候可以使用128bytes的buf读取文件。

public boolean send(String filename){
		try {
			File f = new File(filename);
			fis = new FileInputStream(filename);
			byte[] buf = new byte[117];
			int available;
			socket = new Socket(server,port);
			os = socket.getOutputStream();
			DataOutputStream dos = new DataOutputStream(os);
			dos.writeUTF(f.getName()); //写入文件名
			dos.writeLong(f.length()); //写入文件大小
			while((available = fis.read(buf)) != -1){
				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];
					}
				}
				byte[] cipherText = coder.encrypt(availableBytes);
				byte[] signature = sign.getSignature(cipherText);
				os.write(signature);
				os.write(cipherText);
				//count += cipherText.length;
				//System.out.println("send "+count+" bytes");
			}
			socket.shutdownOutput();
			fis.close();
			return true;
		} catch (FileNotFoundException e) {
			log.error("文件"+filename+" 未找到");
		} catch (UnknownHostException e) {
			log.error("未知主机:"+server);
		} catch (IOException e) {
			log.error("文件读/写错误,可能是因为远程主机无响应");
		}
		return false;
	}

3.多线程

为了测试多线程处理效果,可以将客户端继承于Thread,在重写的run方法中调用 send(String file)方法

@Override
	public void run() {
		send(this.file);
	}

4.模拟多客户端

main中的测试可以这样来模拟多个客户端同时向服务器传输文件

File dir = new File("E://files//demo//");
		File[] files = dir.listFiles();
		
		for(int i=0; i<files.length; i++){
			Thread th = new Thread(new CoderClient(files[i].getAbsolutePath()));
			th.start();
		}

(四)

最后 用JavaServiceWrapper把服务器端程序做成windows服务自动运行。

可以到http://wrapper.tanukisoftware.com/doc/english/download.jsp这里下载到wrapper-windows-x86-32-3.2.3.zip

首先,在本地建立一个文件夹,其中建立bin、lib、conf和logs目录。

解压wrapper-windows-x86-32-3.2.3.zip到本地,暂且把解压后的文件夹叫做wrapper_home.

  • 1.将wrapper_home/bin/wrapper.exe拷贝至你的bin文件夹中,然后拷贝wrapper_home/src/bin中的App.bat.in、InstallApp-NT.bat.in和UninstallApp-NT.bat.in到
  • 你的bin文件夹并将.in后缀去掉。
  • 2.将wrapper_homw/lib/中的wrapper.dll和wrapper.jar拷贝至你的lib文件夹,并将你的可执行服务器端jar程序放到这里,暂且假定为server.jar.
  • 3.将wrapper_home/src/bin/conf中的wrapper.conf.in拷贝至你的conf文件夹并去掉.in后缀。
  • 4.修改wrapper.conf配置。要修改的地方我贴到下面列表:

# Java Application (和配置jdk环境变量类型)
wrapper.java.command=D:/installed app/Java/jdk1.6.0_13/bin/java
# Java Classpath (include wrapper.jar)  Add class path elements as (本来是指向 ../lib/testwrapper.jar的,改成你要启动的server.jar)
wrapper.java.classpath.1=../lib/server.jar
# Application parameters.  Add parameters as needed starting from 1 (这些是中要传入的参数,第一行是java程序入口,后面是main方法参数)
wrapper.app.parameter.1=com.vantasia.common.server.RSAServer
wrapper.app.parameter.2=rsa_private:d:/rsa_private.key
wrapper.app.parameter.3=rsa_public:d:/rsa1_public.key
wrapper.app.parameter.4=port:888
wrapper.app.parameter.5=file_dir:d:/files/
# 日志
wrapper.logfile.maxsize=3m      
#服务名 描述 等 
wrapper.ntservice.name=MyServer
wrapper.ntservice.displayname=MyServer
wrapper.ntservice.description=socket My server
最后到你的bin文件夹运行App.bat可以运行你的文件服务器。

服务器启动后 可以运行客户端 测试一下。。。

到此,一个简单的文件加密传输小例子就写好了。。。

谢谢大家~!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值