VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > VB.net教程 >
  • 使用vb.net编写一个邮件定时发送程序(二)

    上次我已经设计出了基本的界面,而且也确定了使用ini文件做为参数的保存方式,调用Windows API函数进行INI文件的读写操作,但有一个重要的问题,这个小程序涉及到了二个方面的密码:一是数据库的连接密码,二是邮件服务器的发送密码,如果我们直接把密码以明文的方式保存到INI文件中,如果一不小心INI文件泄露的话,其他人很容易就可以得到这些参数,可能会对数据库或邮件服务器进行攻击或者伪造邮件,造成不必要的麻烦。因此,我下一步考虑的是如何对敏感信息进行加密。

    现在通用的加密算法有很多,但首先要确定的一点就是,我们选择的加密算法一定要是可逆的,也就是说,使用同一个密码,可以加密,也可以解密出明文,否则就无法将保存的密码恢复,这样的话,最常用的MD5加密方式就不能使用了,我选择的是DES加密方式,虽然这种加密方式可能已经有点老了,但用在这个场合感觉已经够用了,最重要的是,它本身就包括在了vs的一个类中,很方便就可以调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
Imports System.Security.Cryptography
Imports System.Text
 
Public Function Encrypt(ByVal pToEncrypt As String, ByVal sKey As String) As String
        Dim des As New DESCryptoServiceProvider()
        Dim inputByteArray() As Byte
        inputByteArray = Encoding.Default.GetBytes(pToEncrypt)
        ''建立加密对象的密钥和偏移量
        ''原文使用ASCIIEncoding.ASCII方法的GetBytes方法
        ''使得输入密码必须输入英文文本
        des.Key = ASCIIEncoding.ASCII.GetBytes(sKey)
        des.IV = ASCIIEncoding.ASCII.GetBytes(sKey)
        ''写二进制数组到加密流
        ''(把内存流中的内容全部写入)
        Dim ms As New System.IO.MemoryStream()
        Dim cs As New CryptoStream(ms, des.CreateEncryptor, CryptoStreamMode.Write)
        ''写二进制数组到加密流
        ''(把内存流中的内容全部写入)
        cs.Write(inputByteArray, 0, inputByteArray.Length)
        cs.FlushFinalBlock()
        ''建立输出字符串
        Dim ret As New StringBuilder()
        Dim b As Byte
        For Each b In ms.ToArray()
            ret.AppendFormat("{0:X2}", b)
        Next
        Return ret.ToString()
    End Function
 
    Public Function Decrypt(ByVal pToDecrypt As String, ByVal sKey As String) As String
        Try
            Dim des As New DESCryptoServiceProvider()
            ''把字符串放入byte数组
            Dim len As Integer
            len = pToDecrypt.Length / 2 - 1
            Dim inputByteArray(len) As Byte
            Dim x, i As Integer
            For x = 0 To len
                i = Convert.ToInt32(pToDecrypt.Substring(x * 2, 2), 16)
                inputByteArray(x) = CType(i, Byte)
            Next
            ''建立加密对象的密钥和偏移量,此值重要,不能修改
            des.Key = ASCIIEncoding.ASCII.GetBytes(sKey)
            des.IV = ASCIIEncoding.ASCII.GetBytes(sKey)
            Dim ms As New System.IO.MemoryStream()
            Dim cs As New CryptoStream(ms, des.CreateDecryptor, CryptoStreamMode.Write)
            cs.Write(inputByteArray, 0, inputByteArray.Length)
            cs.FlushFinalBlock()
            Return Encoding.Default.GetString(ms.ToArray)
 
        Catch ex As Exception
            Return ""
        End Try
    End Function

    要注意的是,DES算法的密钥必须是八位英文字母或数字,不能多也不能少,不然就会报错。

    现在,我将读写INI的语句稍做改变,写入时加上密码,读取时自动解密,假设密钥使用一个固定的八位字串,示例代码如下:

 

