背景

2022国家级护网行动即将开启,根据阿里云给出的安全建议,需要将登陆Linux的方式改为密钥对方式。我这里使用的远程工具是自己开发的,能够同时管理Windows和Linux,但是以前不支持密钥对的登陆方式,所以需要改造一下。

护网行动是什么?护网行动从2016年开始,是一场由公安部组织的网络安全攻防演练,目的是针对全国范围的真实网络目标为对象的实战攻防活动,旨在发现、暴露和解决安全问题,检验我国各大企事业单位、部属机关的网络安全防护水平和应急处置能力。护网行动每年举办一次,为期2-3周。

我使用的远程工具 RDManager:https://blog.bossma.cn/tools/new-version-of-rdmanager-replace-poderosa-with-putty/,这个工具访问Linux使用了putty,putty的密钥对登陆方式使用的是自有格式的ppk文件,但是阿里云上下载的是pem格式的密钥文件,所以需要将pem格式转换为ppk格式。

思路

putty本身提供了一个工具,可以将其他格式的密钥文件转换为自有的ppk文件,这个工具的名字是puttygen。在linux上可以通过命令进行转换,在Windows上则必须使用GUI工具手动操作,这多有不便。我期望的是能通过编程的方式进行这个转换,这样只需要在RDManger中上传pem文件,就可以自动转换为putty的ppk格式的文件,不需要再去使用puttygen。

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

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

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

受此启发,我也可以去看putty的源码,然后将相关处理翻译为C#的实现,这样应该是可以解决问题的。

实现

putty的源码官网上就可以下载到,不过我看的是一个几年前的版本:https://github.com/KasperDeng/putty,这个版本和新版本的主要逻辑都是一样的,搞懂C语言的若干函数和数据类型就很容易理解,而且旧版本更原始,没有那么多的抽象,反而更容易理解。

输出ppk内容不正确的问题

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

看putty的代码实现:https://github.com/KasperDeng/putty/blob/037a4ccb6e731fafc4cc77c0d16f80552fd69dce/putty-src/sshrsa.c#L654

    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中的实现:https://github.com/akira345/pem2ppk/blob/d2baee08064953280984607d1e4ae1183127e5ad/PEM2PPK/PuttyKeyFileGenerator.cs#L24

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:https://gist.github.com/bosima/ee6630d30b533c7d7b2743a849e9b9d0#file-puttykeyfilegenerator-cs-L190

	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的使用界面:


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

完整代码在Github,欢迎访问:https://gist.github.com/bosima/ee6630d30b533c7d7b2743a849e9b9d0

