繁体   English   中英

C ++在OpenSSL中读取和使用RSA私钥

[英]C++ Read and use RSA Private key in OpenSSL

假设有一个程序使用OpenSSL RSA来确保安全性,并且我拥有用于​​解密/加密数据的私钥。

这是引用谁写的程序协议如何正常工作的引用:

对于会话打开,客户端生成一个256位AES会话密钥和一个128位AES会话IV(初始化向量)。

客户端向服务器发送GSP SESSION INIT消息,并使用客户端的RSA私钥加密(即,使用私钥加密)。 RSA私钥对于所有Garena客户端都是相同的,已从Windows EXE中撕下,并在此文件的末尾给出:)因此,加密毫无用处:如果有人能够嗅探GSP会话,他可以获得加密的GSP SESSION INIT消息,使用RSA公钥(可以通过OpenSSL非常容易地从RSA私钥派生)解密它,然后获得AES会话密钥和IV。

在GSP SESSION INIT消息之后,所有其他GSP消息都使用会话密钥和IV在AES CBC中加密。 在所有会话中,密钥和IV在两个方向上都保持不变(也就是说,用于加密每个后续消息的IV实际上是起始IV,而不是应该的最后一个密文块)。

正如他提到的,我只需要一次RSA(对GSP SESSION INIT进行加密),然后我们与AES CBC进行通信。

这是我使用的SSL类:(这只是RSA Stuff)

CGarenaEncrypt::CGarenaEncrypt() {
localKeypair = NULL;
remotePubKey = NULL;

#ifdef PSUEDO_CLIENT
    genTestClientKey();
#endif

init();
}

int CGarenaEncrypt::rsaEncrypt(const unsigned char *msg, size_t msgLen, unsigned char **encMsg, unsigned char **ek, size_t *ekl, unsigned char **iv, size_t *ivl) {
size_t encMsgLen = 0;
size_t blockLen = 0;

*ek = (unsigned char*)malloc(EVP_PKEY_size(localKeypair));
*iv = (unsigned char*)malloc(EVP_MAX_IV_LENGTH);
if(*ek == NULL || *iv == NULL) return FAILURE;
*ivl = EVP_MAX_IV_LENGTH;

*encMsg = (unsigned char*)malloc(msgLen + EVP_MAX_IV_LENGTH);
if(encMsg == NULL) return FAILURE;

if(!EVP_SealInit(rsaEncryptCtx, EVP_aes_256_cbc(), ek, (int*)ekl, *iv, &localKeypair, 1)) {
    return FAILURE;
}

if(!EVP_SealUpdate(rsaEncryptCtx, *encMsg + encMsgLen, (int*)&blockLen, (const unsigned char*)msg, (int)msgLen)) {
    return FAILURE;
}
encMsgLen += blockLen;

if(!EVP_SealFinal(rsaEncryptCtx, *encMsg + encMsgLen, (int*)&blockLen)) {
    return FAILURE;
}
encMsgLen += blockLen;

EVP_CIPHER_CTX_cleanup(rsaEncryptCtx);

return (int)encMsgLen;
}

int CGarenaEncrypt::rsaDecrypt(unsigned char *encMsg, size_t encMsgLen, unsigned char *ek, size_t ekl, unsigned char *iv, size_t ivl, unsigned char **decMsg) {
size_t decLen = 0;
size_t blockLen = 0;
EVP_PKEY *key;

*decMsg = (unsigned char*)malloc(encMsgLen + ivl);
if(decMsg == NULL) return FAILURE;

#ifdef PSUEDO_CLIENT
    key = remotePubKey;
#else
    key = localKeypair;
#endif

if(!EVP_OpenInit(rsaDecryptCtx, EVP_aes_256_cbc(), ek, ekl, iv, key)) {
    return FAILURE;
}

if(!EVP_OpenUpdate(rsaDecryptCtx, (unsigned char*)*decMsg + decLen, (int*)&blockLen, encMsg, (int)encMsgLen)) {
    return FAILURE;
}
decLen += blockLen;

if(!EVP_OpenFinal(rsaDecryptCtx, (unsigned char*)*decMsg + decLen, (int*)&blockLen)) {
    return FAILURE;
}
decLen += blockLen;

EVP_CIPHER_CTX_cleanup(rsaDecryptCtx);

return (int)decLen;
}

int CGarenaEncrypt::readPrivateKey(FILE *fd) {
if(!PEM_read_PrivateKey(fd, &localKeypair, NULL, NULL))
    return FAILURE;

return SUCCESS;
}

int CGarenaEncrypt::init() {
// Initalize contexts
rsaEncryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
aesEncryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));

rsaDecryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));
aesDecryptCtx = (EVP_CIPHER_CTX*)malloc(sizeof(EVP_CIPHER_CTX));

// Always a good idea to check if malloc failed
if(rsaEncryptCtx == NULL || aesEncryptCtx == NULL || rsaDecryptCtx == NULL || aesDecryptCtx == NULL) {
    return FAILURE;
}

// Init these here to make valgrind happy
EVP_CIPHER_CTX_init(rsaEncryptCtx);
EVP_CIPHER_CTX_init(aesEncryptCtx);

EVP_CIPHER_CTX_init(rsaDecryptCtx);
EVP_CIPHER_CTX_init(aesDecryptCtx);

// Init RSA
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL);

if(EVP_PKEY_keygen_init(ctx) <= 0) {
    return FAILURE;
}

if(EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, RSA_KEYLEN) <= 0) {
    return FAILURE;
}

if(EVP_PKEY_keygen(ctx, &localKeypair) <= 0) {
    return FAILURE;
}