1
2
3
4
5
IniFile = Application.StartupPath + "\sendmail.ini"
'将密码加密后写入INI文件,密钥固定为"vvian999"
WriteINI("SMTP", "password", Encrypt(Txt_MailPwd.Text, "vvian999"), IniFile)
'将读出的密码解密后放入文本框内
Txt_MailPwd.Text = Decrypt(GetINI("SMTP", "password", "", IniFile), "vvian999")

    这样一来,存入INI文件的密码参数就已经是加密后的字串,在不知道密钥的情况下,一般人已经很难将其破解,得到敏感信息了。但安全永远是放在第一位的,我又想到,如果得到INI文件的那个人在其他的机器上运行了这个程序,文本框中就会显示出星号的密码明文,只要使用一个密码查看器之类的软件,密码也会泄露了,而且密钥如果写死在可执行文件中,加密的规律是相同的,也不好呀,最好的办法是每台机器的密钥是一个固定的值,但各台机器的密钥又不相同,这样的话,就算把程序和INI文件一起拷到其他服务器上也无法正常执行,安全也有了保障。

    每台机器一个固定密钥最好的办法就是获取该机器的某个硬件值,如硬盘序号、CPU编号、网卡MAC地址等一般不会变化的数据,使用这些数据得到一个八位长度的字符串,以此为密钥,加密一个只有自己才知道的字符串,每次启动时解密一次,如果解密结果相同,就继续进行,如果解密结果不同,就报错,要求重新输入数据库和邮件服务器的密码,最大程度的保证了数据的安全,这实际上也是共享软件常用的注册码方式吧。

    获取硬件信息我使用的是System.Management,使用wmi来获取,比如下面的例子就是获取网卡的MAC地址,然后取前八位做为密钥进行加密,因为MAC地址都是12位的,不用担心长度不够的问题,也可以取后八位、中间八位等方式来处理。加一个加密好的字符串同时放入INI文件后,以后每次启动程序时进行一次检查。

1
2
3
4
5
6
7
8
9
10
11
12
13
'先获取硬件代码,这里使用的是网卡MAC地址的前八位
Dim HwCode as String
Dim macaddr As String
Dim Wmimac As New System.Management.ManagementObjectSearcher("SELECT  * FROM Win32_NetworkAdapterConfiguration")
 
For Each WmiObj As ManagementObject In Wmimac.Get
    If CBool(WmiObj("IPEnabled")) Then
        macaddr = WmiObj("MACAddress")
    End If
Next
HwCode = Microsoft.VisualBasic.Left(Replace(macaddr, ":", ""),8))
'把验证字符串写入INI文件
WriteINI("SendMail", "CRC", Encrypt("clicvvian2010", HwCode), IniFile)

    还有一点,如果是服务器上一般不止一块网址,我使用的是最后一块网卡的地址,你也可以使用其他方式来指定某一块网卡,使用wmi来获取其他硬件信息的方法,大家可以通过google搜索,这里就不多说了。

   

1
2
3
4
5
6
7
    Private Sub sendmail_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'前面的一些代码已经删除,只留下判断过程的
                If Decrypt(GetINI("SendMail", "CRC", "", IniFile), HwCode) <> "clicvvian2010" Then
                    MessageBox.Show("注意:检测到服务器有变化,请重新输入数据库、邮件服务器密码后保存,现密码已解码出错!", "注意", MessageBoxButtons.OK, MessageBoxIcon.Information)
                End If
'
    End Sub

    处理了一个问题后,对于第二个问题也可以开始考虑,就是将解密后的密码放回文本框后,如果有心人可以接触到服务器,一样可以通过密码查看软件得到密码,因此我的处理方式是将读入的密码密文直接放到文本框中,将解密后的密码明文放到一个Public变量中,在要使用密码的地方,先进行一次判断,将变量中的密码加密后与文本框中的对比,如果一致,就使用变量中的密码,如果不一致,说明使用者修改过密码,就将文本框中的密码符值给变量然后使用,当使用者每点击一次“保存参数”时,再将加密后的密码重新放到密码框中。当然这也有问题,就是使用者修改了密码,又没有点击保存参数时,密码还是以明文方式存放在文本框中,我尝试使用了Txt_MailPwd_LostFocus和Txt_MailPwd_Validated二种方式都没能做过修改后实时变换,不知道各位有没有什么好的办法。

    与上次相比,界面做了一点点改动,增加了一个测试邮件的输入框和发送测试邮件的按钮,同时去掉了“数据库优先”选项,就直接默认为数据库的信息优先吧(指的是如果数据库中的某条记录指定了发件人姓名,就不再使用本处输入的内容),比上次的排列更好看了一些,呵呵

    原打算在这次就进入数据库设计的,看来又要推到下一次了,呵呵,我力争加大进度,在国庆长假期间完成这个小程序吧。

   




相关教程