zoukankan      html  css  js  c++  java
  • 编写高质量代码改善C#程序的157个建议——建议118:使用SecureString保存密钥等机密字符串

    建议118:使用SecureString保存密钥等机密字符串

    托管代码中的字符串是一类特殊的对象,它们不可用被改变。每次使用System.String类张的方法之一时,或者使用此类型进行运算时(如赋值、拼接等),都要在内存中创建新的字符串对象,也就是为该新对象分配新的空间。这就带来了两个问题:

    1. 原来的字符串是不是还在内存当中?
    2. 如果在内存当中,那么机密数据(如密码)该如何保存才足够安全?

    针对第一个问题,我们来看一段代码:

            static void Method1()
            {
                string str = "liming";
                Console.WriteLine(str);
            }
    
            static void Main(string[] args)
            {
                Method1(); //在此处打上断点
                Console.ReadKey();
            }

    在Method1方法处打上断点。在VS中让程序执行到此处,在即时窗口中相继运行命令:

    .load sos.dll

    !dso

    运行结果:

    .load sos.dll
    已加载扩展 G:WindowsMicrosoft.NETFrameworkv4.0.30319sos.dll
    !dso
    PDB symbol for clr.dll not loaded
    OS Thread Id: 0x9806c (622700)
    ESP/REG  Object   Name
    003EE700 027022a8 System.Object[]    (System.String[])
    003EE9F4 027022a8 System.Object[]    (System.String[])
    003EEDA0 027022a8 System.Object[]    (System.String[])
    003EEDB8 027022a8 System.Object[]    (System.String[])
    003EEDC4 027022a8 System.Object[]    (System.String[])
    003EEE40 027022a8 System.Object[]    (System.String[])
    003EEF9C 027022a8 System.Object[]    (System.String[])
    003EEFD4 027022a8 System.Object[]    (System.String[])
    003EF510 02701238 System.SharedStatics

    打开“调试”->“窗口”->“内存”->“内存1”窗口,找到对应Object列的内存地址"027022a8",然后在内存窗口中输入。

    由于此时还没有进入到Method1中,所以内存当中不存在字符串“liming”。接着让程序运行到方法内部,可以看到内存中应经存在“liming”了。

    这就出现了一个问题,如果有人恶意扫描你的内存,程序中所保存的机密信息将无处可逃。幸好FCL提供了System.Security.SecureString,SecureString表示一个应保密的文本,它在初始化时就已经被加密了。使用SecureString的示例如下:

            static System.Security.SecureString secureString = new System.Security.SecureString();
    
            static void Method2()
            {
                secureString.AppendChar('l');
                secureString.AppendChar('i');
                secureString.AppendChar('m');
                secureString.AppendChar('i');
                secureString.AppendChar('n');
                secureString.AppendChar('g');
            }
    
            static void Main(string[] args)
            {
                Method2(); 
                Console.ReadKey();
            }

    使用相同的调试手法可以发现,再次进入Method2后,已经找不到对应的字符串“liming”了。但是,核心数据保存问题已经解决了,可是文本总是要取出来的,只要取出来不是就会被发现吗?这个问题没法避免,但是我们可以做到文本使用完毕就释放掉,代码如下:

            static void Method3()
            {
                secureString.AppendChar('l');
                secureString.AppendChar('i');
                secureString.AppendChar('m');
                secureString.AppendChar('i');
                secureString.AppendChar('n');
                secureString.AppendChar('g');
                IntPtr addr = Marshal.SecureStringToBSTR(secureString);
                string temp = Marshal.PtrToStringBSTR(addr);
                //使用该机密文本做一些事情
                ///=======开始清理内存
                //清理掉非托管代码中对应的内存的值
                Marshal.ZeroFreeBSTR(addr);
                //清理托管代码对应的内存的值(采用重写的方法)
                int id = GetProcessID();
                byte[] writeBytes = Encoding.Unicode.GetBytes("xxxxxx");
                IntPtr intPtr = Open(id);
                unsafe
                {
                    fixed (char* c = temp)
                    {
                        WriteMemory((IntPtr)c, writeBytes, writeBytes.Length);
                    }
                }
                ///=======清理完毕
            }
    
            static PROCESS_INFORMATION processInfo = new PROCESS_INFORMATION();
    
            public static int GetProcessID()
            {
                Process p = Process.GetCurrentProcess();
                return p.Id;
            }
    
            public static IntPtr Open(int processId)
            {
                IntPtr hProcess = IntPtr.Zero;
                hProcess = ProcessAPIHelper.OpenProcess(ProcessAccessFlags.All, false, processId);
                if (hProcess == IntPtr.Zero)
                    throw new Exception("OpenProcess失败");
                processInfo.hProcess = hProcess;
                processInfo.dwProcessId = processId;
                return hProcess;
            }
    
            static int WriteMemory(IntPtr addressBase, byte[] writeBytes, int writeLength)
            {
                int reallyWriteLength = 0;
                if (!ProcessAPIHelper.WriteProcessMemory(processInfo.hProcess, addressBase, writeBytes, writeLength, out reallyWriteLength))
                {
                    throw new Exception();
                }
                return reallyWriteLength;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            internal struct PROCESS_INFORMATION
            {
                public IntPtr hProcess;
                public IntPtr hThread;
                public int dwProcessId;
                public int dwThreadId;
            }
    
            [Flags]
            enum ProcessAccessFlags : uint
            {
                All = 0x001F0FFF,
                Terminate = 0x00000001,
                CreateThread = 0x00000002,
                VMOperation = 0x00000008,
                VMRead = 0x00000010,
                VMWrite = 0x00000020,
                DupHandle = 0x00000040,
                SetInformation = 0x00000200,
                QueryInformation = 0x00000400,
                Synchronize = 0x00100000
            }
    
            static class ProcessAPIHelper
            {
                [DllImport("kernel32.dll")]
                public static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, [MarshalAs(UnmanagedType.Bool)] bool bInheritHandle, int dwProcessId);
    
                [DllImport("kernel32.dll", SetLastError = true)]
                public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
    
                [DllImport("kernel32.dll", SetLastError = true)]
                public static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, [Out] byte[] lpBuffer, int dwSize, out uint lpNumberOfBytesRead);
    
                [DllImport("kernel32.dll", SetLastError = true)]
                [return: MarshalAs(UnmanagedType.Bool)]
                public static extern bool CloseHandle(IntPtr hObject);
            }

    注意查看上文中的代码:

    IntPtr addr = Marshal.SecureStringToBSTR(secureString);
    string temp = Marshal.PtrToStringBSTR(addr);

    这两行代码表示的就是把机密文本从SecureString取出来,临时赋值给字符串temp。这里存在两个问题:第一行实际调用的是非托管代码,它在内存中也会存储一个“liming”;第二行代码会在托管内存中存储一个“liming”。这两段文本的释放方式是不一样的。前者可以通过使用下面代码释放:

    Marshal.ZeroFreeBSTR(addr);

    而托管内存中的文本,只能通过重写来完成(如上文中,就是重写成无意义的“xxxxxx”了)。当然,没有绝对的安全,因为即便如此,让关键字符串在内存中像流星一样一闪而过,它也存在被捕获的可能性。但是我们通过这种方法降低了数据被破解的概率。

    转自:《编写高质量代码改善C#程序的157个建议》陆敏技

  • 相关阅读:
    git常用指令 github版本回退 reset
    三门问题 概率论
    如何高效的学习高等数学
    数据库6 关系代数(relational algebra) 函数依赖(functional dependency)
    数据库5 索引 动态哈希(Dynamic Hashing)
    数据库4 3层结构(Three Level Architecture) DBA DML DDL DCL DQL
    梦想开始的地方
    java String字符串转对象实体类
    java 生成图片验证码
    java 对象之间相同属性进行赋值
  • 原文地址:https://www.cnblogs.com/jesselzj/p/4751198.html
Copyright © 2011-2022 走看看