C#生成putty格式的ppk文件(支持passphrase)的更多相关文章

  1. Rss 订阅:php动态生成xml格式的rss文件

    Rss 简介: 简易信息聚合(也 叫聚合内容)是一种描述和同步网站内容的格式.使用RSS订阅能更快地获取信息,网站提供RSS输出,有利于让用户获取网站内容的最新更新.网络用户可以在客户端借助于支持RS ...

  2. 使用 HTMLTestRunner 模块生成HTML格式的测试报告文件

    1.下载HTMLTestRunner.py HTMLTestRunner 是 Python 标准库的 unittest 模块的一个扩展.它生成易于使用的 HTML 测试报告.HTMLTestRunne ...

  3. Java生成xlsx格式的excel文件

    xlsx格式的写入的数据量据说有百万级,结合实际需要该格式. public static void main(String[] args) throws Exception { OutputStrea ...

  4. keil MDK中如何生成*.bin格式的文件

    在Realview MDK的集成开发环境中,默认情况下可以生成*.axf格式的调试文件和*.hex格式的可执行文件.虽然这两个格式的文件非常有利于ULINK2仿真器的下载和调试,但是ADS的用户更习惯 ...

  5. 在MDK中怎样生成*.bin格式的文件?

    在Realview MDK的集成开发环境中.默认情况下能够生成*.axf格式的调试文件和*.hex格式的可运行文件. 尽管这两个格式的文件很有利于ULINK2仿真器的下载和调试,可是ADS的用户更习惯 ...

  6. 如何使用Postman生成不同格式测试的报告

    Postman还可以生成测试报告,还是多种格式报告? Postman团队开源Newman作为Postman运营工具,该开源库使用命令行方式执行Postman 脚本,并且生成多种格式报告,还支持Post ...

  7. [转]使用ant让Android自动打包的build.xml,自动生成签名的apk文件(支持android4.0以上的版本)

    在android4.0以后的sdk里那个脚本就失效了,主要是因为 apkbuilder这个程序不见了: 人家sdk升级,我们的脚本也要跟上趟,修改一下喽. 上网一查,大家的文章还停留在我去年的脚本程度 ...

  8. 如何将位图格式图片文件(.bmp)生成geotiff格式图片?

    一.位图格式信息 位图BITMAPINFOHEADER 与BITMAPFILEHEADER: 先来看BITMAPINFOHEADER,只写几个主要的 biSize包含的是这个结构体的大小(包括颜色表) ...

  9. 在Mac OSX下使用ssh建立隧道(在Windows下建立隧道可以使用putty,其间会用到ppk文件)

    在Windows下建立隧道可以使用putty,其间会用到ppk文件.在Mac OSX下,同样的功能可以用ssh命令实现.具体是: ssh -D 8088 -Nf user@ip -i myppk.ss ...

随机推荐

  1. JS_进阶-遍历对象属性、数组输出、Math、正则表达式匹配

    1 <!DOCTYPE html> 2 <html lang="en"> 3 <head> 4 <meta charset="U ...

  2. selenium模块获得js动态数据-17track为例

    通过selenium模块驱动Chrome浏览器,获得js动态数据,以17track为例:通过运单号查询最新的物流信息 1 import re 2 from time import sleep 3 fr ...

  3. Python装饰器:套层壳我变得更强了

    Python装饰器:套层壳我变得更强了 Python装饰器:套层壳我变得更强了 关于作用域和闭包可以聊点什么? 什么是作用域 什么是闭包 装饰器:套层壳我变得更强了 参考资料 昨天阅读了<Pyt ...

  4. 【第六课】SHELL和计划任务(学习笔记)

    4月15日学习笔记

  5. jmeter元件分析

    jmeter元件分析 一.脚本通用性 1.性能测试脚本改动一下,加入断言等元件,就可以作为接口测试脚本来使用 2.但是接口测试的脚本不可以作为性能测试脚本来使用 3.原因:因为性能测试考虑更多的性能, ...

  6. FreeRTOS --(16)资源管理之临界区

    转载自 https://blog.csdn.net/zhoutaopower/article/details/107387427 临界区的概念在任何的 SoC 都存在,比如,针对一个寄存器,基本操作为 ...

  7. SpringBoot中异常处理

    一.背景 在我们编写程序的过程中,程序中可能随时发生各种异常,那么我们如何优雅的处理各种异常呢? 二.需求 1.拦截系统中部分异常,返回自定义的响应. 比如: 系统发生HttpRequestMetho ...

  8. Python技法:用re模块实现简易tokenizer

    一个简单的tokenizer 分词(tokenization)任务是Python字符串处理中最为常见任务了.我们这里讲解用正则表达式构建简单的表达式分词器(tokenizer),它能够将表达式字符串从 ...

  9. Centos 7.4_64位系统安装指南

    小土豆Linux学习随笔 -- 清听凌雪慕忆 目录 1. 范围 1.1标识 1.2 文档概述 2. 安装环境 3. 安装步骤 4. 注意事项 1. 范围 1.1标识 CentOS 7.4 64位系统安 ...

  10. 如何利用 React Hooks 管理全局状态

    如何利用 React Hooks 管理全局状态 本文写于 2020 年 1 月 6 日 React 社区最火的全局状态管理库必定是 Redux,但是 Redux 本身就是为了大型管理数据而妥协设计的- ...