kcrypt--windows内核加密算法库

admin 2023年8月12日19:43:22评论21 views字数 28097阅读93分39秒阅读模式
前言

kcrypt是一个用于windows内核和驱动编写的的加密库库中包含了常用的加解密算法。

◆数字摘要包括MD4-MD5 SHA1-SHA512 对称加密包括DES 3DES AES RC4 非对称加密包括 RSA 并且支持IV和不同的加密模式(块加密的CBC,ECB等等)。

◆源代码已开源:github链接(https://github.com/Oxygen1a1/kcrypt)求各位大佬点个小星星。

◆源码较少涉及各种加密算法实现原理,仅是对微软CNG.lib的封装。

如何使用

◆把三个hpp文件引入,然后#include <Hasher.hpp> #include <SymCipher.hpp> #include <ASymCipher.hpp> 接着必须在连接器选项->引入->添加cng.lib这个库。


#include <Hasher.hpp>
#include <SymCipher.hpp>
#include <ASymCipher.hpp>

Demo

数字摘要

MD5 测试

kcrypt--windows内核加密算法库


unsigned char plainText[2] = { 'a','&#x0;' };
//MD5 Test
kcrypt::Md5Creator md5test;
unsigned char md5Hash[20]{0};
md5test.crypt(plainText,1, md5Hash, kcrypt::MD5::GetHashLength());
char md5Str[40]{ 0 };
kcrypt::hexToStr(md5Str, sizeof (md5Str), md5Hash, kcrypt::MD5::GetHashLength());
printk("md5 crypt->%srn", md5Str);

SHA1 Test

kcrypt--windows内核加密算法库


//SHA1 TEST
kcrypt::SHA1Creator sha1test;
unsigned char sha1Hash[20]{ 0 };
sha1test.crypt(plainText, 1, sha1Hash, kcrypt::SHA1::GetHashLength());
char sha1Str[40]{ 0 };
kcrypt::hexToStr(sha1Str, sizeof sha1Str, sha1Hash, kcrypt::SHA1::GetHashLength());
printk("sha1 crypt->%srn", sha1Str);

对称加密

use default key(0)

DES ecb test


//DES test ecb
unsigned char desPlainText[8] = { 0 };
memset(desPlainText, 7, 8);
kcrypt::DESCreator desTest;
unsigned char desBuf[20] {0};
auto result=desTest.encrypt(desPlainText, sizeof desPlainText, desBuf,sizeof desBuf);
char desStr[20]{ 0 };
kcrypt::hexToStr(desStr, sizeof desStr, desBuf, 8);
printk("des ecb str->%srn", desStr);
//decrypt test
unsigned char decryptCode[8]{ 0 };
desTest.decrypt(decryptCode, sizeof decryptCode, desBuf, result);

kcrypt--windows内核加密算法库

AES test

kcrypt--windows内核加密算法库

非对称加密

RSA test


unsigned char rsaPlainText[1] = { 7 };
unsigned char rsaBuf[500] = { 0 };
char rsaStr[40]{ 0 };
unsigned char rsadecryptCode[1] = { 0 };
kcrypt::RSACreator rsatest;
auto [pubkey, pubkeysize] = rsatest.getPubKey();
auto [prikey, prikeysize] = rsatest.getPriKey();
#pragma warning(disable :4996)
auto pubKeyStr = (char*)ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'tmp');
auto priKeyStr = (char*)ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, 'tmp');
memset(pubKeyStr, 0, PAGE_SIZE);
memset(priKeyStr, 0, PAGE_SIZE);
kcrypt::hexToStr(priKeyStr, PAGE_SIZE, prikey, prikeysize);
kcrypt::hexToStr(pubKeyStr, PAGE_SIZE, pubkey, pubkeysize);
printk("pub key ->%srn pri key ->%srn", pubKeyStr,priKeyStr);
// You can leave arg5 and arg6 blank,
// as this will use the default public key for encryption
// and the private key for decryption.
result=rsatest.encrypt(rsaPlainText, sizeof rsaPlainText, rsaBuf, sizeof rsaBuf);
kcrypt::hexToStr(rsaStr, sizeof rsaStr, rsaBuf, result);
printk("rsa str->%srn", rsaStr);
//rsatest.decrypt(rsadecryptCode, sizeof rsadecryptCode, rsaBuf, result);
//and you can fill any pubkey or private key to encrypt and decrypt
result = rsatest.encrypt(rsaPlainText, sizeof rsaPlainText,
rsaBuf, sizeof rsaBuf, pubkey, pubkeysize);
rsatest.decrypt(rsadecryptCode, sizeof rsadecryptCode, rsaBuf, result);
ExFreePool(pubKeyStr);

实现原理

这个其实更多地用在文件系统的透明加密上面。

我们知道,加密算法主要分为三种:

◆数字摘要(MD5 SHAX)
◆对称加密(DES 3DES AES)
◆非对称加密(RSA)

数字摘要确定数据完整性,对称加密用于数据传输的时候加密,非对称加密用处主要是用于数字签名,证书认证等确认身份的。

在windows内核都实现了相关的函数进行加解密,库是cng.lib,而头文件是bcrypt.h。

数字摘要

这里可以简单地封装个hasher类来进行简单调用这个库,因为使用cng.lib这个库方法是很固定的。

都是先BCryptOpenAlgorithmProvider生成一个特定加密算法的提供者句柄,比如:


NTSTATUS
WINAPI
BCryptOpenAlgorithmProvider(
_Out_ BCRYPT_ALG_HANDLE *phAlgorithm,
_In_z_ LPCWSTR pszAlgId,
_In_opt_z_ LPCWSTR pszImplementation,
_In_ ULONG dwFlags);

返回的就是算法句柄,但是要BCryptCloseAlgorithmProvider来进行清理他;

