尝试用C#和winform实现一次性身份验证原理实验(基于MySQL5.7数据库)

尝试用C#和winform实现一次性身份验证原理实验(基于MySQL5.7数据库)

实验目的

1. 熟悉一次性身份验证的工作原理
2. 熟悉挑战应战的过程
3. 用C#实现一次性身份验证
4. 熟悉散列算法MD5,并会运用
5. 熟悉winform界面化设计

实验环境

  1. 操作系统:windows 10 家庭版
  2. 开发工具:Visual Studio 2019
  3. 语言:C#
  4. 界面实现:winform窗体控制台程序

实验原理

原理如图所示:
在这里插入图片描述核心思想:

  1. 利用散列函数MD5的单向性
  2. 应用挑战应战思想

鉴别流程:
3. 客户端发起认证请求
4. 服务端收到请求后,向客户端发送挑战值N和随机数R
5. 客户端接收挑战值N和R,本地计算应战值(口令的N次MD5迭代值)发回服务端
6. 服务端接收应战值,本地再计算一次MD5散列,于数据库中的h迭代值对比,匹配则认证成功,否则则失败。

注意:
7. 服务端本地数据库中存储的信息有:用户名,口令迭代值h,挑战值N,和随机数R,没有用户的口令密码。此外口令迭代值h始终为H^(N+1)(pwd||R) ,始终比用户端发来的应战值多一次MD5运算,并随着N的变化在变化。
8. 服务器收到有效的应战值后要进行以下几个操作:a.将挑战值N自减1,b.将收到的挑战值作为新的迭代值h更新数据库中原有的h值。

成品效果

数据库存表:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
(一些特定的按钮和设计只是为了更好的看清实验步骤,实际运用中没有。)

代码实现

服务端代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using MySql.Data.MySqlClient;   //引用MySql
using System.Security.Cryptography; //用于MD5加密

namespace 身份验证//server 
{
    public partial class Server : Form
    {
        public Server()
        {
            InitializeComponent();
            //关闭对文本框的非法线程操作检查
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }

        Thread threadWatch = null; //负责监听客户端的线程 
        Socket socketWatch = null; //负责监听客户端的套接字     

        static Dictionary<string, Socket> clientConnectionItems = new Dictionary<string, Socket> { };

        /// <summary>
        /// 启动服务 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void BtnStratSer_Click(object sender, EventArgs e)
        {
            try
            {
                //定义一个套接字用于监听客户端发来的信息  包含3个参数(IP4寻址协议,流式连接,TCP协议)
                socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                //服务端发送信息 需要1个IP地址和端口号
                IPAddress ipaddress = IPAddress.Parse("127.0.0.1"); //手工绑定ip地址 
                //将IP地址和端口号绑定到网络节点endpoint上 
                IPEndPoint endpoint = new IPEndPoint(ipaddress, int.Parse("8888")); //手工绑定端口号
                //监听绑定的网络节点
                socketWatch.Bind(endpoint);
                //将套接字的监听队列长度限制为20
                socketWatch.Listen(20);
                //创建一个监听线程 
                threadWatch = new Thread(WatchConnecting);
                //将窗体线程设置为与后台同步
                threadWatch.IsBackground = true;
                //启动线程
                threadWatch.Start();
                //启动线程后 txtMsg文本框显示相应提示
                TextMsg.AppendText("开始监听客户端传来的信息!" + "\r\n");
                this.BtnStratSer.Enabled = false;//启动后“启动”按钮失效 
            }
            catch (Exception ex)
            {
                TextMsg.AppendText("服务端启动服务失败!" + "\r\n");//  \r\n才能换行 
                this.BtnStratSer.Enabled = true;
            }
        }


        //创建一个负责和客户端通信的套接字 
        Socket socConnection = null;

