建议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 = ;
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个建议》陆敏技

编写高质量代码改善C#程序的157个建议——建议118:使用SecureString保存密钥等机密字符串的更多相关文章

  1. 编写高质量代码改善C#程序的157个建议[1-3]

    原文:编写高质量代码改善C#程序的157个建议[1-3] 前言 本文主要来学习记录前三个建议. 建议1.正确操作字符串 建议2.使用默认转型方法 建议3.区别对待强制转换与as和is 其中有很多需要理 ...

  2. 读书--编写高质量代码 改善C#程序的157个建议

    最近读了陆敏技写的一本书<<编写高质量代码  改善C#程序的157个建议>>书写的很好.我还看了他的博客http://www.cnblogs.com/luminji . 前面部 ...

  3. 编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

    建议157:从写第一个界面开始,就进行自动化测试 如果说单元测试是白盒测试,那么自动化测试就是黑盒测试.黑盒测试要求捕捉界面上的控件句柄,并对其进行编码,以达到模拟人工操作的目的.具体的自动化测试请学 ...

  4. 编写高质量代码改善C#程序的157个建议——建议156:利用特性为应用程序提供多个版本

    建议156:利用特性为应用程序提供多个版本 基于如下理由,需要为应用程序提供多个版本: 应用程序有体验版和完整功能版. 应用程序在迭代过程中需要屏蔽一些不成熟的功能. 假设我们的应用程序共有两类功能: ...

  5. 编写高质量代码改善C#程序的157个建议——建议155:随生产代码一起提交单元测试代码

    建议155:随生产代码一起提交单元测试代码 首先提出一个问题:我们害怕修改代码吗?是否曾经无数次面对乱糟糟的代码,下决心进行重构,然后在一个月后的某个周一,却收到来自测试版的报告:新的版本,没有之前的 ...

  6. 编写高质量代码改善C#程序的157个建议——建议154:不要过度设计,在敏捷中体会重构的乐趣

    建议154:不要过度设计,在敏捷中体会重构的乐趣 有时候,我们不得不随时更改软件的设计: 如果项目是针对某个大型机构的,不同级别的软件使用者,会提出不同的需求,或者随着关键岗位人员的更替,需求也会随个 ...

  7. 编写高质量代码改善C#程序的157个建议——建议153:若抛出异常,则必须要注释

    建议153:若抛出异常,则必须要注释 有一种必须加注释的场景,即使异常.如果API抛出异常,则必须给出注释.调用者必须通过注释才能知道如何处理那些专有的异常.通常,即便良好的命名也不可能告诉我们方法会 ...

  8. 编写高质量代码改善C#程序的157个建议——建议152:最少,甚至是不要注释

    建议152:最少,甚至是不要注释 以往,我们在代码中不写上几行注释,就会被认为是钟不负责任的态度.现在,这种观点正在改变.试想,如果我们所有的命名全部采用有意义的单词或词组,注释还有多少存在的价值. ...

  9. 编写高质量代码改善C#程序的157个建议——建议151:使用事件访问器替换公开的事件成员变量

    建议151:使用事件访问器替换公开的事件成员变量 事件访问器包含两部分内容:添加访问器和删除访问器.如果涉及公开的事件字段,应该始终使用事件访问器.代码如下所示: class SampleClass ...

  10. 编写高质量代码改善C#程序的157个建议——建议150:使用匿名方法、Lambda表达式代替方法

    建议150:使用匿名方法.Lambda表达式代替方法 方法体如果过小(如小于3行),专门为此定义一个方法就会显得过于繁琐.比如: static void SampeMethod() { List< ...

随机推荐

  1. 遇到版本兼容问题时 Newtonsoft.Json

    <runtime> <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1"> <depen ...

  2. openstack网络架构(nova-network/neutron)

    openstack网络体系中,网络技术没有创新,但用到的技术点非常庞杂,包括bridge.vlan.gre.vxlan.ovs.openflow.sdn.iptables等,当然这里不会做具体技术介绍 ...

  3. 热门数据挖掘模型应用入门(一): LASSO回归

    热门数据挖掘模型应用入门(一): LASSO回归 2016-10-10 20:46 作者简介: 侯澄钧,毕业于俄亥俄州立大学运筹学博士项目, 目前在美国从事个人保险产品(Personal Line)相 ...

  4. react-router4 嵌套路由

    先直接贴代码 import React from 'react'; import ReactDOM from 'react-dom'; import { HashRouter as Router, R ...

  5. 概述XML

    xml概述--->干什么的 存储一对多的数据 作为配置文件存储数据 xml组成---->怎么用 元素的分类 包含标签体的标签(有开始标签和结束标签) 例如: <student> ...

  6. Python学习日记(一)——IDLE、运算符

    环境:win8.1+python2.7.8 一.名词解释: 1.IDLE:经常编程的同学相信对集成开发环境(Integrated Development Environment,IDE)应该非常熟悉了 ...

  7. 导出ppt中所有文本框

    打开PPT,按ALT+F11打开VBA编辑器,(部分电脑FN+ALT+F11)在左面的工程视图里点击右键,选择插入->模块,添加一个模块,名字都不用改. 然后点击顶部的"工具" ...

  8. 开发团队(Team)的主要职责和特征

    角色介绍 开发团队是Scrum团队的三个角色之一. 开发团队包括架构师.开发工程师.测试人员.数据库管理员和UI设计师等,这几类人的跨职能组合.具备的技能足以实现产品开发. Team的主要职责 1.S ...

  9. 【原】Coursera—Andrew Ng机器学习—课程笔记 Lecture 11—Machine Learning System Design 机器学习系统设计

    Lecture 11—Machine Learning System Design 11.1 垃圾邮件分类 本章中用一个实际例子: 垃圾邮件Spam的分类 来描述机器学习系统设计方法.首先来看两封邮件 ...

  10. java 内存溢出

    不健壮代码的特征及解决办法 1.尽早释放无用对象的引用.好的办法是使用临时变量的时候,让引用变量在退出活动域后,自动设置为null,暗示垃圾收集器来收集该对象,防止发生内存泄露. 对于仍然有指针指向的 ...