这个pszAlgId其实就是加密算法的名字。


#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"

windows已经预定义了,第三个第四个参数都是可选的,第三个是算法提供程序,第四个是flags。

kcrypt--windows内核加密算法库

获取完这个句柄之后,我们要获取这个提供者的相关信息,分配一个加密对象,注意一定是非分页内存。


status = BCryptGetProperty(hAlgorithm, BCRYPT_OBJECT_LENGTH, (PUCHAR)&hashObjectSize, sizeof(ULONG), &resultSize, 0);
if (!NT_SUCCESS(status))
{
DbgPrint("BCryptGetProperty failed with %xn", status);
goto Cleanup;
}

hashObject = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, hashObjectSize, 'md5h');
if (hashObject == nullptr)
{
DbgPrint("Memory allocation failedn");
status = STATUS_INSUFFICIENT_RESOURCES;
goto Cleanup;
}

status = BCryptCreateHash(hAlgorithm, &hHash, hashObject, hashObjectSize, nullptr, 0, 0);
if (!NT_SUCCESS(status))
{
DbgPrint("BCryptCreateHash failed with %xn", status);
goto Cleanup;
}

status = BCryptHashData(hHash, data, dataLength, 0);
if (!NT_SUCCESS(status))
{
DbgPrint("BCryptHashData failed with %xn", status);
goto Cleanup;
}

status = BCryptFinishHash(hHash, hash, hashLength, 0);
if (!NT_SUCCESS(status))
{
DbgPrint("BCryptFinishHash failed with %xn", status);
goto Cleanup;
}

BCryptGetProperty获取MD5加密算法加密对象的大小,申请块非分页内存,然后使用BCryptCreateHash创建一个加密对象,一定要注意,假如是对称加密,生成密钥就要用BCryptGenerateSymmetricKey了;不同类型的加密算法所使用生成对象的函数是不一样的。

创建成功之后,直接使用BCryptHashData,参数填刚才获得的hHash即可。若要将多个缓冲区合并到哈希或 MAC 中,可以多次调用此函数,每次传递不同的缓冲区。若要获取哈希或 MAC 值,请调用BCryptFinishHash函数(https://learn.microsoft.com/zh-cn/windows/desktop/api/bcrypt/nf-bcrypt-bcryptfinishhash)。为指定的句柄调用BCryptFinishHash函数后,无法重复使用该句柄。

这里封装个类+模板来更方便地优雅地使用hash;这里只支持MD4-MD5,SHA1-SHA256这些常用的数字摘要。

如果支持更多,可以自己添加相关的类,然后填入模板。


#pragma once
#include <fltKernel.h>
#include <bcrypt.h>
#include <ntstrsafe.h>
namespace kcrypt {

//由于这个项目用了crt库 不能用内核的了 只能这样
using fnsprintf = int(*)(char* _DstBuf,const char* _Format, ...);
#pragma warning(disable : 4996)
#define hashprintk(...)do{DbgPrintEx(77,0,__VA_ARGS__);}while(0);

template <typename Algorithm>
class Hasher {
public:
Hasher();
~Hasher();
bool crypt(PUCHAR data, ULONG size, PUCHAR hash, ULONG hash_len);
Hasher(const Hasher&) = delete;
Hasher(Hasher&&) = delete;
Hasher& operator=(Hasher&) = delete;
private:
BCRYPT_ALG_HANDLE hAlgorithm_;//算法提供者句柄
BCRYPT_HASH_HANDLE hHash_;//hash对象句柄
unsigned long hashObjSize_;
void* hashObj_;//哈希对象
};

template <typename Algorithm>
Hasher<Algorithm>::Hasher() {

auto status = STATUS_UNSUCCESSFUL;
do {

status = BCryptOpenAlgorithmProvider(&hAlgorithm_, Algorithm::GetAlgorithmName(), nullptr, 0);
if (!NT_SUCCESS(status)) break;

unsigned long resultSize = 0;
//询问属性
status = BCryptGetProperty(hAlgorithm_, BCRYPT_OBJECT_LENGTH,
(PUCHAR)&hashObjSize_, sizeof hashObjSize_, &resultSize, 0);
if (!NT_SUCCESS(status)) {
break;
}

hashObj_ = ExAllocatePoolWithTag(NonPagedPool, hashObjSize_, 'hash');
if (hashObj_ == nullptr) break;
//创建hash对象
status = BCryptCreateHash(hAlgorithm_, &hHash_,(PUCHAR)hashObj_, hashObjSize_, 0, 0, 0);
if (!NT_SUCCESS(status)) {

break;
}

return;//成功
} while (0);

//走到这是不成功
if (hashObj_) {
ExFreePool(hashObj_);
}
if (hAlgorithm_) {
BCryptCloseAlgorithmProvider(hAlgorithm_,0);
}
if (hHash_) {
BCryptDestroyHash(hHash_);
}

hashprintk("failed to ctor!rn");

}
template <typename Algorithm>
Hasher<Algorithm>::~Hasher() {

if (hashObj_) {
ExFreePool(hashObj_);
hashObj_ = 0;
}
if (hAlgorithm_) {
BCryptCloseAlgorithmProvider(hAlgorithm_,0);
hAlgorithm_ = 0;
}
if (hHash_) {
BCryptDestroyHash(hHash_);
hHash_ = 0;
}
}
template <typename Algorithm>
bool Hasher<Algorithm>::crypt(PUCHAR data, ULONG size, PUCHAR hash, ULONG hash_len) {
if (hHash_ && hashObj_ && hAlgorithm_) {

//先验证hash的长度是否正确
//BCryptFinsihHash 很邪门,必须长度等同于hash值长度
if (Algorithm::GetHashLength() > hash_len)return false;
auto status = STATUS_UNSUCCESSFUL;

status = BCryptHashData(hHash_, data, size, 0);
if (!NT_SUCCESS(status)) return false;

status = BCryptFinishHash(hHash_, hash, Algorithm::GetHashLength(), 0);
if (!NT_SUCCESS(status)) return false;

return true;
}

return false;
}

class SHA1 {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_SHA1_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 20; // SHA-1的哈希值长度为160位,即20字节
}
};

class SHA256 {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_SHA256_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 32; // SHA-256的哈希值长度为256位,即32字节
}
};