        /// <summary>
        /// 监听用户端 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void WatchConnecting()
        {

            while (true)  //持续不断监听客户端发来的请求
            {
                socConnection = socketWatch.Accept();
                TextMsg.AppendText("客户端连接成功! " + "\r\n");
                //创建一个通信线程  pts 
                ParameterizedThreadStart pts = new ParameterizedThreadStart(ServerRecMsg);
                Thread thr = new Thread(pts);
                thr.IsBackground = true;
                //启动线程
                thr.Start(socConnection);
            }
        }

        /// <summary>
        /// 接收消息函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void ServerSendMsg(string sendMsg)
        {
            try  
            {
                //将输入的字符串转换成 机器可以识别的字节数组
                byte[] arrSendMsg = Encoding.UTF8.GetBytes(sendMsg);
                //向客户端发送字节数组信息
                socConnection.Send(arrSendMsg);
                //将发送的字符串信息附加到文本框txtMsg上
                TextMsg.AppendText("服务器:" + GetCurrentTime()+ "\r\n" + sendMsg + "\r\n");
            }
            catch (Exception ex) 
            {
                TextMsg.AppendText("客户端已断开连接,无法发送信息!" + "\r\n");
            }
        }
        //try=catch语句是为了尝试执行程序,程序没有错误则按顺序执行try内语句,程序出错则执行catch内语句,避免程序崩溃中断 

