文章目录
在上学期,自己写了一个丑丑的加密即时通信软件作为网络安全课设。
能力有限,这个即时通信只限于局域网。
知识内容自行百度。

客户端
Activity
BaseEventActivity
package com.example.socketclient.Activity;
import android.os.Bundle;
import android.view.Window;
import androidx.appcompat.app.AppCompatActivity;
import org.greenrobot.eventbus.EventBus;
import butterknife.ButterKnife;
public abstract class BaseEventActivity extends AppCompatActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
getIntentData();
setContentView(getLayoutResId());
ButterKnife.bind(this);// 相当于 activity.subtitle = (TextView) activity.findViewById(R.id.subtitle);
EventBus.getDefault().register(this);//注册订阅者
init();
}
protected void getIntentData() {
}
@Override
protected void onDestroy() {
super.onDestroy();
EventBus.getDefault().unregister(this);
}
protected abstract void init();
protected abstract int getLayoutResId();
}
FriendActivity
package com.example.socketclient.Activity;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Toast;
import com.example.socketclient.R;
import com.example.socketclient.SQL.SQLiteHelper;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.ListItemAdapterUtil;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.SRConstantsUtil;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Map;
import javax.security.auth.Destroyable;
import butterknife.BindView;
import butterknife.OnClick;
public class FriendListActivity extends BaseEventActivity {
@BindView(R.id.listview)
ListView listView;
@BindView(R.id.edt_friendAccount)
EditText edt_friendAccount;
private ProgressDialog mProgressDialog;//加载的小菊花
//定义listView
private ListItemAdapterUtil listItemAdapterUtil ;
private String serverIp=null;
private String username = null;
private Socket socket = null;
//定义线程
sendConnectPacketThread sendcthread = new sendConnectPacketThread();
//定义线程
recPacketThread recpthread = new recPacketThread();
@Override
protected int getLayoutResId() {
return R.layout.activity_friend_list;
}
@Override
protected void init() {
Bundle bundle = this.getIntent().getExtras();
serverIp = bundle.getString(ConstantUtil.SERVERIP);
username = bundle.getString(ConstantUtil.USERNAME);
MySocketApplication app = (MySocketApplication) getApplicationContext();//获取app
socket = app.getSocket();//获取socket
//初始化列表
listItemAdapterUtil = new ListItemAdapterUtil(FriendListActivity.this,new String[]{"account"},
R.layout.friend_item,new int[]{R.id.friend_item_title});
getPrivateKey();
showFriendList();//好友列表
//启动 接收线程
recpthread.start();
//启动 发送连接包 线程
sendcthread = new sendConnectPacketThread();
String data = "{cmd:connect}{username:"+username+"}";
sendcthread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
//sendcthread.start();
//表项监听事件
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Map<String,Object> map = (Map <String,Object>)parent.getItemAtPosition(position);
Toast.makeText(FriendListActivity.this, "好友 : "+map.get("account").toString(), Toast.LENGTH_SHORT).show();
Bundle bundle = new Bundle();
bundle.putString(ConstantUtil.FRIENDACCOUNT,map.get("account").toString());
bundle.putString(ConstantUtil.SERVERIP,serverIp);
bundle.putString(ConstantUtil.USERNAME,username);
Intent intent = new Intent(FriendListActivity.this,Function_Socket_Client.class);
intent.putExtras(bundle);
startActivity(intent);
}
});
}
private void getPrivateKey() {
//查询数据库中有无自己的 信息,没有添加信息,获取私钥,显示列表
SQLiteDatabase db;
ContentValues values;
SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
db = helper.getReadableDatabase();
Cursor cursor = db.rawQuery("select * from keyInformation where USERNAME=? ",
new String[]{username});
if (cursor.getCount() == 0) {
//无私钥就获取私钥
String data = "{cmd:getPrivateKey}"+"{username:"+username+"}";
sendDataPacketThread dataPacketThread = new sendDataPacketThread();
dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
dataPacketThread.start();
//记得处理返回的信息,273行以后
}
cursor.close();
db.close();
}
private void showFriendList() {
//先移除所有好友项
listItemAdapterUtil.deleteItem();
//根据数据库添加好友项
SQLiteDatabase db;
ContentValues values;
SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
db = helper.getReadableDatabase();
//Cursor cursor = db.rawQuery("select * from keyInformation ",null);
Cursor cursor = db.query("keyInformation",
null,null,null,null,null,null);
if (cursor.getCount() != 0 ) {
cursor.moveToFirst();
//把第一个数据加上,即好友列表中有自己,自己可以和自己通讯
//去掉下面这句话就可以不加上自己
//listItemAdapterUtil.addItem(cursor.getString(1));
while (cursor.moveToNext())
listItemAdapterUtil.addItem(cursor.getString(1));
listView.setAdapter(listItemAdapterUtil.getAdapter());
}
}
private class recPacketThread extends Thread {
public void run(){
if (socket != null && socket.isConnected()) {
Log.d("Function_Socket_Client", "开启接收线程");
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = "";
while (true) {
try {
if ((msg = br.readLine()).equals("")) ;
else {
Log.d("Function_Socket_Client", "接收到服务器数据(未解密):" + msg);
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("Function_Socket_Client", "接收到服务器数据(已解密):" + msg);
EventBus.getDefault().post(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
if(socket.isClosed())break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private class sendConnectPacketThread extends Thread {
private String data;
public void setData(String data) {
this.data = data;
}
public void run(){
if (socket != null && socket.isConnected()) {
Log.d("Function_Socket_Client", "线程在线");
//一开始延时5S执行
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
//连接失败,退出线程
if(socket.isClosed()){
Log.d("Function_Socket_Client","该连接已终止!!!!!");
break;
}
//发送数据
try {
OutputStream ops = socket.getOutputStream();//得到socket输入流
OutputStreamWriter opsw = new OutputStreamWriter(ops);//
BufferedWriter writer = new BufferedWriter(opsw);
if (this.data != null){
writer.write(this.data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
//延时5S
try {
sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
private class sendDataPacketThread extends Thread {
private String data;
public void setData(String data) {
this.data = data;
}
public void run(){
if (socket != null && socket.isConnected()) {
Log.d("Function_Socket_Client", "发送数据");
try {
OutputStream ops = socket.getOutputStream();//得到socket输入流
OutputStreamWriter opsw = new OutputStreamWriter(ops);//
BufferedWriter writer = new BufferedWriter(opsw);
if (this.data != null){
writer.write(this.data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
writer.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@OnClick({R.id.btn_addFriend})
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_addFriend:
String friendAccount = edt_friendAccount.getText().toString();
//判断是否时自己
if(friendAccount.equals(username)){
Toast.makeText(this, "请不要犯如此简单的错误!!!", Toast.LENGTH_SHORT).show();
return;
}
if(isRepeatFriend(friendAccount)){
Toast.makeText(this, "已拥有该好友。", Toast.LENGTH_SHORT).show();
return;
}
//数据不重复,发送添加好友
Log.d("FriendListActivity","sendAddFriendRequest");
String data = "{cmd:addFriend}{username:"+friendAccount+"}";
sendDataPacketThread dataPacketThread = new sendDataPacketThread();
dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
dataPacketThread.start();
break;
}
}
private boolean isRepeatFriend(String friendAccount) {
SQLiteDatabase db;
ContentValues values;
SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
db = helper.getReadableDatabase();
//Cursor cursor = db.rawQuery("select * from keyInformation ",null);
Cursor cursor = db.query("keyInformation",
null,null,null,null,null,null);
if (cursor.getCount() != 0 ) {
cursor.moveToFirst();
while (cursor.moveToNext()){
if(cursor.getString(1).equals(friendAccount)){
return true;
}
}
}
return false;
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void serverResult(String serverResponse) {
Log.i("FriendListActivity", "resultCode:" + serverResponse);
//长度大于120,非普通包,必定是密钥
if(serverResponse.length() > 120){
int responseBeginIndex1=serverResponse.indexOf(":")+1;
int responseEndIndex1 = serverResponse.indexOf("}");
String response=serverResponse.substring(responseBeginIndex1,responseEndIndex1);
Log.d("FriendListActivity"," bigPacketResponse: "+response);
//密钥
if(response.equals("sendPublicKey") ){
int keyBeginIndex = serverResponse.indexOf(":",responseEndIndex1) + 1;
int keyEndIndex = serverResponse.length()-1;
String key = serverResponse.substring(keyBeginIndex,keyEndIndex);
String friendAccount = edt_friendAccount.getText().toString();
storeKey(key,friendAccount,SRConstantsUtil.PublicKeyType);
showFriendList();
//showNewFriendItem();//更新好友列表
}
else if(response.equals("sendPrivateKey")){
int keyBeginIndex = serverResponse.indexOf(":",responseEndIndex1) + 1;
int keyEndIndex = serverResponse.length()-1;
String key = serverResponse.substring(keyBeginIndex,keyEndIndex);
storeKey(key,username,SRConstantsUtil.PrivateKeyType);
}
return;
}
//普通包
switch (serverResponse) {
case SRConstantsUtil.CONNECTSUCCESS:
Toast.makeText(FriendListActivity.this, "与服务器连接成功", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.EXISTAPPNORMAL:
Toast.makeText(FriendListActivity.this, "退出APP", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.NOFRIENDACCOUNT:
Toast.makeText(FriendListActivity.this, "无此好友", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.GETPUBLICKEYERROR:
Toast.makeText(FriendListActivity.this, "获取账号公钥失败", Toast.LENGTH_SHORT).show();
break;
default:
//Toast.makeText(FriendListActivity.this, "与服务器连接失败", Toast.LENGTH_SHORT).show();
break;
}
}
private void storeKey(String key,String account,int type) {
SQLiteDatabase db;
ContentValues values;
SQLiteHelper helper = new SQLiteHelper(FriendListActivity.this,username);
db = helper.getWritableDatabase();
values = new ContentValues();//创建Contentvalues对象
//好友公钥
if (SRConstantsUtil.PublicKeyType == type){
Log.d("FriendListActivity","存储公钥");
Log.d(account+"公钥:",key);
values.put("USERNAME", account);//将数据添加到ContentValues对象
values.put("USERKEY",key);
db.insert("keyInformation",null,values);
db.close();
}
//用户私钥
else{
Log.d("FriendListActivity","存储私钥");
Log.d(account+"私钥:",key);
values.put("USERNAME", account);//将数据添加到ContentValues对象
//私钥加密存储
values.put("USERKEY",AESUtil.encrypt(ConstantUtil.PRIVATEKEY_AESKey,key));
db.insert("keyInformation",null,values);
db.close();
}
}
@Override
public void finish() {
Log.d("FriendListActivity","finish()");
super.finish();
}
@Override
protected void onDestroy() {
String data = "{cmd:exitApp}"+"{username:"+username+"}";
sendDataPacketThread dataPacketThread = new sendDataPacketThread();
dataPacketThread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
dataPacketThread.start();
super.onDestroy();
}
@Override
protected void onRestart() {
super.onRestart();
//String data = "{cmd:connect}{username:"+username+"}";
//sendcthread.setData(AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
//sendcthread.start();
}
}
Funtion_Socket_Client
package com.example.socketclient.Activity;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import com.example.socketclient.R;
import com.example.socketclient.SQL.SQLiteHelper;
import com.example.socketclient.Thread.ClientSendDataThread;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MD5Util;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.RSAUtil;
import com.example.socketclient.Util.SRConstantsUtil;
import com.example.socketclient.Util.ToolUtil;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import butterknife.BindView;
import butterknife.OnClick;
/**
* 客户端界面
*/
public class Function_Socket_Client extends BaseEventActivity {
@BindView(R.id.edtTxt_Content)
EditText edtTxt_Content;
@BindView(R.id.tv_friendAccount)
TextView tv_friendAccount;
@BindView(R.id.tv_clientLocalAddress)
TextView tv_clientLocalAddress;
@BindView(R.id.tv_clientReceivedContent)
TextView tv_clientReceivedContent;
@BindView(R.id.tv_clientDecryptContent)
TextView tv_clientDecryptContent;
private ProgressDialog mProgressDialog;//加载的小菊花
private String serverIp=null;
private String username = null;
private Socket socket = null;
private String friendAccount = null;
/**
* 初始化
*/
@Override
protected void init() {
tv_clientLocalAddress.setText(ToolUtil.getHostIP());
Bundle bundle = this.getIntent().getExtras();
serverIp = bundle.getString(ConstantUtil.SERVERIP);
username = bundle.getString(ConstantUtil.USERNAME);
friendAccount = bundle.getString(ConstantUtil.FRIENDACCOUNT);
tv_friendAccount.append(friendAccount);
Log.d("Function_Socket_Client","host ip is "+serverIp+", username is "+username);
MySocketApplication app = (MySocketApplication) getApplicationContext();
socket = app.getSocket();
//定义线程
recPacketThread recpthread = new recPacketThread(socket);
recpthread.start();
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//connect();//创建时刻与服务器连接的进程
}
private void connect() {
new Thread(){
@Override
public void run()
{
super.run();
MySocketApplication app = (MySocketApplication) getApplicationContext();
socket = app.getSocket();
if (socket != null&&socket.isConnected()) {
Log.d("Function_Socket_Client","接受服务器信息中");
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = null;
while(true) {
try {
if ((msg = br.readLine())== null)break;
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("Function_Socket_Client","接收到服务器数据:"+msg);
EventBus.getDefault().post(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
@Override
protected int getLayoutResId() {
return R.layout.function_socket_client;
}
@RequiresApi(api = Build.VERSION_CODES.O)
@OnClick({R.id.btn_encryptAndSend})
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_encryptAndSend:
String text = edtTxt_Content.getText().toString().trim();
try {
SendMessage(text);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private void SendMessage(String text) throws Exception {
//封装发送的消息:时间+内容
String msg = getCurrentTime(2)+": "+text;
String digitalSignature = MD5Util.toMD5(msg);
msg = "{MD5ds:"+digitalSignature+"}"+msg;
//获取好友公钥
String publicKey = getKey(friendAccount);
//根据公钥获取密码种子,根据密码种子生成会话密钥,用密钥生成密文。
String keySeed = getSeedFromString(publicKey,30);
String cipherText =AESUtil.encrypt(keySeed,msg);
Log.d("Socket_ClientActivity","AES对称密钥加密后:"+cipherText);
//密文加上密码种子发送出去,以:作为分隔符
cipherText = cipherText +":"+keySeed;
//公钥加密
String S = RSAUtil.encrypt(cipherText,publicKey);
Log.d("Socket_ClientActivity","RSA公钥加密后:"+S);
String data = "{cmd:sendMsg}{receiverAccount:"+friendAccount+"}{message:"+S+"}{sender:"+username+"}";
new ClientSendDataThread(serverIp, AESUtil.encrypt(ConstantUtil.ServerAESKey, data), ConstantUtil.port).start();//消息发送方启动线程发送消息
showProgressDialog("尝试发送数据到\n\t\t" + friendAccount, true);
}
private String getSeedFromString(String publicKey,int length) {
String seed = "";
int keyLength = length;
int r = (int)(Math.random()*(publicKey.length()-keyLength));
System.out.println("random number: "+r);
seed = publicKey.substring(r,r+keyLength);
System.out.println("randomKey : "+seed);
return seed;
}
private String getKey(String account) {
String key ="";
SQLiteDatabase db;
ContentValues values;
SQLiteHelper helper = new SQLiteHelper(Function_Socket_Client.this,username);
db = helper.getReadableDatabase();
//执行查询的SQL语句
Cursor cursor = db.rawQuery("select * from keyInformation where USERNAME=? ",
new String[]{account});
if (cursor.getCount() != 0 ) {
cursor.moveToFirst();
key = cursor.getString(2);
}
cursor.close();
db.close();
Log.d("Function_Socket_Client","get key from sql:"+key);
if(account.equals(username))
return AESUtil.decrypt(ConstantUtil.PRIVATEKEY_AESKey,key);
return key;
}
private class recPacketThread extends Thread {
Socket socket;
recPacketThread(Socket socket){
this.socket = socket;
}
public void run(){
if (socket != null && socket.isConnected()) {
Log.d("Function_Socket_Client", "开启接收线程");
try {
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = "";
while (true) {
try {
if ((msg = br.readLine()).equals("")) ;
else {
Log.d("Function_Socket_Client", "接收到服务器数据(未解密):" + msg);
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("Function_Socket_Client", "接收到服务器数据(已解密):" + msg);
EventBus.getDefault().post(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
if(socket.isClosed())break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 连接结果
*
* @param serverResponse 0:连接超时;1:发送成功 2:失败
*/
@RequiresApi(api = Build.VERSION_CODES.O)
@Subscribe(threadMode = ThreadMode.MAIN)
public void sendResult(String serverResponse) throws Exception {
Log.i("Function_Socket_Client", "resultCode:" + serverResponse);
dismissProgressDialog();
//用户发送的密文数据
if(serverResponse.length()>120){
String M = DecryptReceiveMsg(serverResponse);
tv_clientDecryptContent.append(M+"\n");
}
//常规数据
else {
switch (serverResponse) {
case SRConstantsUtil.MSGSEND_SUCCESS:
Toast.makeText(this, "发送成功!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.MSGSEND_FAILED:
Toast.makeText(this, "发送失败,服务器无响应!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.MSGSEND_FAILED_NOT_EXIST:
Toast.makeText(this, "发送失败,接收者不存在!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.MSGSEND_FAILED_NOT_ONLINE:
Toast.makeText(this, "发送失败,接收者不在线!", Toast.LENGTH_SHORT).show();
break;
default:
Toast.makeText(this, "接收数据格式错误!", Toast.LENGTH_SHORT).show();
tv_clientReceivedContent.append(serverResponse+"\n");
//tv_clientDecryptContent.append(AESUtil.decrypt(ConstantUtil.password, resultCode)+"\n");
break;
}
}
}
@RequiresApi(api = Build.VERSION_CODES.O)
private String DecryptReceiveMsg(String serverResponse) throws Exception {
int colon1BeginIndex = serverResponse.indexOf(":");
int colon2BeginIndex = serverResponse.indexOf(":", colon1BeginIndex+1);
int colon3BeginIndex = serverResponse.indexOf(":", colon2BeginIndex+1);
String C =serverResponse.substring(colon3BeginIndex+1,serverResponse.length()-1);
System.out.println("split_msg,密文: "+C);
tv_clientReceivedContent.append(C+"\n\n");
String privateKey = getKey(username);
String M = RSAUtil.decrypt(C,privateKey);
System.out.println("RSA解密后的明文: "+M);
System.out.println("数据长度 : "+M.length());
int colonIndex = M.indexOf(":");
System.out.println("colonIndex长度 : "+colonIndex);
String msg = M.substring(0,colonIndex);
System.out.println("msg : "+msg);
String keySeed = M.substring(colonIndex+1);
System.out.println("keySeed:"+keySeed);
M = AESUtil.decrypt(keySeed,msg);
System.out.println("进行双密钥解密的明文是:"+M);
int md5Index = M.indexOf(":")+1;
String MD5DS = M.substring(md5Index,md5Index+32);
System.out.println("接收到的MD5数字签名:"+MD5DS);
int msgBeginIndex = M.indexOf("}")+1;
String recMsg = M.substring(msgBeginIndex);
System.out.println("接收到的消息:"+recMsg);
if(MD5Util.toMD5(recMsg).equals(MD5DS)){
System.out.println("数据完整.");
}
else {
System.out.println("数据不完整,请确认!");
Toast.makeText(Function_Socket_Client.this, "注意:接收的数据不完整。", Toast.LENGTH_SHORT).show();
}
return recMsg;
}
/**
* 数据加载小菊花
*
* @param msg 内容
* @param isCancel 是否允许关闭 true - 允许 false - 不允许
*/
public void showProgressDialog(final String msg, final boolean isCancel) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(Function_Socket_Client.this);
}
if (mProgressDialog.isShowing()) {
return;
}
mProgressDialog.setMessage(msg);
mProgressDialog.setCancelable(isCancel);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setOnCancelListener(null);
mProgressDialog.show();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 隐藏数据加载的进度小菊花
**/
public void dismissProgressDialog() {
try {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
mProgressDialog.dismiss();
}
}
);
}
} catch (Exception e) {
e.printStackTrace();
}
}
/*
获取当前时间
输入参数:type
0 返回年月日 时分秒
1 返回年月日
2 返回时分秒
*/
private String getCurrentTime(int type) {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss");
Date date = new Date(System.currentTimeMillis());
if(type == 0)
return simpleDateFormat.format(date);
if(type == 1)
return simpleDateFormat.format(date).substring(0,11);//截取第一个到第十一个字符
else
return simpleDateFormat.format(date).substring(12);
}
}
LoginActivity
package com.example.socketclient.Activity;
import android.app.ProgressDialog;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.example.socketclient.R;
import com.example.socketclient.Thread.ClientSendDataThread;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MySocketApplication;
import com.example.socketclient.Util.RSAUtil;
import com.example.socketclient.Util.SRConstantsUtil;
import com.example.socketclient.Util.ToolUtil;
import org.greenrobot.eventbus.EventBus;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Map;
import butterknife.BindView;
import butterknife.OnClick;
public class LoginActivity extends BaseEventActivity {
@BindView(R.id.et_username)
EditText et_username;
@BindView(R.id.et_password)
EditText et_password;
@BindView(R.id.et_serverIp)
TextView et_serverIp;
private String serverIp;
private String username= null;
private String password = null;
private ProgressDialog mProgressDialog;//加载的小菊花
private String localIp;
private Socket socket;
@Override
protected void init() {
localIp = ToolUtil.getHostIP();
}
@Override
protected int getLayoutResId() {
return R.layout.activity_login;
}
@OnClick({R.id.btn_regist,R.id.btn_login})
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_regist:
sendRegisterInfoToServer();
break;
case R.id.btn_login:
sendLoginInfoToServer();
break;
}
}
private void sendRegisterInfoToServer(){
username = et_username.getText().toString().trim();
password = et_password.getText().toString().trim();
serverIp = et_serverIp.getText().toString().trim();
String data = "{cmd:register}{username:"+username+"}{password:"+password+"}";
if (ToolUtil.IsIpv4(serverIp)) {
new ClientSendDataThread(serverIp, AESUtil.encrypt(ConstantUtil.ServerAESKey, data), ConstantUtil.port).start();//消息发送方启动线程发送消息
showProgressDialog("尝试发送数据到\n\t\t" + serverIp, true);
} else {
Toast.makeText(LoginActivity.this, "IP不合法!", Toast.LENGTH_SHORT).show();
}
}
private void sendLoginInfoToServer() {
username = et_username.getText().toString().trim();
password = et_password.getText().toString().trim();
serverIp = et_serverIp.getText().toString().trim();
String data = "{cmd:login}{username:"+username+"}{password:"+password+"}{clientIp:"+localIp+"}";
if (ToolUtil.IsIpv4(serverIp)) {
socket = new Socket();
login(socket,AESUtil.encrypt(ConstantUtil.ServerAESKey,data));
showProgressDialog("尝试发送数据到\n\t\t" + serverIp, true);
} else {
Toast.makeText(LoginActivity.this, "IP不合法!", Toast.LENGTH_SHORT).show();
}
}
private void login(Socket socket, String data) {
new Thread(){
@Override
public void run() {
try {
socket.connect(new InetSocketAddress(serverIp,ConstantUtil.port),ConstantUtil.TIME_MILLIS);//设置超时时间
} catch (UnknownHostException e) {
EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
Log.d("LoginActivity", "socket.connect has UnknownHostException" + e.getMessage());
} catch (SocketTimeoutException e) {
EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
Log.d("LoginActivity", "socket.connect has TimeoutException:" + e.getMessage());
}catch (IOException e){
EventBus.getDefault().post(ConstantUtil.CODE_EHOSTUNREACH);
Log.d("LoginActivity", "socket.connect has IOException:" + e.getMessage());
}
if (socket != null&&socket.isConnected()) {
try {
OutputStream ops = socket.getOutputStream();//得到socket输入流
OutputStreamWriter opsw = new OutputStreamWriter(ops);//
BufferedWriter writer = new BufferedWriter(opsw);
writer.write(data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
writer.flush();
//接收服务器响应
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine();
//length<120,才是登录界面应该接收到的包,这个活动结束后 因为有阻塞,线程仍然在跑
if ( msg != null && msg.length()<120)
{
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("LoginActivity","接收到服务器数据:"+msg);
EventBus.getDefault().post(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}.start();
}
/**
* 连接结果
*
* @param resultCode 0:连接超时;1:发送成功 2:失败
*/
@Subscribe(threadMode = ThreadMode.MAIN)
public void sendResult(String resultCode) {
Log.i("LoginActiviy", "resultCode :" + resultCode);
dismissProgressDialog();
switch (resultCode) {
case SRConstantsUtil.LOGIN_SUCCESS:
//存储创建的socket
MySocketApplication app = (MySocketApplication) getApplicationContext();
app.setSocket(socket);
Toast.makeText(LoginActivity.this, "登录成功。", Toast.LENGTH_SHORT).show();
Bundle bundle = new Bundle();
bundle.putString(ConstantUtil.SERVERIP,serverIp);
bundle.putString(ConstantUtil.USERNAME,username);
Intent intent = new Intent(LoginActivity.this, FriendListActivity.class);
intent.putExtras(bundle);
startActivity(intent);
finish();
break;
case SRConstantsUtil.REGISTER_SUCCESS:
Toast.makeText(LoginActivity.this, "注册成功!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.REGISTER_FAILED:
Toast.makeText(LoginActivity.this, "注册失败,已存在该用户名!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.LOGIN_FAILED:
Toast.makeText(LoginActivity.this, "登录失败,请检查账号和密码!", Toast.LENGTH_SHORT).show();
break;
case SRConstantsUtil.LOGIN_ALREADY:
Toast.makeText(LoginActivity.this, "登录失败,该账户已经登录!", Toast.LENGTH_SHORT).show();
break;
case ConstantUtil.CODE_TIMEOUT:
Toast.makeText(LoginActivity.this, "连接超时...", Toast.LENGTH_SHORT).show();
break;
case ConstantUtil.CODE_UNKNOWN_HOST:
Toast.makeText(LoginActivity.this, "错误-未知的host", Toast.LENGTH_SHORT).show();
break;
case ConstantUtil.CODE_EHOSTUNREACH:
Toast.makeText(LoginActivity.this, "No route to host", Toast.LENGTH_SHORT).show();
break;
}
}
/**
* 数据加载小菊花
*
* @param msg 内容
* @param isCancel 是否允许关闭 true - 允许 false - 不允许
*/
public void showProgressDialog(final String msg, final boolean isCancel) {
runOnUiThread(new Runnable() {
@Override
public void run() {
try {
if (mProgressDialog == null) {
mProgressDialog = new ProgressDialog(LoginActivity.this);
}
if (mProgressDialog.isShowing()) {
return;
}
mProgressDialog.setMessage(msg);
mProgressDialog.setCancelable(isCancel);
mProgressDialog.setCanceledOnTouchOutside(false);
mProgressDialog.setOnCancelListener(null);
mProgressDialog.show();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* 隐藏数据加载的进度小菊花
**/
public void dismissProgressDialog() {
try {
if (mProgressDialog != null && mProgressDialog.isShowing()) {
runOnUiThread(
new Runnable() {
@Override
public void run() {
mProgressDialog.dismiss();
}
}
);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
SQL
这是用来存储自己添加的好友账号、公钥,自己的私钥的本地数据库。
SQLiteHepler
package com.example.socketclient.SQL;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
public class SQLiteHelper extends SQLiteOpenHelper {
//构造socket的数据库
public SQLiteHelper(Context context,String DBMame) {
super(context, DBMame+".db", null, 1);
}
@Override
public void onCreate(SQLiteDatabase db) {
//创建一个名字为 keyInformation,3列的表格
//列:1: _id,2: USERNAME,3: USERKEY
db.execSQL("CREATE TABLE keyInformation(_id INTEGER PRIMARY KEY AUTOINCREMENT," +
"USERNAME VARCHAR(20),USERKEY VARCHAR(2500))");//,LATESTIP VARCHAR(15)
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Thread
ClientSendDataThread
package com.example.socketclient.Thread;
import android.util.Log;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import org.greenrobot.eventbus.EventBus;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
/**
* 数据发送线程
*/
public class ClientSendDataThread extends Thread {
private Socket socket;
private String ip;//接收方的IP
private int port;//接收方的端口号
private String data;//准备发送的数据
public ClientSendDataThread(String ip, String data, int port) {
this.ip = ip;
this.data = data;
this.port = port;
}
@Override
public void run() {
try {
socket = new Socket();//建立Socket(),与服务器建立连接
socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//设置超时时间
} catch (UnknownHostException e) {
EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
Log.d("ClientSendDataThread", "socket.connect has UnknownHostException" + e.getMessage());
} catch (SocketTimeoutException e) {
EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
Log.d("ClientSendDataThread", "socket.connect has TimeoutException:" + e.getMessage());
}catch (IOException e){
EventBus.getDefault().post(ConstantUtil.CODE_EHOSTUNREACH);
Log.d("ClientSendDataThread", "socket.connect has IOException:" + e.getMessage());
}
if (socket != null&&socket.isConnected()) {
try {
OutputStream ops = socket.getOutputStream();//得到socket输入流
OutputStreamWriter opsw = new OutputStreamWriter(ops);//
BufferedWriter writer = new BufferedWriter(opsw);
writer.write(data + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
writer.flush();
//接收服务器响应
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = br.readLine();
if ( msg != null )
{
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("ClientSendDataThread","接收到服务器数据:"+msg);
if(msg.length()<=35)
EventBus.getDefault().post(msg);//发布事件
}
else {
Log.d("ClientSendDataThread","没接收到服务器响应");
EventBus.getDefault().post(ConstantUtil.MSGSEND_FAILED);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
ConnectThread
package com.example.socketclient.Thread;
import android.content.Context;
import android.util.Log;
import com.example.socketclient.Util.AESUtil;
import com.example.socketclient.Util.ConstantUtil;
import com.example.socketclient.Util.MySocketApplication;
import org.greenrobot.eventbus.EventBus;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public class ConnectThread extends Thread {
private Socket socket;
private String ip;//接收方的IP
private int port;//接收方的端口号
private Context context;//准备发送的数据
private String username;
public ConnectThread(String ip, int port ,Context context,String username) {
this.ip = ip;
this.context = context;
this.port = port;
this.username = username;
}
@Override
public void run() {
try {
socket = new Socket();//建立Socket(),与服务器建立连接
socket.connect(new InetSocketAddress(ip,port),ConstantUtil.TIME_MILLIS);//设置超时时间
} catch (UnknownHostException e) {
EventBus.getDefault().post(ConstantUtil.CODE_UNKNOWN_HOST);
Log.d("ClientSendDataThread", "SendDataThread.init() has UnknownHostException" + e.getMessage());
} catch (SocketTimeoutException e) {
EventBus.getDefault().post(ConstantUtil.CODE_TIMEOUT);
Log.d("ClientSendDataThread", "SendDataThread.init() has TimeoutException:" + e.getMessage());
}catch (IOException e){
Log.d("ClientSendDataThread", "SendDataThread.init() has IOException:" + e.getMessage());
}
if (socket != null&&socket.isConnected()) {
try {
OutputStream ops = null;//得到socket输入流
try {
ops = socket.getOutputStream();
} catch (IOException e) {
e.printStackTrace();
}
OutputStreamWriter opsw = new OutputStreamWriter(ops);//
BufferedWriter writer = new BufferedWriter(opsw);
String data = "{cmd:connect}{username:"+username+"}";
writer.write(AESUtil.encrypt(ConstantUtil.ServerAESKey,data) + "\r\n\r\n");//由于socket使用缓冲区进行读写数据,因此使用\r\n\r\n用于表明数据已写完.不加这个会导致数据无法发送
writer.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String msg = null;
while(true) {
try {
if ((msg = br.readLine())== null)break;
msg = AESUtil.decrypt(ConstantUtil.ServerAESKey, msg);
Log.d("Function_Socket_Client","接收到服务器数据:"+msg);
EventBus.getDefault().post(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
catch (IOException e) {
e.printStackTrace();
}
}
}
}
Util
AESUtil
package com.example.socketclient.Util;
import android.util.Log;
import java.io.UnsupportedEncodingException;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加密工具类
*/
public class AESUtil {
// private static final String CipherMode = "AES/ECB/PKCS5Padding";使用ECB加密,不需要设置IV,但是不安全
private static final String CipherMode = "AES/CFB/NoPadding";//使用CFB加密,需要设置IV;CFB(Cipher FeedBack Mode,加密反馈),不填充
/**
* 生成加密后的密钥
*
* @param keySeed 密钥种子
* @return isSucceed
*/
public static SecretKeySpec createKey(String keySeed) {
byte[] data = null;
if (keySeed == null) {
keySeed = "";
}
StringBuilder sb = new StringBuilder(32);//32位容量的字符串
sb.append(keySeed);//字符串追加密码
while (sb.length() < 32) {
sb.append("0");//少于32位,追加‘0’
}
if (sb.length() > 32) {
//setLength(newLength)
//如果 newLength 参数小于当前长度,则长度将更改为指定的长度。
//如果 newLength 参数大于或等于当前长度,则将追加有效的 null 字符 ('u0000'),使长度满足 newLength 参数。
sb.setLength(32);
}
try {
data = sb.toString().getBytes("UTF-8");//得到 使用UTF-8编码表 的一个系统默认的编码格式的 字节数组
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return new SecretKeySpec(data, "AES");//根据32字节的数据,生成一个AES算法生成的密钥
}
// /** 加密字节数据 **/
private static byte[] encrypt(byte[] content, String password) {
try {
SecretKeySpec key = createKey(password);//根据密钥种子生成密钥
System.out.println(key);
Cipher cipher = Cipher.getInstance(CipherMode);
//初始化Cipher,mode指定是加密还是解密,key为公钥或密钥;ENCRYPT_MODE加密模式,
// 实例化IvParameterSpec对象,使用指定的初始化向量
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(
new byte[cipher.getBlockSize()]));
//使用CFB加密CFB(Cipher FeedBack Mode,加密反馈)、不填充的方式
//init为 加密形式
//返回btye[]数组
return cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// /** 加密(结果为16进制字符串) **/
public static String encrypt(String password, String content) {
//Log.d("加密前", "seed=" + password + "\ncontent=" + content);
byte[] data = null;//byte字节数组
try {
data = content.getBytes("UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
data = encrypt(data, password);//加密字节数据
String result = byte2hex(data);//字节转hex
Log.d("AES加密后", "result=" + result);
return result;
}
// /** 解密字节数组 **/
private static byte[] decrypt(byte[] content, String password) {
try {
SecretKeySpec key = createKey(password);
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(
new byte[cipher.getBlockSize()]));
return cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
// /** 解密16进制的字符串为字符串 **/
public static String decrypt(String password, String content) {
Log.d("AES解密前", "seed=" + password + "\ncontent=" + content);
byte[] data = null;
try {
data = hex2byte(content);
} catch (Exception e) {
e.printStackTrace();
}
data = decrypt(data, password);
if (data == null)
return null;
String result = null;
try {
result = new String(data, "UTF-8");
//Log.d("解密后", "result=" + result);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return result;
}
// /** 字节数组转成16进制字符串 **/
private static String byte2hex(byte[] b) { // 一个字节的数,
StringBuilder sb = new StringBuilder(b.length * 2);
String tmp ;
for (byte aB : b) {
// 整数转成十六进制表示
tmp = (Integer.toHexString(aB & 0XFF));
if (tmp.length() == 1) {
sb.append("0");
}
sb.append(tmp);
}
return sb.toString().toUpperCase(); // 转成大写
}
// /** 将hex字符串转换成字节数组 **/
private static byte[] hex2byte(String inputString) {
if (inputString == null || inputString.length() < 2) {
return new byte[0];
}
inputString = inputString.toLowerCase();
int l = inputString.length() / 2;
byte[] result = new byte[l];
for (int i = 0; i < l; ++i) {
String tmp = inputString.substring(2 * i, 2 * i + 2);
result[i] = (byte) (Integer.parseInt(tmp, 16) & 0xFF);
}
return result;
}
}
ConstantUtil
package com.example.socketclient.Util;
/**
* 常量类
*/
public class ConstantUtil {
public static final int TIME_MILLIS = 5 * 1000;//连接超时时间
public static final int port = 25256;//端口号
public static final String ServerAESKey = "123456885";//加密所使用的密钥
public static final String PRIVATEKEY_AESKey = "25764i8356";//加密所使用的密钥
public static final String CODE_TIMEOUT = "pzl0";//连接超时
public static final String CODE_EHOSTUNREACH = "pzl1";//No route to host
public static final String CODE_UNKNOWN_HOST = "pzl2";//错误-未知的host
public static final String SERVERIP = "SERVERIP";//服务器地址
public static final String USERNAME = "USERNAME";//服务器地址
public static final String MSGSEND_FAILED = "send_msg_failed";//连接成功
public static final String FRIENDACCOUNT = "friendAccount";//接收者错误
}
ListItemAdapterUtil
package com.example.socketclient.Util;
import android.content.Context;
import android.widget.SimpleAdapter;
import androidx.annotation.IdRes;
import androidx.annotation.LayoutRes;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ListItemAdapterUtil {
private static List<Map<String,Object>> listItem = new ArrayList<Map<String,Object>>();
private Context context;
private static Map<String,Object> map = new HashMap<String,Object>();
private static SimpleAdapter adapter;
private int R_layout_item;
private int[] R_layout_item_title_Array;
/**
* @param context 上下文
* @param from 重map中什么键值提取键值。
* @param R_layout_item item.xml文件
* @param R_layout_item_title_Array item中需要填重的控件。这里已经写死了,new String[]{"account"}
* @return
*/
public ListItemAdapterUtil(Context context, String[] from, @LayoutRes int R_layout_item, @IdRes int[] R_layout_item_title_Array){
this.context = context;
this.R_layout_item = R_layout_item;
this.R_layout_item_title_Array = R_layout_item_title_Array;
//map.put("account",username);
//listItem.add(map);
adapter = new SimpleAdapter(context,listItem,R_layout_item,
from,R_layout_item_title_Array);
adapter.notifyDataSetChanged();
}
public SimpleAdapter addItem(String newFriendAccount)
{
//通过不断的填充新的Map添加数据
Map<String,Object> map = new HashMap<String,Object>();
map.put("account",newFriendAccount);
listItem.add(map);
adapter = new SimpleAdapter(context,listItem,R_layout_item,
new String[]{"account"},R_layout_item_title_Array);
adapter.notifyDataSetChanged();
return adapter;
}
public void deleteItem()
{
int size = listItem.size();
/*
//删除一项
if( size > 0 )
{
listItem.remove(listItem.size() - 1);
adapter.notifyDataSetChanged();
}
*/
if(size>0)
while( size-- > 0 )
{
listItem.remove(listItem.size() - 1);
adapter.notifyDataSetChanged();
}
}
public static SimpleAdapter getAdapter() {
return adapter;
}
}
MD5Util
package com.example.socketclient.Util;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5Util {
//MD5加密
public static String toMD5(String str) {
//实例化一个指定摘要算法为MD5的MessageDigest对象
MessageDigest algorithm = null;
try {
algorithm = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 重置摘要以供再次使用
algorithm.reset();
// 使用bytes更新摘要
algorithm.update(str.getBytes());
// 使用指定的byte数组对摘要进行最后更新,然后完成摘要计算
return toHexString(algorithm.digest(), "");
}
private static String toHexString(byte[] digest, String string) {
// TODO Auto-generated method stub
StringBuilder hexString = new StringBuilder();
for(byte b:digest) {
String hex = Integer.toHexString(0xff & b);//toHexString传的参数应该是int类型32位,将前24位字符省略,将后8位保留。
if (hex.length() == 1) {
hexString.append("0");
}
hexString.append(hex).append(string);
}
return hexString.toString();
}
}
MySocketApplication
package com.example.socketclient.Util;
import android.app.Application;
import android.widget.Toast;
import java.net.Socket;
public class MySocketApplication extends Application {
private static Socket socket = null;
public void setSocket(Socket socket){
this.socket = socket;
}
public Socket getSocket(){
return this.socket;
}
}
RSAUtil
package com.example.socketclient.Util;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
/**
* 密钥长度 于原文长度对应 以及越长速度越慢
*/
private final static int KEY_SIZE = 1536;
private static final String CipherMode = "RSA";
/**
* 用于封装随机产生的公钥与私钥
*/
private static Map<String, String> keyMap = new HashMap<String, String>();
//公钥
public static final String PUBLIC_KEY = "RSAPublicKey";
//私钥
public static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 随机生成密钥对
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(CipherMode);
// 初始化密钥对生成器
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
// 得到私钥字符串
String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
// 将公钥和私钥保存到Map
//0表示公钥
keyMap.put(PUBLIC_KEY, publicKeyString);
//1表示私钥
keyMap.put(PRIVATE_KEY, privateKeyString);
}
public static String getPublicKey(){
return keyMap.get(PUBLIC_KEY);
}
public static String getPrivateKey(){
return keyMap.get(PRIVATE_KEY);
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static String encrypt(String str, String publicKey) throws Exception {
//base64编码的公钥
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(CipherMode).generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static String decrypt(String str, String privateKey) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = Base64.getDecoder().decode(str);
//base64编码的私钥
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(CipherMode).generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
}
SRConstantUtil
package com.example.socketclient.Util;
import android.os.Build;
import androidx.annotation.RequiresApi;
import java.util.Base64;
import javax.crypto.Cipher;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
public class RSAUtil {
/**
* 密钥长度 于原文长度对应 以及越长速度越慢
*/
private final static int KEY_SIZE = 1536;
private static final String CipherMode = "RSA";
/**
* 用于封装随机产生的公钥与私钥
*/
private static Map<String, String> keyMap = new HashMap<String, String>();
//公钥
public static final String PUBLIC_KEY = "RSAPublicKey";
//私钥
public static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 随机生成密钥对
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static void genKeyPair() throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(CipherMode);
// 初始化密钥对生成器
keyPairGen.initialize(KEY_SIZE, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = Base64.getEncoder().encodeToString(publicKey.getEncoded());
// 得到私钥字符串
String privateKeyString = Base64.getEncoder().encodeToString(privateKey.getEncoded());
// 将公钥和私钥保存到Map
//0表示公钥
keyMap.put(PUBLIC_KEY, publicKeyString);
//1表示私钥
keyMap.put(PRIVATE_KEY, privateKeyString);
}
public static String getPublicKey(){
return keyMap.get(PUBLIC_KEY);
}
public static String getPrivateKey(){
return keyMap.get(PRIVATE_KEY);
}
/**
* RSA公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static String encrypt(String str, String publicKey) throws Exception {
//base64编码的公钥
byte[] decoded = Base64.getDecoder().decode(publicKey);
RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance(CipherMode).generatePublic(new X509EncodedKeySpec(decoded));
//RSA加密
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
String outStr = Base64.getEncoder().encodeToString(cipher.doFinal(str.getBytes("UTF-8")));
return outStr;
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
@RequiresApi(api = Build.VERSION_CODES.O)
public static String decrypt(String str, String privateKey) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = Base64.getDecoder().decode(str);
//base64编码的私钥
byte[] decoded = Base64.getDecoder().decode(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance(CipherMode).generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance(CipherMode);
cipher.init(Cipher.DECRYPT_MODE, priKey);
String outStr = new String(cipher.doFinal(inputByte));
return outStr;
}
}
ToolUtil
package com.example.socketclient.Util;
import android.util.Log;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.Enumeration;
/**
* 工具类
*/
public class ToolUtil {
/**
* 获取ip地址
* 如果是移动网络,会显示自己的公网IP,如果是局域网,会显示局域网IP
* 因此本例中服务器端需要断开移动网络以得到本机局域网IP
*/
public static String getHostIP() {
String hostIp = null;
try {
Enumeration nis = NetworkInterface.getNetworkInterfaces();
InetAddress ia;
if(nis!=null)
while (nis.hasMoreElements()) {
NetworkInterface ni = (NetworkInterface) nis.nextElement();
Enumeration<InetAddress> ias = ni.getInetAddresses();
while (ias.hasMoreElements()) {
ia = ias.nextElement();
if (ia instanceof Inet6Address) {
continue;// skip ipv6
}
String ip = ia.getHostAddress();
if (!"127.0.0.1".equals(ip)) {
hostIp = ia.getHostAddress();
break;
}
}
}
} catch (SocketException e) {
Log.i("error", "SocketException");
e.printStackTrace();
}
return hostIp;
}
/**
* 判断地址是否为IPV4地址
*/
public static boolean IsIpv4(String ipv4) {
if (ipv4 == null || ipv4.length() == 0) {
return false;//字符串为空或者空串
}
String[] parts = ipv4.split("\\.");//因为java doc里已经说明, split的参数是reg, 即正则表达式, 如果用"|"分割, 则需使用"\\|"
if (parts.length != 4) {
return false;//分割开的数组根本就不是4个数字
}
for (String part : parts) {
try {
int n = Integer.parseInt(part);
if (n < 0 || n > 255) {
return false;//数字不在正确范围内
}
} catch (NumberFormatException e) {
return false;//转换数字不正确
}
}
return true;
}
}
layout
activity_friend_list.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/bg4">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:padding="5dp"
android:layout_marginLeft="10dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="好友账号: "
android:textColor="#000"
android:textSize="18sp"/>
<EditText
android:id="@+id/edt_friendAccount"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#2fff"
android:padding="10dp"
android:text="123"
android:inputType="number"
android:hint="请输入账号,账号只能是阿拉伯数字"/>
</LinearLayout>
<Button
android:id="@+id/btn_addFriend"
android:layout_width="match_parent"
android:layout_height="55dp"
android:text="添加好友"
android:gravity="center"
android:textSize="20sp"
android:background="#8fec"
android:layout_margin="10dp"/>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="55dp">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:textSize="30dp"
android:text="网络加密-好友列表" />
</RelativeLayout>
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="5dp"/>
</LinearLayout>
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/bg2"
>
<View
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2.5"
android:orientation="vertical"
android:padding="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:padding="5dp"
android:gravity="center">
<TextView
android:layout_width="0dp"
android:layout_weight="1.5"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:text="服务器ip:"
android:textColor="#000"
android:textSize="18sp"/>
<EditText
android:id="@+id/et_serverIp"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="请输入服务器ip"
android:textColorHint="#000"
android:textColor="#000"
android:text="192.168.1.10"
android:inputType="numberDecimal"
/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:padding="5dp"
android:gravity="center">
<TextView
android:layout_width="0dp"
android:layout_weight="1.5"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:text="账 号:"
android:textColor="#000"
android:textSize="18sp"/>
<EditText
android:id="@+id/et_username"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="请输入账号"
android:textColorHint="#666"
android:textColor="#000"
android:text="123"
android:inputType="number"
/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dp"
android:padding="5dp"
android:gravity="center">
<TextView
android:layout_width="0dp"
android:layout_weight="1.5"
android:layout_height="wrap_content"
android:padding="5dp"
android:gravity="center"
android:text="密 码:"
android:textColor="#000"
android:textSize="18sp"/>
<EditText
android:id="@+id/et_password"
android:layout_width="0dp"
android:layout_weight="3"
android:layout_height="wrap_content"
android:hint="请输入密码,最大长度为15"
android:text="123"
android:maxLength="15"
android:textColorHint="#666"
android:inputType="textPassword"
android:textColor="#000"
/>
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="0.5"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="登 录"
android:textSize="20dp"
android:textColor="#000"
android:background="#3000"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<Button
android:id="@+id/btn_regist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:text="注 册"
android:textSize="20dp"
android:textColor="#000"
android:background="#3000"/>
</LinearLayout>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.5"
android:gravity="center"
android:textSize="18sp"
android:text="Power By 琅邪"/>
</LinearLayout>
friend_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/friend_item_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
android:padding="10dp"
android:maxHeight="50dp"
android:maxWidth="50dp"
android:src="@drawable/item_img"/>
<TextView
android:id="@+id/friend_item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dp"
android:layout_gravity="center" />
</LinearLayout>
function_socket_client.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@mipmap/bgchat">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="55dp">
<TextView
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:textSize="30dp"
android:text="网络加密-客户端" />
</RelativeLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="本机地址:"
android:textSize="18sp"
android:textColor="#000"/>
<TextView
android:id="@+id/tv_clientLocalAddress"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:id="@+id/tv_friendAccount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="好友账号:"
android:textSize="18sp"
android:textColor="#000"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="55dp"
android:padding="5dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="文本内容:"
android:textColor="#000"
android:textSize="18sp"/>
<EditText
android:id="@+id/edtTxt_Content"
android:layout_width="match_parent"
android:layout_height="50dp"
android:paddingLeft="5dp"
android:paddingRight="5dp"
android:background="#4fcd"
android:padding="10dp"
android:text="123木头人" />
</LinearLayout>
<ScrollView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="接收到的密文:"
android:textColor="#000"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_clientReceivedContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text=""
android:padding="10dp"
android:textSize="18sp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="解密后的明文:"
android:textColor="#000"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_clientDecryptContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:textSize="18sp"/>
</LinearLayout>
</ScrollView>
<Button
android:id="@+id/btn_encryptAndSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:text="加密并发送"
android:background="#3000"/>
</LinearLayout>
values
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
</resources>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.socketclient">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:name=".Util.MySocketApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".Activity.FriendListActivity"
>
</activity>
<activity
android:name=".Activity.Function_Socket_Client"
android:windowSoftInputMode="adjustNothing|stateHidden" />
<activity
android:name=".Activity.LoginActivity"
android:windowSoftInputMode="adjustNothing|stateHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
build.gradle(project)
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.2'
classpath 'com.jakewharton:butterknife-gradle-plugin:10.0.0'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
build.gradle(module)
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.example.socketclient"
minSdkVersion 15
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.jakewharton:butterknife:10.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
implementation 'org.greenrobot:greendao:3.0.1'
implementation 'org.greenrobot:greendao-generator:3.0.0'
implementation 'org.greenrobot:eventbus:3.0.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}