class SHA384 {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_SHA384_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 48; // SHA-384的哈希值长度为384位,即48字节
}
};

class SHA512 {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_SHA512_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 64; // SHA-512的哈希值长度为512位,即64字节
}
};

class MD4 {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_MD4_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 16; // MD4的哈希值长度为128位,即16字节
}
};

class MD5 {
public:
static constexpr PCWSTR GetAlgorithmName() {

return BCRYPT_MD5_ALGORITHM;
}
static constexpr ULONG GetHashLength() {
return 16;
}
};

using Md4Creator = Hasher<MD4>;
using Md5Creator = Hasher<MD5>;
using SHA1Creator = Hasher<SHA1>;
using SHA256Creator = Hasher<SHA256>;
using SHA384Creator = Hasher<SHA384>;
using SHA512Creator = Hasher<SHA512>;

//16进制转换成str
void hexToStr(char* hexStr, ULONG len, PUCHAR hexArry, ULONG hexLen) {
if (len < hexLen * 2) return;
UNICODE_STRING uFuncName{ 0 };
RtlInitUnicodeString(&uFuncName, L"sprintf");
auto ___sprintf = (fnsprintf)MmGetSystemRoutineAddress(&uFuncName);
for (unsigned int i = 0; i < hexLen; i++) {
//只能从ntoskrnl.exe导出了 因为被stdio这个坑填了
___sprintf(hexStr + i * 2,"%02x", hexArry[i]);
}

}

#pragma warning(default : 4996)
}

对称加密

常见的对称加密主要是DES 3DES AES这些,而对称加密有密钥这个概念,因此就得生成密钥了。

分组加密

