【发布时间】:2014-05-16 23:49:45
【问题描述】:
假设有一个程序使用 OpenSSL RSA 来保证它的安全性,并且我有它用来解密/加密它的数据的私钥。
这是写该程序协议如何工作的引述:
对于会话打开,客户端生成一个 256 位的 AES 会话密钥,以及一个 128 位 AES 会话 IV(初始化向量)。
客户端向服务器发送 GSP SESSION INIT 消息,使用加密 客户端的 RSA 私钥(即用 private 密钥加密)。 所有 Garena 客户端的 RSA 私钥都相同,已被盗取 来自 Windows EXE,并在此文件的末尾给出:) 因此,加密毫无价值:如果有人能够嗅探 GSP会话,他可以得到加密的GSP SESSION INIT消息,解密 它与 RSA 公钥(可以从 RSA 私钥派生 使用 OpenSSL 非常容易),因此获得 AES 会话密钥和 IV。
在 GSP SESSION INIT 消息之后,所有其他 GSP 消息都被加密 在带有会话密钥和 IV 的 AES CBC 中。密钥和 IV 保持不变 所有会话,双向(即用于加密的 IV 每条后续消息的真正开始 IV 而不是最后一个 密文块应该是这样)。
正如他所说,我只需要一次 RSA(加密 GSP SESSION INIT),然后我们与 AES CBC 进行通信。
这是我使用的 SSL 类:(这只是 RSA 的东西)
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编写的,可以生成具有正确长度的正确加密数据。
我认为读取私钥时应该有问题,因为当我调用 PEM_write_PrivateKey(stdout, localKeypair, NULL, NULL, 0, 0, NULL)
在控制台中打印的密钥与我要读取的私钥完全不同,或者我使用了错误的密钥长度?!我不知道。
欢迎任何帮助
【问题讨论】:
-
@noloader 抱歉,您甚至没有尝试阅读我写的所有内容
-
实际上,我确实通读了它。
pem(3)和evp(3)回答了标题中提出的问题(如何读取和使用密钥)。至于你的协议问题——谁知道呢。我们无法对源文件中的注释做任何事情。它没有告诉我们任何有用的东西。您应该检查参考实现。
标签: c++ encryption cryptography openssl rsa