        /// <summary>
        /// 接收消息函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void ServerRecMsg(object socketClientPara)
        {
            Socket socketServer = socketClientPara as Socket;
            while (true)
            {
                //创建一个内存缓冲区 其大小为1024*1024字节 
                byte[] arrServerRecMsg = new byte[1024 * 1024];
                try
                {
                        //将接收到的信息存入到内存缓冲区,并返回其字节数组的长度
                    int length = socketServer.Receive(arrServerRecMsg);
                        //将机器接受到的字节数组转换为人可以读懂的字符串
                    string strSRecMsg = Encoding.UTF8.GetString(arrServerRecMsg, 0, length);
                        //将发送的字符串信息附加到文本框txtMsg上 
                    string[] RecMsg = strSRecMsg.Split('_');   //分成RecMsg[0]=标记符,RecMsg[1]=用户名和RecMsg[2]=密码
                    this.TextMsg.AppendText("客户端:" +GetCurrentTime() + "\r\n" + strSRecMsg + "\r\n");
                   


                    //这里要开始分情况;1 注册--插入语句 2 登录--查询语句 3 查询当前N,R值
                    //--------------------------------------------------------------------------------------------------
                    if (int.Parse(RecMsg[0]) == 1)//注册--插入MySQL --存入数据库username和password,pwd不需要加密
                    {
                        //首先检查用户是否已经被注册
                        string constructorString2 = "server = 127.0.0.1; port = 3306; user = gust; password = gust; database = sk_test;";//root权限太高,出于安全考虑 改用gust
                        MySqlConnection myConnnect2 = new MySqlConnection(constructorString2);
                        myConnnect2.Open();//打开
                        MySqlCommand myCmd2 = new MySqlCommand("SELECT N,R from login2 where username='" + RecMsg[1] + "'; ", myConnnect2);//查找N R sql语句 
                        MySqlDataReader dataReader = myCmd2.ExecuteReader();
                        if (dataReader.HasRows) 
                        {
                            MessageBox.Show("该用户已被注册!");
                        }
                        else
                        {
                            string Enc = Nmd5("1", RecMsg[2]);//MD5加密 可用
                            string constructorString = "server = 127.0.0.1; port = 3306; user = gust; password = gust; database = sk_test;";//root权限太高,出于安全考虑 改用gust
                            MySqlConnection myConnnect = new MySqlConnection(constructorString);
                            myConnnect.Open();//打开
                            MySqlCommand myCmd = new MySqlCommand($"INSERT INTO login2(username,password,N,R) VALUES('{RecMsg[1]}','{Enc}','{this.TextGetN.Text}','{this.TextGetR.Text}');", myConnnect);//sql语句构造成功!插入完成!!$不可少                                                                                                                      
                            if (myCmd.ExecuteNonQuery() > 0)//ExecuteNonQuery()返回影响的行数(用于insert,update,delete),其他关键字返回-1
                            {
                                MessageBox.Show("注册成功!插入数据库成功!");//插入数据库成功!
                            }
                            this.BtnStratSer.Enabled = true;
                        }
                    }


                    if(int.Parse(RecMsg[0]) == 2)//登录--查询MySQL                        
                    {

                        string Enc = Nmd5("1",RecMsg[2]);//新的算法
                        //本地再计算一次N次迭代

                        string constructorString = "server = 127.0.0.1; port = 3306; user = gust; password = gust; database = sk_test;";//root权限太高,出于安全考虑 改用gust
                        MySqlConnection myConnnect = new MySqlConnection(constructorString);
                        myConnnect.Open();//打开
                        MySqlCommand myCmd = new MySqlCommand("UPDATE login2 SET times = times+1,N=N-1,password='" + RecMsg[2] + "' where username = '" + RecMsg[1] + "' and password = '" + Enc + "';", myConnnect);
                        //更新pwd,N值自减1 times自增1(记录用户登录次数)
                        if (myCmd.ExecuteNonQuery() > 0)//通过影响的行数!=0 判断语句被执行
                        {
                            MessageBox.Show("登录成功!插入数据库成功!");//插入数据库成功!
                          
                        }
                        else
                        {
                            MessageBox.Show("登录失败!用户名或密码错误!");
                        }

                    }


                    if (int.Parse(RecMsg[0]) == 3)
                    {   //N=1提示重新注册 
                        string constructorString = "server = 127.0.0.1; port = 3306; user = gust; password = gust; database = sk_test;";//root权限太高,出于安全考虑 改用gust
                        MySqlConnection myConnnect = new MySqlConnection(constructorString);
                        myConnnect.Open();//打开
                        MySqlCommand myCmd = new MySqlCommand("SELECT N,R from login2 where username='" + RecMsg[1] + "'; ", myConnnect);//查找N R sql语句 
                        MySqlDataReader dataReader = myCmd.ExecuteReader();

                        if (dataReader.HasRows)//判断是否存在N,R
                        {
                            //存在N,R
                            dataReader.Read();
                            TextGetN.Text = dataReader[0].ToString();//查到的N 
                            TextGetR.Text = dataReader[1].ToString();//查到的R
                            if (int.Parse(TextGetN.Text) == 1)
                            {
                                
                                string constructorString2 = "server = 127.0.0.1; port = 3306; user = gust; password = gust; database = sk_test;";//root权限太高,出于安全考虑 改用gust
                                MySqlConnection myConnnect2 = new MySqlConnection(constructorString2);
                                myConnnect2.Open();
                                MySqlCommand myCmdDel = new MySqlCommand("DELETE  from login2 where username='" + RecMsg[1] + "'and N ='1'; ", myConnnect2);//语句正确
                                if (myCmdDel.ExecuteNonQuery() > 0)
                                {
                                    MessageBox.Show("N的值为“1”,请重新注册用户!");//插入数据库成功!
                                    int N2 = Ran();
                                    int R2 = Ran2();
                                    TextGetN.Text = N2.ToString();
                                    TextGetR.Text = R2.ToString();
                                    string SendNR = N2.ToString() + "_" + R2.ToString();
                                    ServerSendMsg(SendNR.Trim());//产生新的N R值并发送给客户端

                                }
                                else
                                {
                                    MessageBox.Show("删除语句未正常执行!");
                                }
                            }  

                        }
                        else
                        {
                            //不存在N,R
                            MessageBox.Show("该用户不存在!请先注册!");
                            //发送新的N,R给用户,方便注册
                            int N2 = Ran();
                            int R2 = Ran2();
                            TextGetN.Text = N2.ToString();
                            TextGetR.Text = R2.ToString();
                            string SendNR = N2.ToString() + "_" + R2.ToString();
                            ServerSendMsg(SendNR.Trim());//产生新的N R值并发送给客户端
                        }

                    }


                }
                catch (Exception ex)
                {

                    TextMsg.AppendText("客户端已断开连接!" + "\r\n");         
                    break;
                }
            }
        }