其实对称加密使用起来比数字摘要更简短,在使用了使用BCryptOpenAlgorithmProvider(https://learn.microsoft.com/zh-cn/windows/desktop/api/bcrypt/nf-bcrypt-bcryptopenalgorithmprovider)创建相关算法提供者句柄之后,直接调用BCryptGenerateSymmetricKey这个函数就行了。

里面可以指定密钥,也可以不填,如果不填会由windows默认生成。


NTSTATUS BCryptGenerateSymmetricKey(
[in, out] BCRYPT_ALG_HANDLE hAlgorithm,
[out] BCRYPT_KEY_HANDLE *phKey,
[out, optional] PUCHAR pbKeyObject,
[in] ULONG cbKeyObject,
[in] PUCHAR pbSecret,
[in] ULONG cbSecret,
[in] ULONG dwFlags
);


phKey这个就是传出的加密句柄了,后续BCryptEncrypt时候需要用到,不需要用次句柄就得调用BCryptDestoryKey销毁了。

第三四个参数分别是一个是接受密钥对象的指针后面是密钥对象的大小,这个大小用BCRYPT_OBJECT_LENGTH属性获取(调用BCryptGetProperty函数)。

第三个第四个均可以填0,这个意思是则此函数会分配并释放密钥对象的内存。

第五个第六个参数就是密钥。

用于非对称提供商的BCryptGenerateKeyPairBCryptImportKeyPair
◆对称提供程序的BCryptGenerateSymmetricKeyBCryptImportKey 。

一切就绪之后,即可调用。


NTSTATUS BCryptEncrypt(
[in, out] BCRYPT_KEY_HANDLE hKey,
[in] PUCHAR pbInput,
[in] ULONG cbInput,
[in, optional] VOID *pPaddingInfo,
[in, out, optional] PUCHAR pbIV,
[in] ULONG cbIV,
[out, optional] PUCHAR pbOutput,
[in] ULONG cbOutput,
[out] ULONG *pcbResult,
[in] ULONG dwFlags
);

这个函数进行加密,这个函数非对称加密也是通用的,对称加密使用这个函数很多填0即可。

解密调用BCryptDecrypt;

IV主要是给分组密码用的,这个主要是为了防止块加密对于相同的块加密的数据是相同情况的弊端。

还需要注意的是,每次调用EnCrypt这个IV都会被更改,但是解密的时候和加密的时候必须用到的IV是相同的,而且一般IV大小和块的大小是相同的;因为可以考虑在类中写入get,set函数设置IV,来方便加解密。

而flags可以填的是:如果密钥是对称密钥,则此值可以为零或以下值。

kcrypt--windows内核加密算法库

如果密钥是非对称密钥,则此值可以是下列值之一。

kcrypt--windows内核加密算法库

众所周知AES/DES有多种加密方式,ECB(电子密码本模式)、CBC(密码分组链接模式)、CFB(密码反馈模式;ECB不太安全,CBC啥的更安全,这样加密两段相同明文就不会密文一样了。

至于如何设置一个对称加密的加密模式,使用BCryptSetProperty即可。


if(!NT_SUCCESS(status = BCryptSetProperty(
hAesAlg,
BCRYPT_CHAINING_MODE,
(PBYTE)BCRYPT_CHAIN_MODE_CBC,
sizeof(BCRYPT_CHAIN_MODE_CBC),
0)))
// Property Strings
#define BCRYPT_CHAIN_MODE_NA L"ChainingModeN/A"
#define BCRYPT_CHAIN_MODE_CBC L"ChainingModeCBC"
#define BCRYPT_CHAIN_MODE_ECB L"ChainingModeECB"
#define BCRYPT_CHAIN_MODE_CFB L"ChainingModeCFB"
#define BCRYPT_CHAIN_MODE_CCM L"ChainingModeCCM"
#define BCRYPT_CHAIN_MODE_GCM L"ChainingModeGCM"

此外,对于对称加密有块的概念,所以可以询问块的大小,一般来说明文必须是按照块对齐的。


if(!NT_SUCCESS(status = BCryptGetProperty(
hAesAlg,
BCRYPT_BLOCK_LENGTH,
(PBYTE)&cbBlockLen,
sizeof(DWORD),
&cbData,
0)))

下面与数字摘要那个类似,封装一个类进行实现对称加密的加解密。


enum class Mode {
ecb,
cbc,
cfb,
ccm,
gcm
};

template<typename Algorithm>
class BlockCipher {
public:
//密钥 IV随机向量 还有加密模式
BlockCipher(PUCHAR key=nullptr, Mode mod = Mode::ecb,PUCHAR iv=nullptr);
~BlockCipher();
ULONG encrypt(PUCHAR data,ULONG dataSize,PUCHAR cryptData,ULONG cryptSize);
ULONG decrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize);
PUCHAR constexpr getcuriv() { return _iv; }
PUCHAR constexpr getlastiv(){return _preIv}
PUCHAR constexpr getkey() { return _key; }
private:
PUCHAR _key;//密钥
BCRYPT_ALG_HANDLE _hAlg;//算法提供句柄
BCRYPT_KEY_HANDLE _hKey;//对称加密的密钥对象句柄 必须用这个加解密
PUCHAR _keyObj;
Mode _mod;
PUCHAR _iv;
PUCHAR _preIv;
ULONG _ivSize;
};

template<typename Algorithm>
BlockCipher<Algorithm>::BlockCipher(PUCHAR key, Mode mod,PUCHAR iv):_key(key),_mod(mod),_iv(iv) {

do {

//创建提供者
auto status = BCryptOpenAlgorithmProvider(&_hAlg,
Algorithm::GetAlgorithmName(), 0, 0);
if (!NT_SUCCESS(status)) break;

//询问密钥对象大小
ULONG keyObjSize = 0,result=0;
status = BCryptGetProperty(_hAlg, BCRYPT_OBJECT_LENGTH, (PUCHAR)&keyObjSize, sizeof ULONG, &result, 0);
if (!NT_SUCCESS(status)) break;
_keyObj = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool,keyObjSize, 'sym');
if (_keyObj == nullptr) break;

//设置IV随机向量 用于加密 如果是ECB方式则不需要
if (_key == nullptr) _key = Algorithm::GetDefaultKey();
else {
//需要申请
_key = (PUCHAR)ExAllocatePoolWithTag(PagedPool, Algorithm::GetBlockSize(), 'sym');
if (_key == nullptr) break;
memcpy(_key, key, Algorithm::GetBlockSize());
}

if (mod != Mode::ecb) {
//询问获取_ivSize
if (!NT_SUCCESS(BCryptGetProperty(
_hAlg,
BCRYPT_BLOCK_LENGTH,
(PUCHAR)&_ivSize,
sizeof(ULONG),
&result,
0))) break;
//如果不是ECB加密 需要随机向量
if (_iv == nullptr) {
iv = Algorithm::GetDefaultIV();
}

//申请内存 防止传入的是局部变量
_iv = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, _ivSize, 'sym');
_preIv= (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, _ivSize, 'sym');
if (_iv == nullptr || !_preIv) break;
memcpy(_iv, iv, _ivSize);
memset(_preIv, 0, _ivSize);

}else { _iv = nullptr, _ivSize = 0,_preIv=nullptr; }
//设置加密方式
switch (_mod)
{
case kcrypt::Mode::ecb:
status = BCryptSetProperty(_hAlg, BCRYPT_CHAINING_MODE,
(PUCHAR)BCRYPT_CHAIN_MODE_ECB,
sizeof(BCRYPT_CHAIN_MODE_ECB),
0);
break;
case kcrypt::Mode::cbc:
status = BCryptSetProperty(_hAlg, BCRYPT_CHAINING_MODE,
(PUCHAR)BCRYPT_CHAIN_MODE_CBC,
sizeof(BCRYPT_CHAIN_MODE_CBC),
0);
break;
case kcrypt::Mode::cfb:
status = BCryptSetProperty(_hAlg, BCRYPT_CHAINING_MODE,
(PUCHAR)BCRYPT_CHAIN_MODE_CFB,
sizeof(BCRYPT_CHAIN_MODE_CFB),
0);
break;
case kcrypt::Mode::ccm:
status = BCryptSetProperty(_hAlg, BCRYPT_CHAINING_MODE,
(PUCHAR)BCRYPT_CHAIN_MODE_CCM,
sizeof(BCRYPT_CHAIN_MODE_CCM),
0);
break;
case kcrypt::Mode::gcm:
status = BCryptSetProperty(_hAlg, BCRYPT_CHAINING_MODE,
(PUCHAR)BCRYPT_CHAIN_MODE_GCM,
sizeof(BCRYPT_CHAIN_MODE_GCM),
0);
break;
default:
status = STATUS_INVALID_PARAMETER;
break;
}

if (!NT_SUCCESS(status)) break;

//生成密钥句柄 用于后续加密
status = BCryptGenerateSymmetricKey(_hAlg, &_hKey, _keyObj,keyObjSize, _key, Algorithm::GetBlockSize(),0);
if (!NT_SUCCESS(status)) {
symprintk("failed to generate key!rn");
break;
}

return;
} while (0);

