Kismet 杂谈

on under developer
10 minute read

前言

年初以来,互联网安全领域频繁发生大事件,二月下旬,Google 研究人员攻破了 SHA-1算法,在版本控制领域,主要的版本控制系统都使用了 SHA-1 算法1,由于工作需要,我对此格外关注,也了解了其他 SHA 算法,比如 SHA-256/SHA-512 ,基于 Keccak 的 SHA-3 等,寻找 GUI Hash 计算工具时发现大多数工具都未提供 SHA-3 算法支持,并且界面也不符合我的喜好,在闲暇时间,我就开发了 kismat Hash 计算工具,包括传统的Win32 桌面程序和 UWP 程序。

SHA-3

在 SHA-1 被攻破的十年前,NIST 就已经开始了 SHA-3 算法的征集,经过几年的选拔和测试,2012 年 Keccak 被选为优胜者,成为了 SHA-3。

Keccak 官方网站:http://keccak.noekeon.org/ 在这里,你可以下载 NIST 的原始版本。 在 Keccak Code Package2 项目中你可以下载 SHA-3 以及各种变种。

Github 上 C/C++ 的 SHA-3 实现非常多,但是基础平台的加密算法库都未实现,如 Windows CNG, Apple Security.framework,OpenSSL。在 Window 中截至到 Windows 10 10.0.15063, CNG 依然没有支持 SHA-3,同样的,虽然 OpenSSL 添加了 keccak1600.c 但是其 SHA-3 依然未完全实现。

ARM mbed 的 mbedtls3 Google 的 Boringssl4 OpenBSD 的 LibreSSL5 也未支持 SHA-3。

下面是 bcrypt.h 关于加密算法的定义:

#define BCRYPT_RSA_ALGORITHM                    L"RSA"
#define BCRYPT_RSA_SIGN_ALGORITHM               L"RSA_SIGN"
#define BCRYPT_DH_ALGORITHM                     L"DH"
#define BCRYPT_DSA_ALGORITHM                    L"DSA"
#define BCRYPT_RC2_ALGORITHM                    L"RC2"
#define BCRYPT_RC4_ALGORITHM                    L"RC4"
#define BCRYPT_AES_ALGORITHM                    L"AES"
#define BCRYPT_DES_ALGORITHM                    L"DES"
#define BCRYPT_DESX_ALGORITHM                   L"DESX"
#define BCRYPT_3DES_ALGORITHM                   L"3DES"
#define BCRYPT_3DES_112_ALGORITHM               L"3DES_112"
#define BCRYPT_MD2_ALGORITHM                    L"MD2"
#define BCRYPT_MD4_ALGORITHM                    L"MD4"
#define BCRYPT_MD5_ALGORITHM                    L"MD5"
#define BCRYPT_SHA1_ALGORITHM                   L"SHA1"
#define BCRYPT_SHA256_ALGORITHM                 L"SHA256"
#define BCRYPT_SHA384_ALGORITHM                 L"SHA384"
#define BCRYPT_SHA512_ALGORITHM                 L"SHA512"
#define BCRYPT_AES_GMAC_ALGORITHM               L"AES-GMAC"
#define BCRYPT_AES_CMAC_ALGORITHM               L"AES-CMAC"
#define BCRYPT_ECDSA_P256_ALGORITHM             L"ECDSA_P256"
#define BCRYPT_ECDSA_P384_ALGORITHM             L"ECDSA_P384"
#define BCRYPT_ECDSA_P521_ALGORITHM             L"ECDSA_P521"
#define BCRYPT_ECDH_P256_ALGORITHM              L"ECDH_P256"
#define BCRYPT_ECDH_P384_ALGORITHM              L"ECDH_P384"
#define BCRYPT_ECDH_P521_ALGORITHM              L"ECDH_P521"
#define BCRYPT_RNG_ALGORITHM                    L"RNG"
#define BCRYPT_RNG_FIPS186_DSA_ALGORITHM        L"FIPS186DSARNG"
#define BCRYPT_RNG_DUAL_EC_ALGORITHM            L"DUALECRNG"

#if (NTDDI_VERSION >= NTDDI_WIN8)
#define BCRYPT_SP800108_CTR_HMAC_ALGORITHM      L"SP800_108_CTR_HMAC"
#define BCRYPT_SP80056A_CONCAT_ALGORITHM        L"SP800_56A_CONCAT"
#define BCRYPT_PBKDF2_ALGORITHM                 L"PBKDF2"
#define BCRYPT_CAPI_KDF_ALGORITHM               L"CAPI_KDF"
#define BCRYPT_TLS1_1_KDF_ALGORITHM             L"TLS1_1_KDF"
#define BCRYPT_TLS1_2_KDF_ALGORITHM             L"TLS1_2_KDF"
#endif

#if (NTDDI_VERSION >= NTDDI_WINTHRESHOLD)
#define BCRYPT_ECDSA_ALGORITHM                  L"ECDSA"
#define BCRYPT_ECDH_ALGORITHM                   L"ECDH"
#define BCRYPT_XTS_AES_ALGORITHM                L"XTS-AES"
#endif

