C#开发用于计算安全访问算法Key值的上位机

1.创建C#工程

1.创建C# Windows窗体应用
在这里插入图片描述
2.创建项目
输入工程名,选择存放位置
在这里插入图片描述
选择.NET Core 3.1框架,点击创建即工程创建完成
在这里插入图片描述

2.搭建框架

创建完工程后,双击Form1.cs,打开Form窗体,在左侧出现的工具栏里面找到公共控件
在这里插入图片描述
若无工具箱,则点击视图中工具箱即可
在这里插入图片描述
双击工具栏中控件或者拖动控件到Form窗体,并对控件进行配置,如单击Form窗体中Label控件,输入name及text,再双击Form窗体中的Label控件图标,即可进入控件代码界面
在这里插入图片描述
在这里插入图片描述
将控件在Form窗体中布局好
在这里插入图片描述

3. 功能实现

1.dll文件调用

调试时出现如下错误,参考这篇 博文 解决

未处理System.BadImageFormatException,试图加载格式不正确的程序。 (异常来自 HRESULT:0x8007000B)

删除dll文件时显示没有权限访问文件(这里改了四次,最后才想到真正的原因,泪目<_>,这里也做个记录吧)

:“Access to the path 'F:\DLL\SeedToKey\SeedToKey\bin\Debug\netcoreapp3.1\GenerateKeyExImpl.dll' is denied.

在这里插入图片描述

(第一次修改 )这里改成默认以管理员权限运行,参考博文.

[STAThread]
        static void Main()
        {
            /*
            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new SeedToKey());*/

            Application.SetHighDpiMode(HighDpiMode.SystemAware);
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);

            /**
            * 当前用户是管理员的时候,直接启动应用程序
            * 如果不是管理员,则使用启动对象启动程序,以确保使用管理员身份运行
            */
            //获得当前登录的Windows用户标示
            System.Security.Principal.WindowsIdentity identity = System.Security.Principal.WindowsIdentity.GetCurrent();
            System.Security.Principal.WindowsPrincipal principal = new System.Security.Principal.WindowsPrincipal(identity);
            //判断当前登录用户是否为管理员
            if (principal.IsInRole(System.Security.Principal.WindowsBuiltInRole.Administrator))
            {
                //如果是管理员,则直接运行
                Application.Run(new SeedToKey());
            }
            else
            {
                //创建启动对象
                System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo();
                startInfo.UseShellExecute = true;
                startInfo.WorkingDirectory = Environment.CurrentDirectory;
                startInfo.FileName = Application.ExecutablePath;
                //设置启动动作,确保以管理员身份运行
                startInfo.Verb = "runas";
                try
                {
                    System.Diagnostics.Process.Start(startInfo);
                }
                catch
                {
                    return;
                }
                //退出
                Application.Exit();
            }
        }

(第二次修改)修改后发现还是会出现这种情况,排查后确定错误位置并修改,直接删除就OK(这里有点莫名自信了啊)
在这里插入图片描述
在这里插入图片描述
(第三次修改)后期发现都不是前面的原因,想了很久才想到是因为,点击计算后,就会开始调用dll文件里面的函数,dll文件被正在执行的程序占用,再次点击dll文件选择按钮调用同名文件,因文件占用不能进行删除或者覆盖,导致出错。
解决方法:在调用完后将dll文件释放,这里就需要将静态调用更改成动态调用。
在这里插入图片描述

public unsafe delegate VKeyGenResultEx calculateKey(byte[] ipSeedArray,
                                            uint iSeedArraySize,
                                            uint iSecurityLevel,
                                            char* ipVariant,
                                            char* ipOptions,
                                            byte[] iopKeyArray,
                                            uint iMaxKeyArraySize,
                                            uint oActualKeyArraySize);