//对称加密可以省略密钥对象 由windows自己管理
if (_hAlg) {

BCryptCloseAlgorithmProvider(_hAlg,0);
_hAlg = 0;
}
if (_hKey) {

BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_keyObj) {
ExFreePool(_keyObj);
_keyObj = 0;
}
if (_iv != nullptr) {
ExFreePool(_iv);
_iv = 0;
}
if (_preIv) {
ExFreePool(_preIv);
_preIv = 0;
}
if (_key != Algorithm::GetDefaultKey() && key) {
ExFreePool(_key);
_key = nullptr;
}
symprintk("failed to ctor!rn");

}

template <typename Algorithm>
BlockCipher<Algorithm>::~BlockCipher() {
if (_keyObj) {
ExFreePool(_keyObj);
_keyObj = 0;
}
//对称加密可以省略密钥对象 由windows自己管理
if (_hAlg) {

BCryptCloseAlgorithmProvider(_hAlg,0);
_hAlg = 0;
}
if (_hKey) {

BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_iv != nullptr) {
ExFreePool(_iv);
_iv = 0;
}
if (_preIv) {
ExFreePool(_preIv);
_preIv = 0;
}
if (_key != Algorithm::GetDefaultKey() && _key) {
ExFreePool(_key);
_key = nullptr;
}
}

template <typename Algorithm>
ULONG BlockCipher<Algorithm>::encrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize) {
if (!_hKey || !_hAlg ||!_keyObj) {
return 0;
}
ULONG result = 0;
NTSTATUS status = STATUS_UNSUCCESSFUL;
unsigned char saveIv[8]{ 0 };
if(_iv!=nullptr)
memcpy(saveIv, _iv, 8);
//自动填充到一个块
if (!NT_SUCCESS(status=BCryptEncrypt(_hKey, data, dataSize, 0, _iv,_ivSize,
cryptData, cryptSize, &result, 0))) {

symprintk("fialed to encrypt,errcode->08%xrn", status);
return 0;
}
else {
if (_iv != nullptr)
memcpy(_preIv, saveIv, 8);
return result;
}
}

template<typename Algorithm>
ULONG BlockCipher<Algorithm>::decrypt(PUCHAR data, ULONG dataSize,PUCHAR cryptData, ULONG cryptSize) {

//解密
if (!_hKey || !_hAlg || !_keyObj) {
return 0;
}
ULONG result = 0;
NTSTATUS status = STATUS_UNSUCCESSFUL;
if (!NT_SUCCESS(status = BCryptDecrypt(_hKey,cryptData,cryptSize,0,_preIv
,_ivSize,data,dataSize,&result,0))) {

symprintk("failed to decrypt,errcode->08%xrn", status);
return 0;
}
else return result;

}

class DES {

public:
static constexpr LPCWSTR GetAlgorithmName() {
return BCRYPT_DES_ALGORITHM;
}
static constexpr ULONG GetBlockSize() {

return 8;
}
static PUCHAR GetDefaultKey(){
//DES的密钥是7字节
static UCHAR key[8] = { 0 };
return key;
}
static PUCHAR GetDefaultIV() {
// DES block size is 8 bytes.
static UCHAR iv[8] = { 0 };
return iv;
}
};

class TripleDES {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_3DES_ALGORITHM;
}
static constexpr ULONG GetBlockSize() {
return 24;
}
static UCHAR* GetDefaultKey() {
// 3DES supports key sizes of 21 bytes.
static UCHAR key[24] = { 0 };
return key;
}
static PUCHAR GetDefaultIV() {
// 3DES block size is 8 bytes.
static UCHAR iv[8] = { 0 };
return iv;
}
};

class AES {
public:
static constexpr PCWSTR GetAlgorithmName() {
return BCRYPT_AES_ALGORITHM;
}
static constexpr ULONG GetBlockSize() {
return 16;
}
static UCHAR* GetDefaultKey() {
// AES supports key sizes of 16, 24, or 32 bytes.
static UCHAR key[16] = { 0 };
return key;
}
static UCHAR* GetDefaultIV() {
// AES block size is 16 bytes.
static UCHAR iv[16] = { 0 };
return iv;
}
};

using DESCreator = BlockCipher<DES>;
using TripleDESCreator = BlockCipher<TripleDES>;
using AESCreator = BlockCipher<AES>;

上面还支持了自定义IV,自定义密钥和自定义加密方式(ECB CBC CFB等)。

流加密

这里用RC4做例子,流加密和块加密最大的不同就是流加密对于明文长度是不固定的。

流加密的加密原理是根据密钥(伪随机)生成一个密钥流,然后依次把伪密钥流和明文进行加密运算。

举个例子,假设你的密钥流是1234567890,你第一次调用encrypt使用了前三个数字123,那么第二次调用encrypt就会使用下三个数字456,依此类推。这就是导致连续两次调用encrypt得到的结果不同。

同时,由于密钥流的不同,解密的时候需要重新创建一个对象,而且密钥必须是相同的;这样保证初始化的伪随机的密钥流是相同的;从而可以完成解密。


unsigned char rc4PlainText[1] = { 7 };
unsigned char rc4Buf[1] = { 0 };
char rc4Str[10] = { 0 };
unsigned char rc4decryptCode[1] = { 0 };
kcrypt::RC4Creator rc4test;
result=rc4test.encrypt(rc4PlainText, sizeof rc4PlainText, rc4Buf, sizeof rc4Buf);
kcrypt::hexToStr(rc4Str, sizeof rc4Str, rc4Buf, result);
printk("rc4 str->%srn", rc4Str);
//RC4 is a stream cipher,so we need to create a new rc4Creator instance
//so that we can confirm that stream key is same as we encrypt
kcrypt::RC4Creator rc4Decrypt;
rc4Decrypt.decrypt(rc4decryptCode, sizeof rc4decryptCode, rc4Buf, result);

最终实现如下:


template<typename Algorithm>
class StreamCipher {
public:
StreamCipher(PUCHAR key=nullptr);
~StreamCipher();
ULONG encrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize);
ULONG decrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize);

