.Net 程序集 签名工具sn.exe 密钥对SNK文件 最基本的用法
.Net 程序集 签名工具sn.exe 密钥对SNK文件 最基本的用法
阐述签名工具这个概念之前,我先说说它不是什么:
1.它不是用于给程序集加密的工具,它与阻止Reflector或ILSpy对程序集进行反编译一毛钱关系都没有。
2.它很讨厌人们把它和加密联系在一起。
我再说说它是什么:
1.起个大名字
sn是strong name的缩写,正如其名,sn.exe的目的是给程序集起一个唯一的名字(Hash+name+version+culture),即签名,保证不会让两个不同的DLL重名(就跟身份证不能重一样)
2.让调用者识别被调用的DLL是否被篡改
这一点是我重点要说的,我们先假设一个场景。一个黑客正在用一个银行客户端程序给一个账户转账,通过反编译发现,前端主程序Bank.exe调用Transfer.dll的TransferMoney方法来进行转账操作。他自然地想到,如果我修改了TransferMoney方法,将金额参数乘以10,然后重新编译成Transfer.dll再覆盖原来的Transfer.dll,他就能成为宇宙首富!
由这个例子得知,我们亟待解决的安全问题是,让Bank.exe有能力得知其调用的Transfer.dll是不是被别人篡改过的。那么我们很自然地会想到,用信息摘要算法呗~(废话一下,不同内容摘要出来的128位字符串是绝对不一样的)
是的!sn起的这个大名字就是这个摘要信息,我们可以在Bank.exe的引用信息里存好了我应该调用Transfer.dll的摘要值OriHash,这样我们就可以在调用时先摘要一下当前的Transfer.dll得出CurHash,然后比对一下OriHash和CurHash即可!过程如下图:
这似乎就是正确答案了,可是...
这样做有一个问题,就是关于你预先存好的那个摘要值OriHash。有两个方面:
方面1.一旦我正常地更新了Transfer.dll, 那摘要也就变了,也就是说你要通知Bank.exe去更新OriHash。
方面2.由问题1引发的安全问题。
对于方面1,我觉得是最主要的原因,一旦Transfer.dll更新了,为了比对摘要,Bank.exe需要更新OriHash,更新100次Transfer.dll,你就要更新100次OriHash,这个做法太要命了,当然这还只是麻烦层面的问题,更严重的是,我们可以想象到由于频繁更新OriHash而引发的安全问题。
对于方面2,由于你总去更新OriHash,这个环节一旦被黑客截获,你的OriHash就不一定保证是真的了,当然我个人认为这个怀疑有点过头了,因为DLL引用信息一般都是工具自动添加的,除非你通过某种网络机制传输这个OriHash到Bank.exe所属的项目文件(如Bank.csPRoj),此时OriHash就有了更多的被黑风险,这是一个安全问题!
所以基于以上两点,.Net拿出了非对称加密算法这把利剑!(再废话一下,非对称加密即生成一对儿钥匙——私钥和公钥,明文被该私钥加密只能用该公钥解密,反之亦然,所以私钥自己要绝对保密,公钥可以给任何人)
将OriHash用私钥加密为CodedHash,并存储在Transfer.dll中。然后在Bank.exe里一次性存入这个为了调用Transfer.dll的公钥,但一定要意识到,Transfer.dll里面是绝对没有私钥的,私钥只包含在myPair.snk文件中,也只有你拿着,并配以荷枪实弹的保安们誓死保护不被别人拿走。
那么新的验证流程如下,请结合下图:
a.首先Bank.exe里含有公钥,Transfer.dll里含有CodedHash
b.Bank.exe用公钥解密CodedHash
c1.若解密成功,就证明这个CodedHash是用和我公钥配对的那个私钥加密的。
c11.对Transfer.dll进行摘要得到CurHash,将其和解密的Hash对比,如果一样则证明Transfer.dll是安全的,并且是完整的,加载即可。如果不同,则证明这个Transfer.dll是别人篡改过的,此时拒绝加载Transfer.dll
c2.若解密失败,则证明这个CodedHash是用别的私钥加密的,也就是黑客弄的,此时同样拒绝加载Transfer.dll
如下图:
这样的流程解决了之前的两个问题,即当Transfer.dll正常更新时,不用再把新的Hash传给Bank.exe了,Bank.exe只需用公钥去解密CodedHash便可知晓这东西是谁发的,并通过解密了的Hash和实时计算的Transfer.dll的Hash进行对比。这里我们逻辑上有个假定,那就是Bank.exe里的那个为了调用Transfer.dll的那个公钥是真的。如果你说这个被黑了怎么办,那我只能说,他都能改你这个Bank.exe里的公钥信息了,他就想改什么改什么了,你就束手就缚吧。另外如果你说,我们既然之前说怕传给Bank.exe的OriHash有假,那你为什么不怕传给Bank.exe的公钥有假?我只能说,你的逻辑很清晰,的确,我们无法保证,但我要说的是毕竟公钥只传一次,而OriHash有可能需要多次传输,风险概率不一样,如果我们假定Transfer.dll永不改变,也就是说OriHash也只用传一次,那我可以说你用不用非对称加密算法都是一样的,因为保证第一次给的OriHash是真的和保证第一次给的公钥是真的,这俩的风险系数是一样的。不知道这么说,大家能不能明白我的意思,我也是绕了好久才捋顺的。
我们可以再假设一遍黑客可能进行的攻击方法:(注意前提是黑客不能动Bank.exe以及内部的PubKey1)
攻击1:改动并覆盖了我的Transfer.dll,但CodedHash是一模一样的
这种情况下,对Transfer.dll摘要的Hash和PubKey1对CodedHash解密的结果Hash是不一样的,成功破案!
攻击2:改动并覆盖了我的Transfer.dll,黑客自己生成了公钥私钥对,并对Transfer.dll的Hash用私钥加密成CodedHash.
这种情况下,当我们用PubKey1解密CodedHash时,由于公钥PubKey1和黑客的私钥不成对,解密时解不开的,成功破案!
攻击3:改动并覆盖了我的Transfer.dll,而Transfer.dll根本不是签名编译的。
这种情况下,结果和前面两个是一样的,成功破案!
攻击4:黑客兄由于拿不着你的私钥,表示没有其它攻击方案了,T_T。
至此,原理总算是说明白了。下面我就介绍如何利用sn.exe来办这些事儿了。
我很讨厌MS把这么多重要的任务都包装在了sn.exe里,首先我们有两个工程Bank.csproj和Transfer.csproj,我们要做的就是实现上图的流程。
1.用sn.exe生成公钥私钥对文件myPair.snk。如下图:(你要誓死保护好myPair.snk,因为里面有私钥,且这也是私钥唯一存在的地方)
2.在Transfer工程中,将myPair.snk的路径填到AssemblyInfo.cs文件中,如下图:
3.编译Transfer工程,生成Transfer.dll。此时,编译器瞬间完成了对Transfer.dll的Hash,并用myPair.snk中的私钥加密了Hash成CodedHash,并将CodedHash存入Transfer.dll中,同时公钥信息也存入了Transfer.dll中(注意绝对没有私钥)。
4.在Bank工程中,重新引用Transfer.dll文件,重新编译Bank.exe,编译时编译器将提取Transfer.dll中的公钥PubKey1,并将其写在引用Dll的描述中。当我们用反编译工具查看Bank.exe时,我们可以查看到如下信息:
// Transfer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=3d5e0147c186af58
PublicKeyToken即是我们所说的公钥PubKey1。
5.此时大功告成,无论有人用自己的私钥编译篡改了的Transfer.dll或者根本就没有用私钥签名编译,运行Bank.exe时会抛出如下信息:
这便是最基本的应用了,我也是参考了不少文章,其中比较重要的是http://www.windowsdevcenter.com/pub/a/dotnet/2003/04/28/strongnaming.html,该文章还介绍了延迟签名等机制,有兴趣的朋友可以看看。
在这里再次说明,sn.exe并不能对程序集加密阻止反编译,只是让调用者知道调用对象是不是想要的,仅此而已。
另外,HTTPS也是用的类似的方案来判断对方是不是自己人的,只是多了一个用对称加密算法加密数据包这一步,其它和这个都是一样的。