public static class NativeMethod
{
    [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
    public static extern int LoadLibrary(
        [MarshalAs(UnmanagedType.LPStr)] string lpLibFileName);

    [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
    public static extern IntPtr GetProcAddress(int hModule,
        [MarshalAs(UnmanagedType.LPStr)] string lpProcName);

    [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
    public static extern bool FreeLibrary(int hModule);
}

点击计算button调用dll文件,在CalcuButton_Click() 函数中加载dll

//1. 动态加载C++ Dll
int hModule = NativeMethod.LoadLibrary(@".\dll\GenerateKeyExImpl.dll");
if (hModule == 0) return;

//2. 读取函数指针
IntPtr intPtr = NativeMethod.GetProcAddress(hModule, "GenerateKeyExOpt");

//3. 将函数指针封装成委托
calculateKey GenerateKeyExOptFun = (calculateKey)Marshal.GetDelegateForFunctionPointer(intPtr, typeof(calculateKey));

//4.调用函数
GenerateKeyExOptFun(GenerateKeyExOptPara.ipSeedArray, GenerateKeyExOptPara.iSeedArraySize,
                GenerateKeyExOptPara.iSecurityLevel, null, null, GenerateKeyExOptPara.iopKeyArray,
                GenerateKeyExOptPara.iMaxKeyArraySize, GenerateKeyExOptPara.oActualKeyArraySize);

在这里插入图片描述
调用完,将Dll文件释放。
在这里插入图片描述

其余参考博文:
C# 打开文件.
C# 拷贝文件.
C# 调用dll文件.
C# 动态调用dll文件.
C# 把dll放在不同的目录让你的程序更整洁.
C# 创建子文件夹文件.
C# 创建普通文件夹.

计划实现当前窗体关闭时,删除相应dll文件,感觉还是在选择dll文件时删除已经存在的dll文件可能更好,此功能没有实现,这里只是记录一下。
在这里插入图片描述
在这里插入图片描述

2.字节大小和安全等级可进行选择

可参考
combobox控件如何添加下拉内容.
C#使用 ComboBox 控件.

注意:
行为里的DrawMode为Normoal,选择其他需自己选择字体颜色,否则会跟背景一个颜色,看不见字

下拉选择控件实现放在窗体Load里面

private void SeedToKey_Load(object sender, EventArgs e)
        {
            _ = ByteSelect.Items.Add("2"); //对应ByteSelect.SelectedIndex == 0
            _ = ByteSelect.Items.Add("4"); //对应ByteSelect.SelectedIndex == 1
            _ = ByteSelect.SelectedIndex = 0;  //默认的下拉框选项

            _ = securityLevelComboBox.Items.Add(1);
            _ = securityLevelComboBox.Items.Add(2);
            _ = securityLevelComboBox.Items.Add(3);
            _ = securityLevelComboBox.SelectedIndex = 0;  //默认的下拉框选项
        }

1.字节选择

private void ByteSelect_SelectedIndexChanged(object sender, EventArgs e)
        {
            /*
            switch (ByteSelect.SelectedItem.ToString()) //获取选择的内容
            {
                case "2": MessageBox.Show("A"); break;
                case "4": MessageBox.Show("B"); break;
            }*/

            if (ByteSelect.SelectedIndex == 0)
            {
                GenerateKeyExOptPara.iSeedArraySize = 2;
            }

            if (ByteSelect.SelectedIndex == 1)
            {
                GenerateKeyExOptPara.iSeedArraySize = 4;
            }

            ClearSeedAndKey(); //字节选择发生改变时,将Seed和Key清除
        }

3.输入种子

private void InputSeed_TextChanged(object sender, EventArgs e)
{
    if (GenerateKeyExOptPara.iSeedArraySize == 2)
    {
        InputSeed.MaxLength = 6; //字节为2时,最大只能输入6个(如:0xAAAA,2+2*2)
    }

    if (GenerateKeyExOptPara.iSeedArraySize == 4)
    {
        InputSeed.MaxLength = 10; //字节为4时,最大只能输入10个(如:0xAAAA,2+4*2)
    }
    //KeyValue.Text = InputSeed.ToString(); //会提取到输入框的地址加输入框中的数据,用下面方法可解决
    //KeyValue.Text = InputSeed.ToString().Remove(0, 36);

    //GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Substring(InputSeed.ToString().Length - 8);
    GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Replace("System.Windows.Forms.TextBox, Text: ", "");  
    //KeyValue.Text = GenerateKeyExOptPara.ipSeedStr; //测试提取到的值
    GenerateKeyExOptPara.ipSeedArray = Encoding.UTF8.GetBytes(GenerateKeyExOptPara.ipSeedStr);

    GenerateKeyExOptPara.ipSeedArray = ASCALLTOHex(GenerateKeyExOptPara.ipSeedArray);
    //加InputSeed.TextLength==6判断是如果输入满6才能进入,不加判断就会每输入一次进入一次
    if ((GenerateKeyExOptPara.iSeedArraySize==2) && ((InputSeed.TextLength==4) || (InputSeed.TextLength==6)) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
    {
        if (InputSeed.TextLength == 6) //有0x,将0x覆盖掉
        {
            for (byte i = 0; i < 4; i++)
            {
                GenerateKeyExOptPara.ipSeedArray[i] = GenerateKeyExOptPara.ipSeedArray[i+2];
            }
        }

        GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
        GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
    }

    if ((GenerateKeyExOptPara.iSeedArraySize==4) && ((InputSeed.TextLength==8) || (InputSeed.TextLength==10)) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
    {
        if (InputSeed.TextLength == 10)
        {
            for (byte i = 0; i < 8; i++)
            {
                GenerateKeyExOptPara.ipSeedArray[i] = GenerateKeyExOptPara.ipSeedArray[i + 2];
            }
        }

        GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
        GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
        GenerateKeyExOptPara.ipSeedArray[2] = (byte)((GenerateKeyExOptPara.ipSeedArray[4] << 4) + GenerateKeyExOptPara.ipSeedArray[5]);
        GenerateKeyExOptPara.ipSeedArray[3] = (byte)((GenerateKeyExOptPara.ipSeedArray[6] << 4) + GenerateKeyExOptPara.ipSeedArray[7]);
    }
}          

注意:
1.由于InputSeed.ToString() 提取到的字符串前面有很多存放地址,所以需要提取出有用的信息

System.Windows.Forms.TextBox, Text: 12

在这里插入图片描述
使用Substring(str.length()-8) 即可提取从字符串从右向左8个字符,其它提取方法参考博文.但是在后期调试是发现输入不足8个时,会将前面信息显示出来,于是改用
GenerateKeyExOptPara.ipSeedStr = InputSeed.ToString().Replace("System.Windows.Forms.TextBox, Text: ", "");提取输入的数据
2.combox输入的是字符型的数据,需要转换成十六进制或者十进制,调试需要对照ASCll码表.
3.调试过程中发现转换后的数据显示的是ASCLL码对应16进制,"a"的16进制为61,而不是想要的十六进制a,导致计算出错。并发现输入一个字符就会占据一个byte位。
在这里插入图片描述
4.自己写了一个转换函数 ASCALLTOHex() 将字符转换成16进制,转换后效果如下:

private byte[] ASCALLTOHex(byte[] ascallArr)
        {
            byte ascallArr0To9 = 0x30; //字符"0"的16进制
            byte hexArr0To9 = 0x00; //16进制0
            byte ascallArraTof = 0x41; //字符"a"的16进制
            byte ascallArrAToF = 0x61; //字符"A"的16进制
            byte hexArrAToF = 0x0a; //16进制a

            for (byte i=0; i< ascallArr.Length; i++)
            {
                for (byte j = 0; j < 10; j++)
                {
                    if (ascallArr[i] == ascallArr0To9++)
                    {
                        ascallArr[i] = hexArr0To9;
                    }

                    hexArr0To9++;
                }
                ascallArr0To9 = 0x30;
                hexArr0To9 = 0x00;

                for (byte j = 0; j < 6; j++)
                {
                    if (ascallArr[i] == ascallArraTof++)
                    {
                        ascallArr[i] = hexArrAToF;
                    }

                    hexArrAToF++;
                }
                ascallArraTof = 0x41;
                hexArrAToF = 0x0a;

                for (byte j = 0; j < 6; j++)
                {
                    if (ascallArr[i]==ascallArrAToF++)
                    {
                        ascallArr[i] = hexArrAToF;
                    }

                    hexArrAToF++;
                }
                ascallArrAToF = 0x61;
                hexArrAToF = 0x0a;
            }

            return ascallArr;
        }

在这里插入图片描述
在这里插入图片描述
我想要的数据是0xAAAA,而这里显然不是想要的结果,再将数据进行处理一下,处理过程中发现需要输入完全后再进行数据处理,不加判断条件,每输入一次会进来处理数据,如只输入一个A,进来处理数据就会出错,因目前数组只有一个GenerateKeyExOptPara.ipSeedArray[0]数据,而对数据处理包含对GenerateKeyExOptPara.ipSeedArray[1]等的处理,导致报错,后期实现输入数据是否匹配字节大小判断

//加InputSeed.TextLength==6判断是如果输入满6才能进入,不加判断就会每输入一次进入一次
            if ((GenerateKeyExOptPara.iSeedArraySize==2) && (InputSeed.TextLength==6) && (GenerateKeyExOptPara.ipSeedArray.Length!=0))
            {
                GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
                GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
            }

            if ((GenerateKeyExOptPara.iSeedArraySize == 4) && (InputSeed.TextLength == 10) && (GenerateKeyExOptPara.ipSeedArray.Length != 0))
            {
                GenerateKeyExOptPara.ipSeedArray[0] = (byte)((GenerateKeyExOptPara.ipSeedArray[0] << 4) + GenerateKeyExOptPara.ipSeedArray[1]);
                GenerateKeyExOptPara.ipSeedArray[1] = (byte)((GenerateKeyExOptPara.ipSeedArray[2] << 4) + GenerateKeyExOptPara.ipSeedArray[3]);
                GenerateKeyExOptPara.ipSeedArray[2] = (byte)((GenerateKeyExOptPara.ipSeedArray[4] << 4) + GenerateKeyExOptPara.ipSeedArray[5]);
                GenerateKeyExOptPara.ipSeedArray[3] = (byte)((GenerateKeyExOptPara.ipSeedArray[6] << 4) + GenerateKeyExOptPara.ipSeedArray[7]);
            }

处理后效果:
在这里插入图片描述

4.通过点击计算Button,计算出Key值并显示

1.使用DllImport引用dll文件
在这里插入图片描述
在窗体类public partial class SeedToKey : Form的最前面使用DllImport函数,尝试过放在后面会报错

[DllImport("GenerateKeyExImpl.dll", EntryPoint = "GenerateKeyExOpt")]
static extern unsafe VKeyGenResultEx GenerateKeyExOpt(byte[] ipSeedArray,
	                                     uint iSeedArraySize,
	                                     uint iSecurityLevel,
	                                     char* ipVariant,
	                                     char* ipOptions,
	                                     byte[] iopKeyArray,
	                                     uint iMaxKeyArraySize,
	                                     uint oActualKeyArraySize);

注意:
1.GenerateKeyExImpl.dll是要调用的dll文件,EntryPoint = "GenerateKeyExOpt"是只这里要使用GenerateKeyExImpl.dll文件中的GenerateKeyExOpt函数,这里参照了一篇博文
2.C#中使用指针需要加unsafe关键字,在调试过程中,出现以下错误,参考这篇 博文 解决

C#:不安全代码只会在使用 /unsafe 编译的情况下出现

3.因byte和byte[]在后面函数赋值时无法转换,这里直接写成byte[],和dll文件中的函数原型不一致,但实质是一样的,都是unsigned char类型的
4.C#数组定义参考博文.

KEYGENALGO_API VKeyGenResultEx GenerateKeyExOpt(
	const unsigned char* ipSeedArray,          /* Array for the seed [in] */
	unsigned int iSeedArraySize,               /* Length of the array for the seed [in] */
	const unsigned int iSecurityLevel,         /* Security level [in] */
	const char* ipVariant,                     /* Name of the active variant [in] */
	const char* ipOptions,                     /* Optional parameter which might be used for OEM specific information [in]*/
	unsigned char* iopKeyArray,                /* Array for the key [in, out] */
	unsigned int iMaxKeyArraySize,             /* Maximum length of the array for the key [in] */
	unsigned int oActualKeyArraySize)         /* Length of the key [out] */

4.C#的数据类型和C++的不一致,如C#的byte在C++中为BYTE,具体对应关系可以参照MSDN和一篇博文.

在Button事件中添加代码

if (securityLevelComboBox.SelectedIndex == 0)
            {
                GenerateKeyExOptPara.iSecurityLevel = 1;
            }

            if (securityLevelComboBox.SelectedIndex == 1)
            {
                GenerateKeyExOptPara.iSecurityLevel = 2;
            }

            if (securityLevelComboBox.SelectedIndex == 2)
            {
                GenerateKeyExOptPara.iSecurityLevel = 3;
            }

            GenerateKeyExOptPara.oActualKeyArraySize = GenerateKeyExOptPara.iSeedArraySize;

            GenerateKeyExOpt(GenerateKeyExOptPara.ipSeedArray, GenerateKeyExOptPara.iSeedArraySize,
                GenerateKeyExOptPara.iSecurityLevel, null, null, GenerateKeyExOptPara.iopKeyArray,
                GenerateKeyExOptPara.iMaxKeyArraySize, GenerateKeyExOptPara.oActualKeyArraySize);

            if (GenerateKeyExOptPara.iSeedArraySize == 2)
            {
                KeyValue.Text = "0X" + Convert.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16).ToUpper().PadLeft(2, '0') +
                Convert.ToString(GenerateKeyExOptPara.iopKeyArray[1], 16).ToUpper().PadLeft(2, '0');
            }

            if (GenerateKeyExOptPara.iSeedArraySize == 4)
            {
                KeyValue.Text = "0X" + Convert.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16).ToUpper().PadLeft(2, '0') +
                Convert.ToString(GenerateKeyExOptPara.iopKeyArray[1], 16).ToUpper().PadLeft(2, '0') +
                Convert.ToString(GenerateKeyExOptPara.iopKeyArray[2], 16).ToUpper().PadLeft(2, '0') +
                Convert.ToString(GenerateKeyExOptPara.iopKeyArray[3], 16).ToUpper().PadLeft(2, '0');
            }

由于计算出来的Key值是byte[]类型的,需要转换成combox控件的string类型

Convert.ToString(GenerateKeyExOptPara.iopKeyArray[3], 16).ToUpper().PadLeft(2, '0');

注意:
1.ToString(GenerateKeyExOptPara.iopKeyArray[0], 16) 是将byte[]类型转换成string类型,并且将十进制的数据转换成十六进制
2.ToUpper() 是返回大写格式
3.PadLeft(2, ‘0’) 表示检查字符串长度是否少于2位,若少于2位,则自动在其左侧以’0’补足。 参考博文.
4.C#中字符串拼接可用“+”号运算符,参考一篇博文.
5.有地方需要将byte[]类型和string类型相互转换,参考博文.

5.给应用程序添加图标

参考 给C#应用程序添加应用图标.

第一次尝试用C#写上位机,磕磕绊绊弄出来了,后面有时间再看看。
源码放在Gitee了,有需要的话也可以下载看看

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值