private:
PUCHAR _key;
BCRYPT_ALG_HANDLE _hAlg;
BCRYPT_KEY_HANDLE _hKey;
PUCHAR _keyObj;

};

template<typename Algorithm>
StreamCipher<Algorithm>::StreamCipher(PUCHAR key):_key(key) {

do {

NTSTATUS status = 0;
//process key
if (_key == nullptr) key = Algorithm::GetDefaultKey();
_key = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, Algorithm::GetKeySize(), 'sym');
if (_key == nullptr) {
symprintk("failed to create key space errcode->%08xrn", status);
break;
}
memcpy(_key, key, Algorithm::GetKeySize());
//open provider
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&_hAlg,
Algorithm::GetAlgorithmName(), 0, 0))) {

symprintk("failed to create algorithm provider! errcode->%08xrn", status);
break;
}
ULONG objSize = 0,result=0;
//get property
if (!NT_SUCCESS(status = BCryptGetProperty(_hAlg, BCRYPT_OBJECT_LENGTH,
(PUCHAR)&objSize, sizeof ULONG, &result, 0))) {
symprintk("failed to get object size! errcode->%08xrn", status);
break;
}

_keyObj = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, objSize, 'sym');
if (_keyObj == nullptr) {
symprintk("failed to create errcode->%08xrn", status);
break;
}

//generate key handle for encrypt or decrypt
if (!NT_SUCCESS(status = BCryptGenerateSymmetricKey(_hAlg, &_hKey,
_keyObj, objSize, _key,
Algorithm::GetKeySize(), 0
))) {
symprintk("failed to generate key! errcode->%08xrn",status);
break;
}
return;
} while (0);

if (_keyObj) {

ExFreePool(_keyObj);
_key = 0;
}
if (_hAlg) {
BCryptCloseAlgorithmProvider(_hAlg, 0);
_hAlg = 0;
}
if (_hKey) {
BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_key) {
ExFreePool(_key);
_key = 0;
}

}
template<typename Algorithm>
StreamCipher<Algorithm>::~StreamCipher() {

if (_keyObj) {

ExFreePool(_keyObj);
_key = 0;
}
if (_hAlg) {
BCryptCloseAlgorithmProvider(_hAlg, 0);
_hAlg = 0;
}
if (_hKey) {
BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_key) {
ExFreePool(_key);
_key = 0;
}

}
template<typename Algorithm>
ULONG StreamCipher<Algorithm>::encrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize) {
if (!_hKey) return 0;

NTSTATUS status = 0;
ULONG result = 0;
if (!NT_SUCCESS(status = BCryptEncrypt(_hKey, data, dataSize,
0, 0, 0, cryptData, cryptSize, &result, 0))) {
symprintk("failed to encrypt! errorcode->08%Xrn",status);
return 0;
}
else return result;

}
template<typename Algorithm>
ULONG StreamCipher<Algorithm>::decrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize) {
if (!_hKey) return 0;

NTSTATUS status = 0;
ULONG result = 0;
if (!NT_SUCCESS(status = BCryptDecrypt(_hKey, cryptData, cryptSize,
0, 0, 0, data, dataSize, &result, 0))) {
symprintk("failed to decrypt! errorcode->08%Xrn", status);
return 0;
}
else return result;

}
class RC4 {
public:
static constexpr LPCWSTR GetAlgorithmName() {
return BCRYPT_RC4_ALGORITHM;
}
static constexpr ULONG GetKeySize() {
return 16; // 128-bit key.
}
static PUCHAR GetDefaultKey() {
static unsigned char key[16] = { 0 };
return key;
}
};

using RC4Creator = StreamCipher<RC4>;

非对称加密

我们知道,非对称加密(也叫)公钥加密,也分为很多种;但是作用不同。

◆RSA

既可以数字签名,也可以用于加密数据。

◆DSA

仅用于数字签名。

◆圆锥曲线加密

也是一种非对称加密,效率高。

windows的cng.sys也提供了上述算法,同时,由于非对称加密有私钥和公钥的概念。

因此,加解密是需要调用BCryptExportKey来获取私钥,公钥长度和真实数值的;此外,因为公钥加密的特殊性,只有密钥的位数是固定的,密钥本身应当是随机生成的。

目前cng.sys所支持的非对称加密公用这些。

分别是DH(密钥交换协议,不是用于加密或签名的,而是用于在不安全的网络环境中创建一个只有两方知道的共享密钥,具体算法不知道,猜测可能是像大数定理那样质数分解)

RSA,DSA,基于椭圆曲线的DSA,基于椭圆取消的DH;

RSA就很熟悉了,它本质上是用n=pq,pq都是质数,然后计算;

选取e(1<e<φ(n)),e 与φ(n)互素,计算d,使得ed=1(modφ(n))

公钥为(n,e),私钥为(n,d)

加密:c=m^e^mod n

解密:e=m^d^mod n

kcrypt--windows内核加密算法库

这里只使用RSA作为加解密的简单演示。

注意,首先BCryptGenerateKeyPair调用这个是生成一对公钥和私钥;此外,还应当提供一个接口,RSA加密大部分是只有公钥没有私钥,解密也是只用到私钥;

可以在类中直接简单地使用加解密函数,但是无法自定义公钥和私钥那么也没有啥意义了;所以最好写两个构造函数,分别是只有私钥和只有公钥的情况;分别对应加解密。

而这样的还是先BCryptOpenAlgorithmProvider然后调用:


status = BCryptImportKeyPair(hAlgo,
NULL,
BCRYPT_RSAPUBLIC_BLOB,
&hKey,
PublicKey,
cbPublicKey,
BCRYPT_NO_KEY_VALIDATION);

来把公钥变成一个密钥句柄,只有这样才能调用BCryptEncypt;私钥用于解密也是同理。