        /// <summary>
        /// 发送消息到客户端 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void BtnSendMsg_Click(object sender, EventArgs e)
        {
            int N = Ran(); //N这里设定为1-10,支持N足够大
            int R = Ran2();
            this.TextGetN.Text = N.ToString();
            this.TextGetR.Text = R.ToString();//从这里  可以拿到N R
            string SendNR = N.ToString() + "_" + R.ToString();
            ServerSendMsg(SendNR.Trim());//发送N R

        }

        /// <summary>
        /// 查找当前的N,R 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void SelectNR_Click(object sender, EventArgs e)
        {
            string GN = this.TextGetN.Text;
            string GR = this.TextGetR.Text;
            string SendNR = GN + "_" + GR;
            ServerSendMsg(SendNR.Trim());//发送N R
        }

        //服务器端只需要把收到的H^N(pwd||R)在做一次md5就好

        //11.15 新的尝试 H^2(p)==H(H(P))
        /// <summary>
        /// MD5加密函数 
        /// </summary>
        /// <param name="socketClientPara"></param>
        public static string Nmd5(string a, string text)
        {
            int n = int.Parse(a);
            StringBuilder sb = new StringBuilder();
            while (n >= 1)
            {
                for (int k = 0; k < n; k++)
                {
                    MD5 md5 = MD5.Create();//创建MD5实例
                    byte[] buffer = System.Text.Encoding.Default.GetBytes(text);//转换成byte[]
                    byte[] Md5buffer = md5.ComputeHash(buffer);//加密
                    //转换成16进制
                    for (int i = 0; i < Md5buffer.Length; i++)
                    {
                        sb.Append(Md5buffer[i].ToString("X2"));  //x-->将10进制转换为16进制。2-->每次都是两位数输出。
                    }
                    md5.Clear();//释放MD5计算空间
                    text = sb.ToString();
                    sb.Length = 0;//清空sb,以便下次给text赋值
                    n -= 1;//n自减1
                }
            }
            return text;
        }


        //调用 int N =Ran(); 
        //调用 int R =Ran();
        public int Ran()  //随机数不发送1 
        {
            int num = new int();
            Random Random = new Random();
            num = Random.Next(5, 21);//5-20 随机数
            return num;
        }  //随机数支持int型数值上限

        public int Ran2() //随机数不发送1
        {
            int num = new int();
            Random Random = new Random();
            num = Random.Next(5, 31);//5-30 随机数
            return num;
        }

        /// <summary>
        /// 获取当前系统时间 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private DateTime GetCurrentTime()
        {
            DateTime currentTime = new DateTime();
            currentTime = DateTime.Now;
            return currentTime;
        }

    }
}
by 久违 2019.11.12

客户端代码:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using MySql.Data.MySqlClient;   //引用MySql
using System.Security.Cryptography; //用于MD5加密

namespace 身份验证client
{
    public partial class Client : Form
    {
        public Client()
        {
            InitializeComponent();
            //关闭对文本框的非法线程操作检查
            TextBox.CheckForIllegalCrossThreadCalls = false;
        }
        //创建 1个客户端套接字 和1个负责监听服务端请求的线程  
        Socket socketClient = null;
        Thread threadClient = null;


