Kismet 杂谈
前言
年初以来,互联网安全领域频繁发生大事件,二月下旬,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 的 mbedtls
3 Google 的 Boringssl
4 OpenBSD 的 LibreSSL
5 也未支持 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
了解了 PPL
6 ,在 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 工具的一些截图:
无状态窗口:
自定义标题栏:
冲突检测:
计算中:
计算完成:
SHA-3 512:
自定义主题色:
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 的截图如下:
初始界面:
计算完成:
由于开发者账号的缘故,Kismet UWP 并未发布到 Windows Store,需要使用的用户可以自己克隆构建。
附录
- Git 将对象类型(blob,tree,commit)与长度以及文件内容连接在一起做 SHA-1 计算。Subversion 的 SHA-1 冲突要严重多了。
- KCP KeccakCodePackage (https://github.com/gvanas/KeccakCodePackage)
- Mbedtls https://github.com/ARMmbed/mbedtls
- BoringSSL https://github.com/google/boringssl
- LibreSSL http://www.libressl.org/
- Parallel Patterns Library (PPL) https://msdn.microsoft.com/en-us/library/dd492418.aspx
- Asynchronous programming in C++ https://docs.microsoft.com/en-us/windows/uwp/threading-async/asynchronous-programming-in-cpp-universal-windows-platform-apps
- CRT Library Features https://msdn.microsoft.com/en-us/library/abx4dbyh.aspx
Let me know what you think of this article on twitter @sinopre!