如果开发者需要支持 SHA-3 ,又要考虑提供库的稳定性,可以选择 RHash , CMake 3.8 开始使用 RHash 支持 SHA-3 ,地址为:Utilities/cmlibrhash

在其他语言中,大多是第三方库支持 SHA-3,然而标准库或者平台 API 并未实现(这些语言标准库可能使用 Windows CNG 或者 OpenSSL 的加密算法实现)。

Kismet

Kismet 是一个传统的 Win32 程序,使用 ATL 封装窗口,Direct2D 绘制界面,Hash 库使用的正是 RHash,RHash 支持 MD4,MD5,SHA-1,SHA-224/256,SHA-384/512,SHA-3 (224,256,384,512) ,能够很大程度的避免引入多个依赖库。如果使用 Windows CNG,还需要额外添加 SHA-3 支持,反而没有直接使用 RHash 方便。

Kismet Github 托管地址为: https://github.com/fcharlie/Kismet 开源协议为 MIT。

Kismet 包装 RHash 的代码在: Hashusm.cpp 中,Hashsum 接口类为:

class Hashsum {
public:
	virtual void Initialize(int width) = 0;
	virtual void Update(const unsigned char *buf, size_t len) = 0;
	virtual void Final(bool ucase, std::wstring &hash) = 0;
};

二进制数据转 16 进制:

static inline void BinaryToHex(const unsigned char *buf, size_t len, std::wstring &str) {
	char to_hex[] = "0123456789abcdef";
	for (uint32_t i = 0; i < len; i++) {
		unsigned int val = buf[i];
		str.push_back(to_hex[val >> 4]);
		str.push_back(to_hex[val & 0xf]);
	}
}

static inline void BinaryToHexUCase(const unsigned char *buf, size_t len, std::wstring &str) {
	char to_hex[] = "0123456789ABCDEF";
	for (uint32_t i = 0; i < len; i++) {
		unsigned int val = buf[i];
		str.push_back(to_hex[val >> 4]);
		str.push_back(to_hex[val & 0xf]);
	}
}

而使用 Hashsum 的代码如下:

Hashsum * CreateHashsum(const std::wstring & file, int alg, int width)
{
	Hashsum *sum = nullptr;
	switch (alg) {
	case kFilesumMD5:
		sum = new MD5Hashsum();
		sum->Initialize(width);
		break;
	case kFilesumSHA1:
		sum = new SHA1Sum();
		sum->Initialize(width);
		break;
	case kFilesumSHA1DC:
		sum = new SHADC1Sum();
		sum->Initialize(width);
		break;
	case kFilesumSHA2:
		if (width <= 256) {
			sum = new SHA256Sum();
			sum->Initialize(width);
		}
		else {
			sum = new SHA512Sum();
			sum->Initialize(width);
		}
		break;
	case kFilesumSHA3:
		sum = new SHA3Sum();
		sum->Initialize(width);
		break;
	default:
		return nullptr;
	}
	return sum;
}

在 GUI 程序的开发过程中,如果在主线程执行耗时的操作会导致界面失去响应,这个时候就需要使用多线程,使用 Win32 API CreateThread CRT 的 _beginthread 这些都需要传递全局函数,使用 lambda 的话非常 Ugly。这个是用可以使用 std::thread 但是如果执行了多个计算过程,就会需要启动多个线程,不过对于 Kismet 这样的小工具无关紧要。我在开发 Git LFS Server (Moses) 时使用了 cpprestsdk 了解了 PPL6 ,在 Windows 10 中,如果使用 C++/CX 开发 App 时也是使用了 PPL7。在 Kismet 中,我也就采用了 PPL,代码如下:

LRESULT NeonWindow::Filesum(const std::wstring & file)
{
	if (file.empty()) {
		return S_FALSE;
	}
	if (locked) {
		return S_FALSE;
	}
	FilesumAlgw aw;
	if (!HashsumAlgmCheck(ComboBox_GetCurSel(hCombo), aw)) {
		return false;
	}
	filetext.clear();
	hash.clear();
	sizetext.clear();
	std::wstring title;
	title.append(L"(").append(aw.name).append(L") ").append(PathFindFileNameW(file.data()));
	UpdateTitle(title);
	showerror = false;
	Concurrency::create_task([this, file,aw]()->bool {
		std::shared_ptr<Hashsum> sum(CreateHashsum(file, aw.alm, aw.width));
		if (!sum) {
			return false;
		}
		AllocSingle as;
		BYTE *buffer = as.Alloc<BYTE>(AllocSingle::kInternalBufferSize);
		if (as.size() == 0 || buffer == nullptr) {
			return false;
		}
		auto hFile = CreateFileW(file.data(),
			GENERIC_READ,
			FILE_SHARE_READ,
			NULL,
			OPEN_EXISTING,
			FILE_ATTRIBUTE_NORMAL,
			nullptr);
		if (hFile == INVALID_HANDLE_VALUE) {
			return false;
		}
		LARGE_INTEGER li;
		GetFileSizeEx(hFile, &li);
		if (file.size() > 64) {
			filetext.assign(PathFindFileNameW(file.data()));
			if (filetext.size() > 64)
			{
				filetext.resize(64);
				filetext.append(L"...");
			}
		}
		else {
			filetext = file;
		}
		sizetext.assign(std::to_wstring(li.QuadPart));
		InvalidateRect(nullptr);
		DWORD dwRead;
		int64_t cmsize = 0;
		uint32_t pg = 0;
		for (;;) {
			if (!ReadFile(hFile, buffer, AllocSingle::kInternalBufferSize, &dwRead, nullptr)) {
				break;
			}
			sum->Update(buffer, dwRead);
			cmsize += dwRead;
			auto N = (uint32_t)(cmsize * 100 / li.QuadPart);
			progress = (uint32_t)N;
			/// when number is modify, Flush Window
			if (pg != N) {
				pg = (uint32_t)N;
				InvalidateRect(nullptr,FALSE);
			}
			if (dwRead<AllocSingle::kInternalBufferSize)
				break;
		}
		CloseHandle(hFile);
		hash.clear();
		bool ucase = (Button_GetCheck(hCheck) == BST_CHECKED);
		sum->Final(ucase, hash);
		return true;
	}).then([this](bool result) {
		if (!result) {
			showerror = true;
		}
		InvalidateRect(nullptr,FALSE);
		locked = false;
	});
	return S_OK;
}

使用 PPL 和 lambda 能够很方便的使用窗口类的变量,PPL 使用线程池也是非常高效的。

窗口图形方面,我使用的是 Direct2D,在 Windows 10 中,Direct2D 对 Emoji 的支持更好了,能够支持彩色 ❤ 。同样在文字渲染方面也优于 GDI。

使用 Direct2D ID2D1RenderTarget::DrawText 时,将 D2D1_DRAW_TEXT_OPTIONS options 设置为 D2D1_DRAW_TEXT_OPTIONS_ENABLE_COLOR_FONT,就可以支持彩色 Emoji 了。下面是 Kismet 工具的一些截图:

无状态窗口:

none

自定义标题栏:

title

冲突检测:

coll

计算中:

progress

计算完成:

complete

SHA-3 512:

sha3-512

自定义主题色:

theme

Kismet UWP

UWP 程序应该是以后 Windows App 开发的趋势,作为 Microsoft 技术爱好者,自然也会开发简易的 UWP App 。

RHash 移植到 UWP 还是很容易的,得益于 Windows ucrt8, RHash 可以无缝迁移到 UWP 中,唯一比较麻烦的是,UWP App 读取文件的缓冲区是 Windows::Storage::Streams::IBuffer,而 RHash 使用的是直接内存,解决方案如下:

byte * KismetUWP::MainPage::GetPointerToPixelData(Windows::Storage::Streams::IBuffer ^ pixelBuffer, unsigned int * length)
{
	Object^ obj = pixelBuffer;
	ComPtr<IInspectable> insp(reinterpret_cast<IInspectable*>(obj));

	// Query the IBufferByteAccess interface.
	ComPtr<IBufferByteAccess> bufferByteAccess;
	insp.As(&bufferByteAccess);

	// Retrieve the buffer data.
	byte* pixels = nullptr;
	bufferByteAccess->Buffer(&pixels);
	return pixels;
}

通过 GetPointerToPixelData 将 IBuffer 转换为 BYTE*。这样是有代价的,一个较大的文件 Kismet 的计算速度快于 Kismet UWP。

在 UWP 程序中,IO 的读写往往异步的,开发者使用 PPL 即可,如果在 Visual Studio 2015 Update 2 以上版本中设置了 /await 就可以使用 co_wait 简化异步操作。MSDN 文档:https://blogs.msdn.microsoft.com/vcblog/2016/04/04/using-c-coroutines-to-simplify-async-uwp-code/

Kismet UWP 在 Github 上的地址为:https://github.com/fcharlie/KismetUWP

Kismet UWP 的截图如下:

初始界面:

u1

计算完成:

u2

由于开发者账号的缘故,Kismet UWP 并未发布到 Windows Store,需要使用的用户可以自己克隆构建。

附录

  1. Git 将对象类型(blob,tree,commit)与长度以及文件内容连接在一起做 SHA-1 计算。Subversion 的 SHA-1 冲突要严重多了。
  2. KCP KeccakCodePackage (https://github.com/gvanas/KeccakCodePackage)
  3. Mbedtls https://github.com/ARMmbed/mbedtls
  4. BoringSSL https://github.com/google/boringssl
  5. LibreSSL http://www.libressl.org/
  6. Parallel Patterns Library (PPL) https://msdn.microsoft.com/en-us/library/dd492418.aspx
  7. Asynchronous programming in C++ https://docs.microsoft.com/en-us/windows/uwp/threading-async/asynchronous-programming-in-cpp-universal-windows-platform-apps
  8. CRT Library Features https://msdn.microsoft.com/en-us/library/abx4dbyh.aspx