        /// 连接服务端事件
        private void BtnConnectSer_Click(object sender, EventArgs e)
        {
            //定义一个套字节监听  包含3个参数(IP4寻址协议,流式连接,TCP协议)
            socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //需要获取文本框中的IP地址
 
            IPAddress ipaddress = IPAddress.Parse("127.0.0.1");
            //将获取的ip地址和端口号绑定到网络节点endpoint上
         
           IPEndPoint endpoint = new IPEndPoint(ipaddress, int.Parse("8888"));
            //这里客户端套接字连接到网络节点(服务端)用的方法是Connect 而不是Bind
           try
            {
                socketClient.Connect(endpoint);
                MessageBox.Show("客户端连接服务端成功!");
                //this.TextMsg.AppendText("客户端连接服务端成功!" + "\r\n");//\r\n换行
                this.BtnConnectSer.Enabled = false;
                //创建一个线程 用于监听服务端发来的消息
                threadClient = new Thread(RecMsg);//后面有函数RecMsg
                //将窗体线程设置为与后台同步
                threadClient.IsBackground = true;
                //启动线程
                threadClient.Start();
            }
            catch (Exception ex)
            {
                MessageBox.Show("远程服务端断开,连接失败!");
     
            }
        }
         
        /// <summary>
        /// 接收消息函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        public void RecMsg()      
        {
            while (true) //持续监听服务端发来的消息
            {
                try
                {
                    //定义一个1M的内存缓冲区 用于临时性存储接收到的信息
                    byte[] arrRecMsg = new byte[1024 * 1024];
                    //将客户端套接字接收到的数据存入内存缓冲区, 并获取其长度
                    int length = socketClient.Receive(arrRecMsg);
                    //将套接字获取到的字节数组转换为人可以看懂的字符串
                    string strRecMsg = Encoding.UTF8.GetString(arrRecMsg, 0, length);
                    //string[] RecMsg = strRecMsg.Split('_');

                    //将发送的信息追加到聊天内容文本框中   GetCurrentTime()获取现在时间的函数
                    TextMsg.Text= strRecMsg;  //只用来接收N R 后面接收的覆盖掉前面的
                 
                }
                catch (Exception ex)
                {
                    MessageBox.Show("远程服务器已中断连接!");
                    //this.TextMsg.AppendText("远程服务器已中断连接!" + "\r\n");
                    this.BtnConnectSer.Enabled = true;
                    break;
                }
            }
        }


        /// <summary>
        /// 发送消息函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void ClientSendMsg(string sendMsg)
        {
            try
            {
                //将输入的内容字符串转换为机器可以识别的字节数组
                byte[] arrClientSendMsg = Encoding.UTF8.GetBytes(sendMsg);
                //调用客户端套接字发送字节数组
                socketClient.Send(arrClientSendMsg);
                //将发送的信息追加到聊天内容文本框中
                //TextMsg.AppendText("客户端" + GetCurrentTime() + "\r\n" + sendMsg + "\r\n");
            }
            catch (Exception ex)
            {
                MessageBox.Show("远程服务器已中断连接,无法发送消息!");
                //this.TextMsg.AppendText("远程服务器已中断连接,无法发送消息!" + "\r\n");
            }
        }

        /// <summary>
        /// 注册函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void BtnZC_Click(object sender, EventArgs e)  
        {
            if (TextName.Text == "")
            {
                MessageBox.Show("用户名不能为空!");
            }
            if (TextPwd.Text == "")
            {
                MessageBox.Show("密码不能为空!");
            }

            string RecNR = TextMsg.Text;
            string[] GetNR = RecNR.Split('_');//GetNR[0]=N GetNR[1]=R
            string Enc = Nmd52(GetNR[0], this.TextPwd.Text, GetNR[1]);                                                             
            string r = "1";//作为标记
            string sendTxt = r + '_' + this.TextName.Text + '_' + Enc;//server端把RecMsg[0]作为标识符
            ClientSendMsg(sendTxt.Trim()); //整体发送,然后根据_分割成三部分 标记符号_用户名_密码   
            MessageBox.Show("注册信息提交成功!");
        }