EVP_PKEY_CTX_free(ctx);

// Init AES
aesKey = (unsigned char*)malloc(AES_KEYLEN/8);
aesIV = (unsigned char*)malloc(AES_KEYLEN/16);

unsigned char *aesPass = (unsigned char*)malloc(AES_KEYLEN/8);
unsigned char *aesSalt = (unsigned char*)malloc(8);

if(aesKey == NULL || aesIV == NULL || aesPass == NULL || aesSalt == NULL) {
    return FAILURE;
}

// For the AES key we have the option of using a PBKDF (password-baswed key derivation formula)
// or just using straight random data for the key and IV. Depending on your use case, you will
// want to pick one or another.
#ifdef USE_PBKDF
    // Get some random data to use as the AES pass and salt
    if(RAND_bytes(aesPass, AES_KEYLEN/8) == 0) {
        return FAILURE;
    }

    if(RAND_bytes(aesSalt, 8) == 0) {
        return FAILURE;
    }

    if(EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha256(), aesSalt, aesPass, AES_KEYLEN/8, AES_ROUNDS, aesKey, aesIV) == 0) {
        return FAILURE;
    }
#else
    if(RAND_bytes(aesKey, AES_KEYLEN/8) == 0) {
        return FAILURE;
    }

    if(RAND_bytes(aesIV, AES_KEYLEN/16) == 0) {
        return FAILURE;
    }
#endif

free(aesPass);
free(aesSalt);

return SUCCESS;
}

这就是我试图做所有事情的方式:

if (!AfxSocketInit())
Util_Log(0, 2, _T("AfxSocketInit() failed."));

FILE* privKey;

int err  = fopen_s( &privKey, "E:\\gkey.pem", "r" );

if( err != 0 ){
    Util_Log(0, 2, _T("Failed to open 'gkey.pem' file."));
    return;
}

err = m_encrypt.readPrivateKey(privKey);

fclose(privKey);

if( err != 0 ){
    Util_Log(0, 2, _T("readPrivateKey Failed."));
    return;
}


sendGSPSessionInit();

bool CGarenaInterface::sendGSPSessionInit(void)
{
FILE *f;
if(AllocConsole()) {
freopen_s(&f, "CONOUT$", "wt", stdout);
SetConsoleTitle(_T("Debug Console"));
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_RED);
}

Util_Log(0, 0, _T("Sending GSP session init ..."));

int nEncSize = 0;
int nDecSize = 0;
uint8_t* EncOut = NULL;
uint8_t* DecOut = NULL;
unsigned char *ek;
unsigned char *iv;
size_t ekl;
size_t ivl;

CByteBuffer *bytebuffer = new CByteBuffer();
CByteBuffer *bytebuffer2 = new CByteBuffer();
bytebuffer->Allocate(50);
bytebuffer->PutBytes(m_encrypt.aesKey, 32);
bytebuffer->PutBytes(m_encrypt.aesIV, 16);
bytebuffer->PutShort(0xF00F);

printf("\n original:\n");
Util_HexPrint(*bytebuffer, bytebuffer->ByteBuffer->len);

nEncSize = m_encrypt.rsaEncrypt(*bytebuffer, bytebuffer->ByteBuffer->len, &EncOut, &ek, &ekl, &iv, &ivl);

printf("encrypted:\n");
Util_HexPrint(EncOut, nEncSize);

nDecSize = m_encrypt.rsaDecrypt(EncOut, nEncSize, ek, ekl, iv, ivl, &DecOut);

printf("decrypted:\n");
Util_HexPrint(DecOut, nDecSize);

bytebuffer2->Allocate(nEncSize + 6);
bytebuffer2->PutInt(258);
bytebuffer2->PutShort(0x00AD);
bytebuffer2->PutBytes(EncOut, nEncSize);

printf("\n final:\n");
Util_HexPrint(*bytebuffer2, bytebuffer2->ByteBuffer->len);

//  m_socket.Create();
//  m_socket.m_buffer = *bytebuffer2;
//  m_socket.nBufLen = bytebuffer2->ByteBuffer->len;
//  m_socket.Connect(GARENA_MAIN_SERVER_ADDRESS, 7456);


delete bytebuffer;
bytebuffer = NULL;
//  delete bytebuffer2;
//  bytebuffer2 = NULL;

free(EncOut);
free(DecOut);
free(ek);
free(iv);
EncOut = NULL;
DecOut = NULL;
ek = NULL;
iv = NULL;


return true;
}

证明

如图所示,一切正常,但问题出在我连接的服务器上,我希望〜200-250长度的数据包是我的〜70。 这意味着当他们加密数据时(我使用的数据与原始客户端使用的数据相同),加密长度将很大(〜240)。 还有一种替代方法,用Java编写的原始客户端(我实际上在做什么)会生成具有正确长度的正确加密数据。

GCB

我认为读取私钥时应该有问题,因为当我打电话时
PEM_write_PrivateKey(stdout, localKeypair, NULL, NULL, 0, 0, NULL)在控制台中打印的密钥与我要读取的私钥完全不同,或者我使用的密钥长度不正确? 我不知道。

任何帮助将是最欢迎的

首先,您的信息有误。 EVP_Seal ...操作不使用rsa专用加密。 EVP_Seal ..使用rsa_public_encryption,而EVP_Open ..使用rsa_private_decryption。

秒,加密密钥的大小取决于RSA密钥的大小。 在代码中,您使用RSA_KEYLEN作为密钥大小。 我认为您的服务器的密钥大小可能为2048(位)长。 您使用512(位)长。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM