布景

2022国家级护网举动行将开启,根据阿里云给出的安全建议,需求将登陆Linux的方法改为密钥对方法。我这儿运用的长途东西是自己开发的,能够同时办理Windows和Linux,可是以前不支持密钥对的登陆方法,所以需求改造一下。

护网举动是什么? 护网举动从2016年开端,是一场由公安部组织的网络安全攻防演练,意图是针对全国范围的真实网络方针为目标的实战攻防活动,旨在发现、露出和处理安全问题,查验我国各大企事业单位、部下机关的网络安全防护水平缓应急处置才干。护网举动每年举办一次,为期2-3周。

我运用的长途东西 RDManager: blog.bossma.cn/tools/new-v…,这个东西拜访Linux运用了putty,putty的密钥对登陆方法运用的是自有格局的ppk文件,可是阿里云上下载的是pem格局的密钥文件,所以需求将pem格局转化为ppk格局。

思路

putty自身提供了一个东西,能够将其他格局的密钥文件转化为自有的ppk文件,这个东西的姓名是puttygen。在linux上能够通过指令进行转化,在Windows上则有必要运用GUI东西手动操作,这多有不便。我希望的是能通过编程的方法进行这个转化,这样只需求在RDManger中上传pem文件,就能够自动转化为putty的ppk格局的文件,不需求再去运用puttygen。

首先查了下有没有现成的轮子,通过屡次寻觅,在Github上找到了一个项目:pem2ppk (github.com/akira345/pe…),这个项目看姓名就知道很贴合我的需求,它的主要功用便是读取pem文件,然后输出为ppk文件。我最终的处理方案主体也是从此而来。不过这个程序有两个问题:

  • 1、不是所有的pem文件都能转化成功,网上也是有人说成功了,有人说不可。
  • 2、不支持对密钥进行加密,别人拿走了这个ppk文件就能够直接运用。puttygen是有这个功用的。

除此之外,很难再找到比较贴合需求的资料了。怎么办?其实这个Github项意图很大一部分代码来源于另一篇文章:antonymale.co.uk/generating-…,作者说到能够去看putty的源码。

受此启示,我也能够去看putty的源码,然后将相关处理翻译为C#的完成,这样应该是能够处理问题的。

完成

putty的源码官网上就能够下载到,不过我看的是一个几年前的版别:github.com/KasperDeng/…,这个版别和新版别的主要逻辑都是相同的,搞懂C言语的若干函数和数据类型就很简单了解,而且旧版别更原始,没有那么多的抽象,反而更简单了解。

输出ppk内容不正确的问题

这个问题主要是由于填充(padding)运用不当形成的,pem2ppk项目在输出密钥的各个特点时都运用了前置填充,而putty并不是固定的都加了填充。

看putty的代码完成:github.com/KasperDeng/…

    dlen = (bignum_bitcount(rsa->private_exponent) + 8) / 8;
    plen = (bignum_bitcount(rsa->p) + 8) / 8;
    qlen = (bignum_bitcount(rsa->q) + 8) / 8;
    ulen = (bignum_bitcount(rsa->iqmp) + 8) / 8;
    bloblen = 16 + dlen + plen + qlen + ulen;

这段代码是计算密钥的各个特点的值的字节数,然后用于初始化一个大的字节数组,将这些数据写进去。bignum_bitcount是计算值的比特位数,除以8便是得到字节数,为什么还要加8呢?这是因为C言语中除法的结果是向下取整的,比如数学计算结果是1.5,那么C言语中得到的便是1,为了不让任何一个比特丢掉,所以这儿加了一个8,预留好充足的空间。

再来看pem2ppk中的完成:github.com/akira345/pe…

private const int prefixSize = 4;
private const int paddedPrefixSize = prefixSize + 1;
byte[] publicBuffer = new byte[3 + keyType.Length + paddedPrefixSize + keyParameters.Exponent.Length +
                                           paddedPrefixSize + keyParameters.Modulus.Length + 1];

这儿keyParameters.Exponent和keyParameters.Modulus是公钥的两个特点,能够看到前边加了一个固定的长度paddedPrefixSize,这个paddedPrefixSize=prefixSize + 1,这儿边的1就对应putty中的+8逻辑。

不过固定+1是有问题的,能够想一下C#和C言语在处理这些特点值时的不同。

在putty中假如数据比特数不能被8整除,那么+8之后再整除就能够得到正确的字节数,否则就会少1个字节;假如数据能被8整除,那么+8就会多1个空的字节,这个多的字节便是padding了。所以能被8整除的时分才会有这个padding。

在C#中开端处理的时分就已经都是字节了,所以C#中不需求处理位数不能被8整除的问题,可是需求在能被8整除的时分添加一个空字节,C#中怎么判别数据的位数能被8整除呢?能够认为数据首byte的最高位是1的时分,比特数就能被8整数,此刻最小二进制数是10000000,比它小的数就能够被舍弃掉至少1位。10000000也便是128,因此但凡大于等于这个数的都是能被8整数的,也便是需求padding的。

所以能够这样判别是否需求添加padding:gist.github.com/bosima/ee66…

	private static bool CheckIsNeddPadding(byte[] bytes)
	{
		return bytes[0] >= 128;
	}
	private static int GetPrefixSize(byte[] bytes)
	{
		return CheckIsNeddPadding(bytes) ? paddedPrefixSize : prefixSize;
	}

完成ppk加密

pem2ppk项目中没有对key进行加密的完成,网上也没有找到C#的源代码能够完成这个功用。可是这个功用很要害,在RDManager中所有的暗码都是加密处理的,这样服务器账号落盘的时分安全性才干有比较好的保障,可是阿里云导出的pem是没有加密的,虽然puttygen也能够给pem加密,可是还不是不能将加密以编程的方法集成到RDManager中。

处理这个问题的方法仍是搬运putty的完成方法,将C言语的完成转化为C#的完成。其中有两个要害的处理:一是要在计算Private-MAC的值时给私钥添加padding,二是运用AES256进行加密处理。至于putty为什么要这样处理,我没有研究,仅仅照搬过来。

主要看下AES256加密的处理,有些参数很要害:

byte[] passKey = new byte[40];
...
byte[] iv = new byte[16];
byte[] aesKey = new byte[32];
Buffer.BlockCopy(passKey, 0, aesKey, 0, 32);
using (RijndaelManaged rijalg = new RijndaelManaged())
{
	rijalg.BlockSize = 128;
	rijalg.KeySize = 256;
	rijalg.Padding = PaddingMode.None;
	rijalg.Mode = CipherMode.CBC;
	rijalg.Key = aesKey;
	rijalg.IV = iv;
	ICryptoTransform encryptor = rijalg.CreateEncryptor(rijalg.Key, rijalg.IV);
	return encryptor.TransformFinalBlock(bytes, 0, bytes.Length);
}
  • iv是长度为16的字节数组,里面都是默许值0。
  • aeskey是一个长度为32的字节数组,不过计算的时分准备的是长度为40的字节数组,需求截一下。
  • Padding需求设置为PaddingMode.None,默许的不是这个。

其它就没什么好说的了。来一张RDManger的运用界面:

C#生成putty格式的ppk文件(支持加密)


以上便是本文的主要内容了。

完好代码在Github,欢迎拜访:gist.github.com/bosima/ee66…