        /// <summary>
        /// 登录函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        //登录的时候接收服务端发来的N和R,然后本地加密之后发送给服务器
        private void BtnDL_Click(object sender, EventArgs e)
        {
            if (TextName.Text == "")
            {
                MessageBox.Show("用户名不能为空!");
            }
            if (TextPwd.Text == "")
            {
                MessageBox.Show("密码不能为空!");
            }
            if (TextMsg.Text == "")
            {
                MessageBox.Show("N和R不能为空!");
            }
            else
            {

                string RecNR = TextMsg.Text;
                string[] GetNR = RecNR.Split('_');//GetNR[0]=N GetNR[1]=R
                string r = "2";//作为标记       
                string Enc = Nmd52(GetNR[0], this.TextPwd.Text, GetNR[1]);
                string sendTxt = r + '_' + this.TextName.Text + '_' + Enc/*md5MsgPwd*/;//server端把RecMsg[0]作为标记符
                ClientSendMsg(sendTxt.Trim()); //整体发送,然后根据_分割成三部分 标记符_用户名_密码(密码部分后面加密)
                MessageBox.Show("用户名和密码提交成功!");         
            }
        }

        /// <summary>
        /// 查看当前N,R 
        /// </summary>
        /// <param name="socketClientPara"></param>
        private void BtnCheck_Click(object sender, EventArgs e)
        {
            if (TextName.Text == "")
            {
                MessageBox.Show("用户名不能为空!");
            }

            else
            {
                string r = "3";//作为标记
                string sendTxt = r + '_' + this.TextName.Text;//根据用户名查找N R 
                ClientSendMsg(sendTxt.Trim());
                MessageBox.Show("检查信息提交成功!");
            }

        }

        /// <summary>
        /// MD5加密函数
        /// </summary>
        /// <param name="socketClientPara"></param>
        public static string Nmd5(string a, string text)
        {
            int n = int.Parse(a);
            StringBuilder sb = new StringBuilder();

            while (n >= 1)
            {

                for (int k = 0; k < n; k++)
                {
                    MD5 md5 = MD5.Create();

                    byte[] buffer = System.Text.Encoding.Default.GetBytes(text);//转换
                    byte[] Md5buffer = md5.ComputeHash(buffer);//加密
                    //转换成16进制
                    for (int i = 0; i < Md5buffer.Length; i++)
                    {
                        sb.Append(Md5buffer[i].ToString("X2"));  //x-->将10进制转换为16进制。2-->每次都是两位数输出。
                    }
                    md5.Clear();
                    //sb.ToString();
                    text = sb.ToString();
                    sb.Length = 0;//清空sb
                    n -= 1;//n自减1
                }


            }
            return text;
        }


        //11.15 未调用 
        public static string Nmd52(string a, string str1, string str2)
        {
            int n = int.Parse(a);
            string text = str1 + str2;
            StringBuilder sb = new StringBuilder();

            while (n >= 1)
            {

                for (int k = 0; k < n; k++)
                {
                    MD5 md5 = MD5.Create();

                    byte[] buffer = System.Text.Encoding.Default.GetBytes(text);//转换
                    byte[] Md5buffer = md5.ComputeHash(buffer);//加密
                    //转换成16进制
                    for (int i = 0; i < Md5buffer.Length; i++)
                    {
                        sb.Append(Md5buffer[i].ToString("X2"));  //x-->将10进制转换为16进制。2-->每次都是两位数输出。
                    }
                    md5.Clear();
                    //sb.ToString();
                    text = sb.ToString();
                    sb.Length = 0;//清空sb
                    n -= 1;//n自减1
                }


            }
            return text;
        }

    }
}
by 久违 2019.11.12

总结和思考