最终代码如下:


#pragma once
#include <fltkernel.h>
#include <bcrypt.h>

namespace kcrypt {
#pragma warning(disable :4996)
#define asymprink(...)do{DbgPrintEx(77,0,__VA_ARGS__);}while(0)
struct keyInfo {
PUCHAR key;
ULONG keySize;
};

template<typename Algorithm>
class ASymCipher {

public:

ASymCipher(ULONG keySize=512);
~ASymCipher();
ULONG encrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize,PUCHAR pubKey=nullptr,ULONG pubSize=0);
ULONG decrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize,PUCHAR priKey=nullptr,ULONG priSize=0);
keyInfo constexpr getPriKey() { return { _priKey,_priSize}; }
keyInfo constexpr getPubKey() { return { _pubKey ,_pubSize}; }

ASymCipher& operator=(ASymCipher&) = delete;
ASymCipher& operator=(ASymCipher&&) = delete;
ASymCipher(ASymCipher&) = delete;
private:
BCRYPT_ALG_HANDLE _hAlg;//create when open alg provider
BCRYPT_KEY_HANDLE _hKey;//use for encrypt and decrypt
PUCHAR _priKey;
PUCHAR _pubKey;
ULONG _pubSize;
ULONG _priSize;
private:
bool rsaCheck(ULONG cbKeySize, ULONG cbData, BOOLEAN bEncrypt);

};

template<typename Algorithm>
inline ASymCipher<Algorithm>::ASymCipher(ULONG keySize)
{
do {
//check key size correct
if (keySize != 512 && keySize != 1024 && keySize != 2048 && keySize != 4096) {
asymprink("key len error!rn");
break;
}

NTSTATUS status = 0;
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&_hAlg,
Algorithm::GetAlgorithmName(), 0, 0))) {
asymprink("failed to open alg provider! errcode->%08xrn",status);
break;
}

//generate key pair and must call BCryptFinalizeKeyPair
if (!NT_SUCCESS(status = BCryptGenerateKeyPair(_hAlg, &_hKey, keySize, 0))) {
asymprink("failed to generate key pair! errcode->08%xrn", status);
break;
}

//if call BCryptFinalizeKeyPair we can not call BCryptSetProperty anymore
if (!NT_SUCCESS(status = BCryptFinalizeKeyPair(_hKey, 0))) {
asymprink("failed to finalize key pair! errcode->%08xrn", status);
break;
}

//get public key and private key size
if (!NT_SUCCESS(status = BCryptExportKey(_hKey, 0, BCRYPT_RSAPUBLIC_BLOB, 0, 0, &_pubSize, 0))) {

asymprink("failed to get pub key size! errcode->%08xrn", status);
break;
}
if (!NT_SUCCESS(status = BCryptExportKey(_hKey, 0, BCRYPT_RSAPRIVATE_BLOB, 0, 0, &_priSize, 0))) {

asymprink("failed to get private key size! errcode->%08xrn", status);
break;
}
_pubKey = (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, _pubSize, 'asym');
_priKey= (PUCHAR)ExAllocatePoolWithTag(NonPagedPool, _priSize, 'asym');
if (!_priKey || !_pubKey) {
asymprink("failed to alloc mem for key! errcode->%08xrn", status);
break;
}
//get public and private key
if (!NT_SUCCESS(status = BCryptExportKey(_hKey, 0, BCRYPT_RSAPUBLIC_BLOB, _pubKey, _pubSize, &_pubSize, 0))) {

asymprink("failed to get pub key! errcode->%08xrn", status);
break;
}
if (!NT_SUCCESS(status = BCryptExportKey(_hKey, 0, BCRYPT_RSAPRIVATE_BLOB, _priKey, _priSize, &_priSize, 0))) {

asymprink("failed to get private key! errcode->%08xrn", status);
break;
}

return;

} while (0);

//fault or err
if (_hKey) {

BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_hAlg) {
BCryptCloseAlgorithmProvider(_hAlg,0);
_hAlg = 0;
}
if (_priKey) {
ExFreePool(_priKey);
_priKey = 0;
}
if (_pubKey) {
ExFreePool(_pubKey);
_pubKey = 0;

}
}

template<typename Algorithm>
inline ASymCipher<Algorithm>::~ASymCipher()
{
if (_hKey) {

BCryptDestroyKey(_hKey);
_hKey = 0;
}
if (_hAlg) {
BCryptCloseAlgorithmProvider(_hAlg,0);
_hAlg = 0;
}
if (_priKey) {
ExFreePool(_priKey);
_priKey = 0;
}
if (_pubKey) {
ExFreePool(_pubKey);
_pubKey = 0;

}

}

//if arg5 isn't nullptr means that we need to import a new key handle to encrypt
template<typename Algorithm>
inline ULONG ASymCipher<Algorithm>::encrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize, PUCHAR pubKey,ULONG pubSize)
{
if (!_hKey) return 0;
if (!rsaCheck(pubSize ? pubSize :_pubSize, dataSize, true)) {
asymprink("key size or data size err!rn");
return 0;
}
ULONG result=0;
NTSTATUS status = 0;
if (pubKey == nullptr) {
//using pkcs1 padding and defualt public key
if (!NT_SUCCESS(status = BCryptEncrypt(_hKey, data, dataSize, 0, 0, 0, cryptData,
cryptSize, &result, BCRYPT_PAD_PKCS1))) {
asymprink("failed to encrypt! errcode->%xrn", status);
return 0;
}
else return result;

}
else {
//we need open new hAlg and create new key handle(public key)
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlg,
Algorithm::GetAlgorithmName(), 0, 0))) {
asymprink("failed to open provider errcode->%08xrn", status);
break;
}

__debugbreak();
//import key handle
if (!NT_SUCCESS(status = BCryptImportKeyPair(hAlg
, nullptr, BCRYPT_RSAPUBLIC_BLOB
, &hKey, pubKey, pubSize
, BCRYPT_NO_KEY_VALIDATION))) {
asymprink("failed to get key handle errcode->%xrn", status);
break;
}

//using pkcs1 padding and defualt public key
if (!NT_SUCCESS(status = BCryptEncrypt(hKey, data, dataSize, 0, 0, 0, cryptData,
cryptSize, &result, BCRYPT_PAD_PKCS1))) {
asymprink("failed to encrypt! errcode->%xrn", status);
result = 0;
break;
}
else break;

} while (0);

if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return result;

}

}

//if arg5 isn't nullptr means that we need to import a new key handle to decrypt
template<typename Algorithm>
inline ULONG ASymCipher<Algorithm>::decrypt(PUCHAR data, ULONG dataSize, PUCHAR cryptData, ULONG cryptSize, PUCHAR priKey,ULONG priSize)
{
if (!_hKey) return 0;
if (!rsaCheck(priSize ? priSize :_priSize, dataSize, false)) {
asymprink("key size or data size err!rn");
return 0;
}
ULONG result = 0;
NTSTATUS status = 0;
//using pkcs1 padding
if (priKey == nullptr) {
if (!NT_SUCCESS(status = BCryptDecrypt(_hKey, cryptData, cryptSize, 0, 0, 0, data,
dataSize, &result, BCRYPT_PAD_PKCS1))) {
asymprink("failed to decrypt! errcode->%xrn", status);
return 0;
}
else return result;

}
else {

//we need open new hAlg and create new key handle(public key)
BCRYPT_ALG_HANDLE hAlg = nullptr;
BCRYPT_KEY_HANDLE hKey = nullptr;
do {
if (!NT_SUCCESS(status = BCryptOpenAlgorithmProvider(&hAlg,
Algorithm::GetAlgorithmName(), 0, 0))) {
asymprink("failed to open provider errcode->%08xrn", status);
break;
}

//import key handle
if (!NT_SUCCESS(status = BCryptImportKeyPair(hAlg
, nullptr, BCRYPT_RSAPRIVATE_BLOB
, &hKey, priKey, priSize
, BCRYPT_NO_KEY_VALIDATION))) {
asymprink("failed to get key handle errcode->%08xrn", status);
break;
}

//using pkcs1 padding and defualt public key
if (!NT_SUCCESS(status = BCryptDecrypt(hKey, cryptData, cryptSize, 0, 0, 0, data,
dataSize, &result, BCRYPT_PAD_PKCS1))) {
asymprink("failed to decrypt! errcode->%xrn", status);
result = 0;
break;
}
else break;

} while (0);

if (hKey) {
BCryptDestroyKey(hKey);
}
if (hAlg) {
BCryptCloseAlgorithmProvider(hAlg, 0);
}
return result;

}

}

/*
the length of the content that can be encrypted
depends on the bis size of the key.
For 512bit key:
Public key length: 91, Private key: 155
For 1024bit:
Public key: 155, Private key: 283
For 2048bit:
Public key: 283, Private key: 539
For 4096bit:
Public key: 539, Private key: 1051
*/
template<typename Algorithm>
inline bool ASymCipher<Algorithm>::rsaCheck(ULONG cbKeySize, ULONG cbData, BOOLEAN bEncrypt)
{
if (bEncrypt)
{
switch (cbKeySize)
{
case 91: // 512bit
if (cbData > 64)
return FALSE;
break;
case 155: // 1024bit
if (cbData > 128)
return FALSE;
break;
case 283: // 2048bit
if (cbData > 256)
return FALSE;
break;
case 539: // 4096bit
if (cbData > 512)
return FALSE;
break;
default:
return FALSE;
break;
}
/*if (cbKeySize - cbData > 27)
return TRUE;
else
return FALSE;*/
return TRUE;
}
else
{
switch (cbKeySize)
{
case 155: // 512bit
case 283: // 1024bit
case 539: // 2048bit
case 1051: // 4096bit
return TRUE;
break;
default:
return FALSE;
break;
}
}
}

class RSA {
public:
static constexpr LPCWSTR GetAlgorithmName() {
return BCRYPT_RSA_ALGORITHM;
}

};

class DSA {
public:
static constexpr LPCWSTR GetAlgorithmName() {
return BCRYPT_DSA_ALGORITHM;
}

};

using RSACreator = ASymCipher<RSA>;
#pragma warning(default :4996)
}

参考

https://zh.wikipedia.org/wiki/%E5%88%86%E7%BB%84%E5%AF%86%E7%A0%81%E5%B7%A5%E4%BD%9C%E6%A8%A1%E5%BC%8F

https://chat.openai.com/

https://learn.microsoft.com/

kcrypt--windows内核加密算法库

看雪ID:Oxygen1a1

https://bbs.kanxue.com/user-home-935881.htm

*本文为看雪论坛优秀文章,由 Oxygen1a1 原创,转载请注明来自看雪社区
kcrypt--windows内核加密算法库

# 往期推荐

1、在 Windows下搭建LLVM 使用环境

2、深入学习smali语法

3、安卓加固脱壳分享

4、Flutter 逆向初探

5、一个简单实践理解栈空间转移

6、记一次某盾手游加固的脱壳与修复

kcrypt--windows内核加密算法库
kcrypt--windows内核加密算法库

球分享

kcrypt--windows内核加密算法库
球点赞
kcrypt--windows内核加密算法库
球在看

原文始发于微信公众号(看雪学苑):kcrypt--windows内核加密算法库

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年8月12日19:43:22
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   kcrypt--windows内核加密算法库http://cn-sec.com/archives/1953110.html

发表评论

匿名网友 填写信息