建议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:\Windows\Microsoft.NET\Framework\v4.0.30319\sos.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个建议》陆敏技

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

  1. Android使用C/C++来保存密钥

    Android使用C/C++来保存密钥 本文主要介绍如何通过native方法调用取出密钥,以替代原本直接写在Java中,或写在gradle脚本中的不安全方式. 为什么要这么做 如果需要在本地存储一个密 ...

  2. java appkey_1.新建Android studio工程2.新建class:AppKey.java.主要为了保存密钥代码块package com...adminap...

    1.新建Android studio工程 2.新建class:AppKey.java.主要为了保存密钥 代码块 package com...adminapp.lib.utils.jni; /** * ...

  3. 乐鑫esp8266学习rtos3.0笔记第3篇: 一篇文章带你搞掂存储技术 NVS 的认识和使用,如何利用NVS保存整型、字符串、数组以及结构体。(附带demo)

    本系列博客学习由非官方人员 半颗心脏 潜心所力所写,不做开发板.仅仅做个人技术交流分享,不做任何商业用途.如有不对之处,请留言,本人及时更改. 1. Esp8266之 搭建开发环境,开始一个" ...

  4. JAVA保存数据库前验证字符串长度

    1.如果数据库保存的是char.varchar的字符类型,则使用以下方法获取字符串长度 int len =  a.length(); if(len > MAX_SIZE){ .... } 2.获 ...

  5. 【转载保存】linux shell字符串切割成数组

    原地址:https://www.cnblogs.com/FlyFive/p/3640243.html a="one,two,three,four" 要将$a分割开,可以这样: 按 ...

  6. flask带有传入参数既有URL还有其他参数类型:建议使用方式二(flask 使用查询字符串的方式)

    方式一: 例如此时,既需要传递带有URL的训练数据地址,还需要传入其他的参数 由于path类型会将所有的 / 不忽略,那么此时的 traget_cls 参数会被忽略,于是我们将带有URL的参数(即pa ...

  7. pandas 字符串切片后保存_我擦~字符串转字节切片后,切片的容量竟然千奇百怪...

    以下文章来源于新世界杂货铺 ,作者许文 新世界杂货铺 作为一名Gopher, 我愿称之为Go的干(杂)货铺子! 神奇的现象 切片, 切片, 又是切片! 今天遇到的神奇问题和切片有关, 具体怎么个神奇法 ...

  8. pandas 字符串切片后保存_pandas:快速处理字符串方法

    前言 当我们遇到一个超级大的DataFrame,里面有一列类型为字符串,要将每一行的字符串都用同一方式进行处理,一般会想到遍历整合DataFrame,但是如果直接这样做的话将会耗费很长时间,有时几个小 ...

  9. 已知某一天的日出时间和日落时间,分别保存在两个字符串中,求这个白天一共有几小时几分

    public static void main(String []args) { String riChu = "下午01:20"; String riLuo = "下午 ...

最新文章

  1. 房价预测-paddle 实现
  2. 基于SpringBoot的个人博客系统
  3. Linux futex 快速同步互斥机制简介
  4. WebAssembly 系列(一):生动形象地介绍 WebAssembly
  5. 阿里云 OpenYurt 成为 CNCF 沙箱项目,加速原生 Kubernetes 边缘场景全覆盖
  6. UI设计师应了解最终用户的十件事
  7. 鸿蒙10 5G手机,继鸿蒙后麒麟V10问世 5G时代国产操作系统将起飞
  8. 深度学习之目标检测:R-CNN、Fast R-CNN、Faster R-CNN
  9. 【LeetCode笔记】253. 会议室 II(Java、偏数学)
  10. 从0开始学习自动化测试框架cypress(五)案例
  11. centos7 docker端口_使用Docker部署Python应用
  12. 《数字电子技术基础》3.3 CMOS门电路(下)
  13. Frodo and pillows
  14. 数学知识都是计算机,数学在计算机的作用
  15. 西湖大学鞠峰组招聘【塑料降解 / 污水工程 / 微生物学】方向博士后和科研助理...
  16. 向西,向西,到栖霞去(二)--走马看福山
  17. ThreadLocal 是什么?有哪些使用场景?
  18. 虚拟服务器关机之后怎么开,云服务器关机了怎么开机
  19. 并不对劲的方格取数问题
  20. airplay 安卓接收_在没有Apple TV的情况下将AirPlay添加到接收器-Raspbmc和Raspberry Pi

热门文章

  1. harris角点匹配 matlab,基于Harris角点的图像匹配算法
  2. 计算机网络体系结构 - 网络安全
  3. Win7 64位IIS集成php(独创)
  4. 安全生产双重预防体系建设数字化解决方案
  5. JS正则表达式数字、字母、特殊符号第一弹
  6. Dvwa之暴力破解全级别学习笔记
  7. c语言存储类型关键字作用,C语言, 存储类型关键字?
  8. 简述C语言中32个关键字
  9. 【HTML】简单的书签式菜单选择设置
  10. html5 加速球 效果,css 渐隐渐现、echarts 圆环图、百度地图覆盖物、echarts水球图(360加速球效果)...