1、实验的原理相对简单,但是在代码实现的时候却碰到了很多问题。实验实现的思路大概是服务端和客户端先建立socket连接,服务端和数据库之间连接,然后根据一次性验证的原理在三者之间传值加以验证。
2、首先是socket连接,实现连接的过程不难,但是为了能显示出来服务器和客户端的通信的信息内容,我不得不追加传送的内容到一个显示框,方便观察,也方便两者之间取值。但是不同的传值表示格式不同,而我要实现的功能却很多,包括注册,登录,检查等,所以我最终统一了所有传值的格式,采用“标记符+用户名+迭代值”的方式。标记符用于服务端区分不同的功能选项。例如标记符为1执行注册功能代码,为2执行登录功能代码,为3则执行检查功能代码。同时由于我采用TCP传输,数据一次就发送完成了,因此我不得不将数据在接收的时候分段处理。例如服务端接收客户端发的数据:“1_sun_123”时,根据“_”,将数据分成三部分分别处理。
3、其次是运用C#语言,让服务端和数据库互连得实现。从最开始得导入mysql链接库文件,到连接(打开)数据库,在到编写select,update等语句,看似简单的过程,完整的做下来着实花了我不少时间。这其中有一个难点是,如何才能把数据库的状态反馈给服务端,即如何让服务端知道数据库有没有打开,这条语句有没有执行成功,如何从数据库中寻找值,取值等等。这里我根据不同的sql语句采用了不同的办法,以下是一些用到的函数及其作用:Open()打开数据库,ExecuteNonQuery()返回影响的行数(用于insert,update,delete),其他关键字返回-1,MySqlDataReader dataReader读取数据库内容函数,dataReader.HasRows可以用来判断查询的值在数据库中是否存在,等等
4、最重要的是关于N次迭代的MD5算法的编写。由于C#自带的MD5加密算法只能对字节数组类型的数据进行加密,而传值过后的数据类型是字符串,但是MD5最终的输入又是16进制数,所以我不得不进行多次的数据类型的转换,要将string转成byte[],在转成16进制数。而且这种转换不能一蹴而就,还必须一个一个字符转换,这对我编写加密算法大大提高了难度。长达两个星期我都卡在这个上面。有一个错误困扰了我很久(我单纯的循环了N次byte[]类型的MD5加密过程),就是HN+1(P||R)居然不等于H1(HN(P||R)),这样的话直接让我的程序无法登陆,我都不知道加密出来的东西是啥。这个问题在后来我将整体转换数据类型,加密byte[],转成16进制循环N次之后得到解决,心里一块大石落地。但是即便是这样,最终加密出来的结果严格意义上说还是不对的,但是最起码自己能和自己对上了
5、当N不断的减小成1的时候,我设计的服务端会删除这一天用户字段,同时提醒用户重新注册,这样一来N的存在其实有点像秘钥有效期,N=1时,有效期失效
6、注意一点,本程序是为了验证一次性验证而存在,所以有一定的操作过程,按照操作过程可以实现实时监控传值内容。但是关于“检查”这个按钮我也很纠结,它的设计本意是为了让客户端提醒服务器,服务器从数据库中找到该用户当前的N,R值并且返回给客户端的,让我们能看到N,R的传递过程而存在的,这样用也没有什么问题,但是这也违背的一次性密码验证的性质,一次性认证传递的是动态的哈希过的密码,因为N,R的未知和动态,每一次都不一样,这样也保证了安全。但是当一个非法用户知道我的用户名和密码时,他就可以用这个“检查”按钮得知数据库中当前的N,R只是多少,从而无视动态加密认证过程直接可以登录。我等于是自己给自己挖了一个漏洞,我也是很无奈。后来我也想通了,“检查”这个按钮单纯是为了实验,为了更好的看到实验过程而存在,在生活中是万万不能出现的,毕竟哪有人会为了测试保险箱的保密性把密码告诉别人的。
这次实验遇到的难题不少,对我来说也是一种进步吧,希望自己再接再厉,多往上走一走。
2019.11.15

一些无关实验的感想:
我自己在自学C#和winform窗体控制台程序编写的时候,也是在CSDN上借鉴了很多前辈的文章,终于是修修改改完成了,很感谢在CSDN上发帖的大佬们,看着那么多前辈在种树,我也想加入进来,虽然我的能力还很微弱,不求能帮助到谁, 但是写下来,就算是记录,看看自己的成长的过程,也看看自己的极限在哪里,我觉得我还可以再往上一点,再往上一点。
(11.15就写好了,过了快一个月终于有时间整理出来了,又填上一个自己挖的坑,满足)
2019.12.7

  • 15
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

久违 °

小菜鸟就要使劲飞

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值